362 lines
23 KiB
Plaintext
362 lines
23 KiB
Plaintext
page wrnexuscompiler {
|
|
seo {
|
|
title = "@wrnexus/compiler"
|
|
description = "Parser and code generators for the .wrn language."
|
|
}
|
|
|
|
view {
|
|
<div class="docs-shell">
|
|
<header class="topbar">
|
|
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
|
<nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
|
<button data-wire-theme-toggle class="theme-button" aria-label="Toggle theme">Theme</button>
|
|
</header>
|
|
<main class="page package-page">
|
|
<aside class="sidebar"><a href="/packages">← All packages</a><span class="category">Core</span><h1>@wrnexus/compiler</h1><p>Parser and code generators for the .wrn language.</p><code>bun add @wrnexus/compiler@0.2.12</code><nav><a href="#guide">Guide</a><a href="#api">Complete API</a></nav></aside>
|
|
<article class="documentation"><section class="doc-intro"><span class="eyebrow">Core</span><h1>@wrnexus/compiler</h1><p>Parser and code generators for the .wrn language.</p><pre><code>bun add @wrnexus/compiler@0.2.12</code></pre></section><section id="guide" class="prose"><blockquote>Compiler for the <code>.wrn</code> language — tokenizes, parses, and lowers <code>.wrn</code> page and component files to TypeScript.</blockquote>
|
|
<p>Part of the <strong>WRNexusJS</strong> framework — an SSR-first, Bun-native full-stack web framework.</p>
|
|
<h3 id="overview">Overview</h3>
|
|
<p><code>@wrnexus/compiler</code> turns <code>.wrn</code> source into TypeScript that targets the framework's runtime primitives. A <code>.wrn</code> file declares either a <code>page</code> (a route) or a <code>component</code> (a reusable, prop-driven fragment) with blocks for <code>state</code>, <code>view</code> (plain HTML), <code>seo</code>, <code>style</code>, <code>functions</code>, <code>api</code>, <code>ssr</code>/<code>client</code> data bindings, and <code>realtime</code> websocket handlers. The pipeline is <code>source → Lexer → parse() → PageAst → generate() → TypeScript</code>. It is a build/server-side library — the WRNexusJS dev loader calls it to compile <code>.wrn</code> files on the fly, surfacing <code>ParseError</code> as a readable error page.</p>
|
|
<h3 id="installation">Installation</h3>
|
|
<pre data-language="bash"><code>bun add @wrnexus/compiler</code></pre>
|
|
<blockquote>Private package — the machine must be authenticated to the <code>wrnexus</code> npm org</blockquote>
|
|
<blockquote>(a read token in <code>~/.npmrc</code>). Requires <strong>Bun</strong> (Node is not supported).</blockquote>
|
|
<h3 id="api">API</h3>
|
|
<p>All exports come from the package root (<code>@wrnexus/compiler</code>).</p>
|
|
<h4 id="compilewirefile-source-string-string"><code>compileWireFile(source: string): string</code></h4>
|
|
<p>Compile <code>.wrn</code> source to a TypeScript module string. Throws <code>ParseError</code> on invalid input. The output is prefixed with a <code>// compiled from .wrn</code> comment.</p>
|
|
<h4 id="compile-source-string-compileresult"><code>compile(source: string): CompileResult</code></h4>
|
|
<p>Richer entry point that returns the generated code, the AST, and any diagnostics.</p>
|
|
<pre data-language="ts"><code>interface CompileResult {
|
|
code: string;
|
|
ast: PageAst;
|
|
diagnostics: string[];
|
|
}</code></pre>
|
|
<p>On a <code>ParseError</code> it pushes the message into <code>diagnostics</code> and re-throws.</p>
|
|
<h4 id="parse-source-string-pageast"><code>parse(source: string): PageAst</code></h4>
|
|
<p>Run the lexer + recursive-descent parser and return the AST. Throws <code>ParseError</code> (lexer <code>LexError</code>s are caught and rethrown as <code>ParseError</code>).</p>
|
|
<h4 id="generate-ast-pageast-string"><code>generate(ast: PageAst): string</code></h4>
|
|
<p>Lower a <code>PageAst</code> to TypeScript. <code>page</code> ASTs become a default-export page component (plus <code>meta</code>, optional <code>layout</code>, <code>__wrnexusApi</code>/method handlers, <code>websocket</code>, and SSR/CSR data bindings); <code>component</code> ASTs become a module exporting <code>render(props)</code> and <code>__wrnexusComponent</code>.</p>
|
|
<h4 id="lexer"><code>Lexer</code></h4>
|
|
<p>On-demand lexer for <code>.wrn</code>. Yields structural tokens and exposes raw-span readers for the parser.</p>
|
|
<pre data-language="ts"><code>class Lexer {
|
|
pos: number;
|
|
constructor(src: string);
|
|
next(): Token; // consume next structural token
|
|
peek(): Token; // look ahead without consuming
|
|
readPath(): string; // route path, e.g. /users/[id]
|
|
readToLineEnd(): string; // rest of line (state/prop initializers)
|
|
readBalancedBraces(): string; // inner text of a { ... } block, string-aware
|
|
}</code></pre>
|
|
<p><code>Token</code> is <code>{ type: TokenType; value: string; pos: number }</code>, where <code>TokenType</code> is one of <code>ident</code>, <code>string</code>, <code>lbrace</code>, <code>rbrace</code>, <code>lparen</code>, <code>rparen</code>, <code>at</code>, <code>eq</code>, <code>comma</code>, <code>eof</code>.</p>
|
|
<h4 id="errors">Errors</h4>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Class</th><th>Thrown by</th><th>Meaning</th></tr></thead>
|
|
<tbody><tr><td><code>ParseError</code></td><td><code>parse</code>, <code>compile</code>, <code>compileWireFile</code>, <code>generate</code></td><td>Invalid <code>.wrn</code> grammar or (rewrapped) lex failure.</td></tr><tr><td><code>LexError</code></td><td><code>Lexer</code></td><td>Unexpected character / unterminated string / unbalanced braces.</td></tr></tbody></table></div>
|
|
<h4 id="ast-types">AST types</h4>
|
|
<p>Exported type-only symbols describing the parsed tree:</p>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Type</th><th>Description</th></tr></thead>
|
|
<tbody><tr><td><code>PageAst</code></td><td>Root node: <code>kind</code> (`"page" \</td><td>"component"<code>), </code>name<code>, optional </code>layout<code>, </code>props<code>, </code>states<code>, </code>seo<code>, </code>view<code>, </code>styles<code>, </code>functions<code>, </code>dataApis<code>, </code>modeFunctions<code>, </code>apis<code>, </code>realtimes`.</td></tr><tr><td><code>ViewNode</code></td><td><code>{ type: "text"; value }</code> or <code>{ type: "element"; tag; attrs; children }</code>.</td></tr><tr><td><code>Attr</code></td><td><code>{ name; value; event; boolean? }</code> — <code>event</code> marks <code>@event</code> bindings.</td></tr><tr><td><code>StateDecl</code></td><td><code>{ name; expr }</code> — a <code>state x = <expr></code> declaration.</td></tr><tr><td><code>SeoBlock</code></td><td><code>Record<string, string></code> from the <code>seo { ... }</code> block.</td></tr><tr><td><code>ApiBlock</code></td><td><code>{ method; path; body }</code> — a top-level <code>api METHOD /path { ... }</code>.</td></tr><tr><td><code>DataApiBlock</code></td><td><code>{ mode; name; method; path; body }</code> — an <code>api</code> inside an <code>ssr</code>/<code>client</code> block.</td></tr><tr><td><code>DataMode</code></td><td>`"ssr" \</td><td>"client"`.</td></tr><tr><td><code>ModeFunctionsBlock</code></td><td><code>{ mode; body }</code> — a <code>functions { ... }</code> inside an <code>ssr</code>/<code>client</code> block.</td></tr><tr><td><code>RealtimeBlock</code></td><td><code>{ name; handlers }</code> — a <code>realtime <name> { on evt(args) { ... } }</code> block.</td></tr></tbody></table></div>
|
|
<h3 id="usage">Usage</h3>
|
|
<p>Compile a page:</p>
|
|
<pre data-language="ts"><code>import { compileWireFile } from "@wrnexus/compiler";
|
|
|
|
const ts = compileWireFile(`
|
|
page Home {
|
|
state count = 0
|
|
seo { title = "Home" description = "Welcome" }
|
|
view {
|
|
<button @click="count++">Clicked {count} times</button>
|
|
}
|
|
}
|
|
`);
|
|
// ts is a TypeScript module: exports `meta`, and a default page component
|
|
// returning an HTML string, wrapped in a data-scope for the reactive runtime.</code></pre>
|
|
<p>Inspect the AST and diagnostics:</p>
|
|
<pre data-language="ts"><code>import { compile, ParseError } from "@wrnexus/compiler";
|
|
|
|
try {
|
|
const { code, ast, diagnostics } = compile(source);
|
|
console.log(ast.kind, ast.name, ast.states.length);
|
|
} catch (err) {
|
|
if (err instanceof ParseError) console.error(err.message);
|
|
}</code></pre>
|
|
<p>Drive the parse/codegen stages directly:</p>
|
|
<pre data-language="ts"><code>import { parse, generate } from "@wrnexus/compiler";
|
|
|
|
const ast = parse(componentSource); // ast.kind === "component"
|
|
const module = generate(ast); // exports render(props) + __wrnexusComponent</code></pre>
|
|
<p>Use the lexer standalone:</p>
|
|
<pre data-language="ts"><code>import { Lexer } from "@wrnexus/compiler";
|
|
|
|
const lx = new Lexer("page Home {");
|
|
lx.next(); // { type: "ident", value: "page", pos: 0 }
|
|
lx.next(); // { type: "ident", value: "Home", pos: 5 }
|
|
lx.next(); // { type: "lbrace", value: "{", pos: 10 }</code></pre>
|
|
<h3 id="the-wrn-language-as-parsed">The <code>.wrn</code> language (as parsed)</h3>
|
|
<p>A file opens with <code>page <Name></code> or <code>component <Name></code> followed by a <code>{ ... }</code> body containing zero or more members:</p>
|
|
<ul>
|
|
<li><code>layout = "<name>"</code> — selects <code>app/layouts/<name>.wrn</code> (pages only).</li>
|
|
<li><code>props { name = <default> ... }</code> — component props; each default's type drives coercion.</li>
|
|
<li><code>state <ident> = <expr></code> — reactive state seeded from a raw JS expression.</li>
|
|
<li><code>view { <html> }</code> — plain HTML with <code>{expr}</code> interpolation, hyphenated attributes, boolean attributes, <code>@event="..."</code> client bindings, and <code><!-- comments --></code>.</li>
|
|
<li><code>seo { key = "value" ... }</code> — metadata merged into the generated <code>meta</code>.</li>
|
|
<li><code>style { <raw css> }</code> — inlined page/component stylesheet (repeatable).</li>
|
|
<li><code>functions { <raw js> }</code> — shared server-side helpers (repeatable).</li>
|
|
<li><code>api <METHOD> <path> { <raw js> }</code> — route handler, lowered to a <code>METHOD</code> export (repeatable).</li>
|
|
<li><code>ssr { ... }</code> / <code>client { ... }</code> — data blocks holding <code>api <name> <METHOD> <path> { ... }</code> bindings and their own <code>functions { ... }</code>.</li>
|
|
<li><code>realtime <name> { on <evt>(<args>) { <raw js> } ... }</code> — websocket handlers, lowered to a <code>websocket</code> export.</li>
|
|
</ul>
|
|
<p><code>view</code> markup is parsed by a lenient dedicated HTML parser (<code>parseHtmlView</code>); HTML void elements (<code><br></code>, <code><img></code>, …) take no closing tag. Line comments (<code>//</code>) are skipped by the lexer.</p>
|
|
<h3 id="requirements-notes">Requirements / Notes</h3>
|
|
<ul>
|
|
<li>Pure TypeScript with no runtime dependencies; runs under <strong>Bun</strong> as part of the WRNexusJS toolchain (Node is not supported).</li>
|
|
<li>Generated modules target WRNexusJS runtime primitives (<code>data-scope</code>, <code>data-text</code>, <code>data-on-*</code>, <code>data-for</code>, <code>data-component</code>, <code>__wrnexus*</code>/<code>__wire*</code> helpers) — consume the output within a WRNexusJS app, e.g. via <code>@wrnexus/core</code>'s dev loader.</li>
|
|
</ul></section><section id="api" class="prose api"><h2>Complete TypeScript API</h2><p>This declaration is generated from the exact published package and lists its exported functions, classes, interfaces, and types.</p><pre data-language="typescript"><code>/**
|
|
* Recursive-descent parser for `.wrn`, producing a small AST.
|
|
*
|
|
* Grammar (subset of the vision, but real):
|
|
*
|
|
* page <Name> {
|
|
* state <ident> = <expr> // zero or more
|
|
* view { <html> } // plain HTML (see parseHtmlView)
|
|
* seo { title = "Home" description = "..." }
|
|
* ssr { api <name> <METHOD> <path> { <render js> } functions { <raw js> } }
|
|
* client { api <name> <METHOD> <path> { <render js> } functions { <raw js> } }
|
|
* style { <raw css> } // zero or more, inlined with the page
|
|
* functions { <raw js> } // zero or more, shared helpers
|
|
* api <METHOD> <path> { <raw js> } // zero or more
|
|
* realtime <name> { on <evt>(<args>) { <raw js> } * } // zero or more
|
|
* }
|
|
*
|
|
* The `view` block is written as ordinary HTML — nothing new to learn. Text may
|
|
* contain `{expr}` interpolation, attributes may be hyphenated (`data-*`), and
|
|
* `@event="..."` declares a client event binding. See `parseHtmlView`.
|
|
*/
|
|
interface StateDecl {
|
|
name: string;
|
|
/** Raw JS initializer expression, e.g. `0` or `'x'`. */
|
|
expr: string;
|
|
}
|
|
interface Attr {
|
|
name: string;
|
|
value: string;
|
|
/** True for `@event` bindings (vs. plain HTML attributes). */
|
|
event: boolean;
|
|
/** True for a valueless boolean attribute, e.g. `<button disabled>`. */
|
|
boolean?: boolean;
|
|
}
|
|
type ViewNode = {
|
|
type: "text";
|
|
value: string;
|
|
} | {
|
|
type: "element";
|
|
tag: string;
|
|
attrs: Attr[];
|
|
children: ViewNode[];
|
|
}
|
|
/**
|
|
* A server-side loop: `{#each <list> as <item>[, <index>]} …body… {:empty} …empty… {/each}`.
|
|
* `list` is a JS expression (evaluated on the server, may reference an `ssr` data
|
|
* binding). The `body` is rendered once per item with `{item.field}` interpolation;
|
|
* `empty` renders when the list is empty. See codegen `compileEach`.
|
|
*/
|
|
| {
|
|
type: "each";
|
|
list: string;
|
|
item: string;
|
|
index?: string;
|
|
body: ViewNode[];
|
|
empty: ViewNode[];
|
|
}
|
|
/**
|
|
* A server-side conditional: `{#if <expr>} … {:else if <expr>} … {:else} … {/if}`.
|
|
* Rendered branches are chosen on the server. Each branch's `cond` is a JS expression
|
|
* (`null` for the final `{:else}`); the first truthy branch renders. See `compileIfExpr`.
|
|
*/
|
|
| {
|
|
type: "if";
|
|
branches: {
|
|
cond: string | null;
|
|
body: ViewNode[];
|
|
}[];
|
|
};
|
|
interface ApiBlock {
|
|
method: string;
|
|
path: string;
|
|
body: string;
|
|
}
|
|
type SeoBlock = Record<string, string>;
|
|
type DataMode = "ssr" | "client";
|
|
interface DataApiBlock {
|
|
mode: DataMode;
|
|
name: string;
|
|
method: string;
|
|
path: string;
|
|
body: string;
|
|
}
|
|
interface ModeFunctionsBlock {
|
|
mode: DataMode;
|
|
body: string;
|
|
}
|
|
interface RealtimeHandler {
|
|
event: string;
|
|
args: string[];
|
|
body: string;
|
|
}
|
|
interface RealtimeBlock {
|
|
name: string;
|
|
handlers: RealtimeHandler[];
|
|
}
|
|
interface PropDecl {
|
|
name: string;
|
|
/** Raw JS default expression, e.g. `0` or `'Count'`. Its type drives coercion. */
|
|
default: string;
|
|
}
|
|
interface PageAst {
|
|
type: "page";
|
|
/** `page` (a route) or `component` (a reusable, prop-driven fragment). */
|
|
kind: "page" | "component";
|
|
name: string;
|
|
/** Name of the page layout (`app/layouts/<layout>.wrn`), if the page sets one. */
|
|
layout?: string;
|
|
/** Declared component props (empty for pages). */
|
|
props: PropDecl[];
|
|
states: StateDecl[];
|
|
seo: SeoBlock;
|
|
view: ViewNode[];
|
|
styles: string[];
|
|
functions: string[];
|
|
dataApis: DataApiBlock[];
|
|
modeFunctions: ModeFunctionsBlock[];
|
|
apis: ApiBlock[];
|
|
realtimes: RealtimeBlock[];
|
|
}
|
|
declare class ParseError extends Error {
|
|
}
|
|
declare function parse(source: string): PageAst;
|
|
|
|
/**
|
|
* Code generation: lower a `.wrn` AST to TypeScript that targets the framework's
|
|
* existing primitives.
|
|
*
|
|
* state -> a `data-scope` declaration consumed by the runtime
|
|
* view -> an HTML string returned by a page component
|
|
* @event="..." -> data-on-<event>="..."
|
|
* "...{expr}..." -> text kept verbatim ({expr} is mustache for runtime)
|
|
* api="<name>" -> SSR/client data binding declared in a mode block
|
|
* ssrGet/ssrText -> legacy server-side API fetch + render
|
|
* csrGet/csrText -> legacy browser-side API fetch + render
|
|
* style -> an inline page stylesheet
|
|
* functions -> server-only helpers for API/realtime code
|
|
* api M /p {b} -> export const M = async (ctx) => { b }
|
|
* realtime {..} -> export const websocket = { evt(ws, ...args) { b } }
|
|
*/
|
|
|
|
declare function generate(ast: PageAst): string;
|
|
|
|
declare class NativeCompileError extends Error {
|
|
constructor(message: string);
|
|
}
|
|
/** Compile a parsed `.wrn` page to an Expo Router React Native screen. */
|
|
declare function generateNative(ast: PageAst): string;
|
|
|
|
/**
|
|
* Lexer for the `.wrn` language.
|
|
*
|
|
* `.wrn` mixes a small structural grammar (page/state/view/api/realtime) with
|
|
* raw JavaScript bodies. A pure token stream can't represent the raw JS, so the
|
|
* lexer is driven on demand by the parser: it yields structural tokens via
|
|
* `next()`/`peek()`, and exposes `readBalancedBraces()`, `readPath()` and
|
|
* `readToLineEnd()` for the parser to grab raw spans when grammar demands it.
|
|
*/
|
|
type TokenType = "ident" | "string" | "lbrace" | "rbrace" | "lparen" | "rparen" | "at" | "eq" | "comma" | "eof";
|
|
interface Token {
|
|
type: TokenType;
|
|
value: string;
|
|
pos: number;
|
|
}
|
|
declare class LexError extends Error {
|
|
}
|
|
declare class Lexer {
|
|
readonly src: string;
|
|
pos: number;
|
|
constructor(src: string);
|
|
/** Skip whitespace and `// line comments`. */
|
|
private skipTrivia;
|
|
/** Read and consume the next structural token. */
|
|
next(): Token;
|
|
/** Look at the next token without consuming it. */
|
|
peek(): Token;
|
|
private readString;
|
|
/** Read a route path like `/users/[id]` up to whitespace or `{`. */
|
|
readPath(): string;
|
|
/** Read the rest of the current line (used for `state x = <expr>`). */
|
|
readToLineEnd(): string;
|
|
/**
|
|
* Read a `{ ... }` block and return its INNER text (no outer braces), with
|
|
* brace counting that respects string and template literals so a `}` inside a
|
|
* string doesn't end the block early.
|
|
*/
|
|
readBalancedBraces(): string;
|
|
private lineAt;
|
|
}
|
|
|
|
/**
|
|
* @wrnexus/compiler — the `.wrn` language compiler.
|
|
*
|
|
* Pipeline: source ──▶ Lexer ──▶ parse() ──▶ AST ──▶ generate() ──▶ TypeScript
|
|
*
|
|
* See VISION.md for the language design. The MVP supports `page` with `state`,
|
|
* `view`, `api`, and `realtime` blocks, lowering to the framework's primitives.
|
|
*/
|
|
|
|
interface CompileResult {
|
|
code: string;
|
|
ast: PageAst;
|
|
diagnostics: string[];
|
|
}
|
|
/** Compile `.wrn` source into an Expo Router React Native screen. */
|
|
declare function compileNativeWireFile(source: string): string;
|
|
/**
|
|
* Compile `.wrn` source into TypeScript source. Throws `ParseError` on invalid
|
|
* input (the dev loader surfaces this as a readable error page).
|
|
*/
|
|
declare function compileWireFile(source: string): string;
|
|
/** Richer entry point returning the AST and diagnostics alongside the code. */
|
|
declare function compile(source: string): CompileResult;
|
|
|
|
export { type ApiBlock, type Attr, type CompileResult, type DataApiBlock, type DataMode, LexError, Lexer, type ModeFunctionsBlock, NativeCompileError, type PageAst, ParseError, type RealtimeBlock, type SeoBlock, type StateDecl, type ViewNode, compile, compileNativeWireFile, compileWireFile, generate, generateNative, parse };
|
|
</code></pre></section><section id="examples" class="prose examples"><h2>Examples</h2><p>Copy-ready examples taken from this package's published documentation.</p><div class="example-grid"><article class="example-card"><h3>Example 1</h3><pre data-language="bash"><code>bun add @wrnexus/compiler</code></pre></article><article class="example-card"><h3>Example 2</h3><pre data-language="ts"><code>interface CompileResult {
|
|
code: string;
|
|
ast: PageAst;
|
|
diagnostics: string[];
|
|
}</code></pre></article><article class="example-card"><h3>Example 3</h3><pre data-language="ts"><code>class Lexer {
|
|
pos: number;
|
|
constructor(src: string);
|
|
next(): Token; // consume next structural token
|
|
peek(): Token; // look ahead without consuming
|
|
readPath(): string; // route path, e.g. /users/[id]
|
|
readToLineEnd(): string; // rest of line (state/prop initializers)
|
|
readBalancedBraces(): string; // inner text of a { ... } block, string-aware
|
|
}</code></pre></article><article class="example-card"><h3>Example 4</h3><pre data-language="ts"><code>import { compileWireFile } from "@wrnexus/compiler";
|
|
|
|
const ts = compileWireFile(`
|
|
page Home {
|
|
state count = 0
|
|
seo { title = "Home" description = "Welcome" }
|
|
view {
|
|
<button @click="count++">Clicked {count} times</button>
|
|
}
|
|
}
|
|
`);
|
|
// ts is a TypeScript module: exports `meta`, and a default page component
|
|
// returning an HTML string, wrapped in a data-scope for the reactive runtime.</code></pre></article></div></section></article>
|
|
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#overview">Overview</a><a class="toc-level-3" href="#installation">Installation</a><a class="toc-level-3" href="#api">API</a><a class="toc-level-4" href="#compilewirefile-source-string-string">compileWireFile(source: string): string</a><a class="toc-level-4" href="#compile-source-string-compileresult">compile(source: string): CompileResult</a><a class="toc-level-4" href="#parse-source-string-pageast">parse(source: string): PageAst</a><a class="toc-level-4" href="#generate-ast-pageast-string">generate(ast: PageAst): string</a><a class="toc-level-4" href="#lexer">Lexer</a><a class="toc-level-4" href="#errors">Errors</a><a class="toc-level-4" href="#ast-types">AST types</a><a class="toc-level-3" href="#usage">Usage</a><a class="toc-level-3" href="#the-wrn-language-as-parsed">The .wrn language (as parsed)</a><a class="toc-level-3" href="#requirements-notes">Requirements / Notes</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
|
</main>
|
|
<footer>WRNexusJS 0.2.12 · SSR-first · Bun-native · Documentation generated from published package APIs.</footer>
|
|
</div>
|
|
}
|
|
}
|