@nan0web/ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.datasets/README.dataset.jsonl +12 -0
- package/.editorconfig +20 -0
- package/CONTRIBUTING.md +42 -0
- package/LICENSE +15 -0
- package/README.md +238 -0
- package/docs/uk/README.md +240 -0
- package/package.json +64 -0
- package/playground/User.js +52 -0
- package/playground/currency.exchange.js +48 -0
- package/playground/i18n/index.js +21 -0
- package/playground/i18n/uk.js +53 -0
- package/playground/language.form.js +25 -0
- package/playground/main.js +72 -0
- package/playground/registration.form.js +58 -0
- package/playground/topup.telephone.js +62 -0
- package/src/App/Command/Options.js +78 -0
- package/src/App/Command/index.js +9 -0
- package/src/App/Core/CoreApp.js +129 -0
- package/src/App/Core/UI.js +116 -0
- package/src/App/Core/Widget.js +67 -0
- package/src/App/Core/index.js +11 -0
- package/src/App/Scenario.js +45 -0
- package/src/App/User/Command/Message.js +44 -0
- package/src/App/User/Command/Options.js +48 -0
- package/src/App/User/Command/index.js +11 -0
- package/src/App/User/UserApp.js +73 -0
- package/src/App/User/UserApp.test.js +56 -0
- package/src/App/User/UserUI.js +20 -0
- package/src/App/User/UserUI.test.js +51 -0
- package/src/App/User/index.js +15 -0
- package/src/App/index.js +22 -0
- package/src/Component/Process/Input.js +70 -0
- package/src/Component/Process/Process.js +26 -0
- package/src/Component/Process/index.js +5 -0
- package/src/Component/Welcome/Input.js +50 -0
- package/src/Component/Welcome/Welcome.js +26 -0
- package/src/Component/Welcome/index.js +5 -0
- package/src/Component/index.js +9 -0
- package/src/Frame/Frame.js +591 -0
- package/src/Frame/Frame.test.js +429 -0
- package/src/Frame/Props.js +102 -0
- package/src/Locale.js +119 -0
- package/src/Model/User/User.js +56 -0
- package/src/Model/index.js +7 -0
- package/src/README.md.js +371 -0
- package/src/StdIn.js +111 -0
- package/src/StdOut.js +99 -0
- package/src/View/RenderOptions.js +48 -0
- package/src/View/View.js +289 -0
- package/src/View/View.test.js +77 -0
- package/src/core/Form/Form.js +289 -0
- package/src/core/Form/Form.test.js +116 -0
- package/src/core/Form/Input.js +116 -0
- package/src/core/Form/Input.test.js +58 -0
- package/src/core/Form/Message.js +86 -0
- package/src/core/Form/Message.test.js +54 -0
- package/src/core/Form/index.js +11 -0
- package/src/core/InputAdapter.js +41 -0
- package/src/core/InputAdapter.test.js +35 -0
- package/src/core/Message/InputMessage.js +119 -0
- package/src/core/Message/InputMessage.test.js +45 -0
- package/src/core/Message/Message.js +77 -0
- package/src/core/Message/Message.test.js +58 -0
- package/src/core/Message/OutputMessage.js +143 -0
- package/src/core/Message/OutputMessage.test.js +61 -0
- package/src/core/Message/index.js +7 -0
- package/src/core/OutputAdapter.js +50 -0
- package/src/core/OutputAdapter.test.js +35 -0
- package/src/core/Stream.js +71 -0
- package/src/core/Stream.test.js +78 -0
- package/src/core/StreamEntry.js +59 -0
- package/src/core/index.js +13 -0
- package/src/functions.js +38 -0
- package/src/index.js +34 -0
- package/src/index.test.js +14 -0
- package/src/models/SimpleUser.js +18 -0
- package/stories/App/AppView.js +15 -0
- package/stories/App/AppView.test.js +22 -0
- package/stories/App/RenderOptions.js +14 -0
- package/stories/nodejs/interface.test.js +27 -0
- package/system.md +187 -0
- package/system1.md +137 -0
- package/task.md +181 -0
- package/tsconfig.json +23 -0
- package/types/App/Command/Options.d.ts +46 -0
- package/types/App/Command/index.d.ts +8 -0
- package/types/App/Core/CoreApp.d.ts +70 -0
- package/types/App/Core/UI.d.ts +49 -0
- package/types/App/Core/Widget.d.ts +40 -0
- package/types/App/Core/index.d.ts +10 -0
- package/types/App/Scenario.d.ts +26 -0
- package/types/App/User/Command/Message.d.ts +30 -0
- package/types/App/User/Command/Options.d.ts +27 -0
- package/types/App/User/Command/index.d.ts +8 -0
- package/types/App/User/UserApp.d.ts +31 -0
- package/types/App/User/UserUI.d.ts +18 -0
- package/types/App/User/index.d.ts +12 -0
- package/types/App/index.d.ts +14 -0
- package/types/Component/Process/Input.d.ts +48 -0
- package/types/Component/Process/Process.d.ts +13 -0
- package/types/Component/Process/index.d.ts +4 -0
- package/types/Component/Welcome/Input.d.ts +34 -0
- package/types/Component/Welcome/Welcome.d.ts +13 -0
- package/types/Component/Welcome/index.d.ts +4 -0
- package/types/Component/index.d.ts +8 -0
- package/types/Frame/Frame.d.ts +186 -0
- package/types/Frame/Props.d.ts +77 -0
- package/types/Locale.d.ts +55 -0
- package/types/Model/User/User.d.ts +36 -0
- package/types/Model/index.d.ts +6 -0
- package/types/StdIn.d.ts +62 -0
- package/types/StdOut.d.ts +52 -0
- package/types/View/RenderOptions.d.ts +29 -0
- package/types/View/View.d.ts +115 -0
- package/types/core/Form/Form.d.ts +123 -0
- package/types/core/Form/Input.d.ts +69 -0
- package/types/core/Form/Message.d.ts +28 -0
- package/types/core/Form/index.d.ts +5 -0
- package/types/core/InputAdapter.d.ts +28 -0
- package/types/core/Message/InputMessage.d.ts +71 -0
- package/types/core/Message/Message.d.ts +50 -0
- package/types/core/Message/OutputMessage.d.ts +53 -0
- package/types/core/Message/index.d.ts +5 -0
- package/types/core/OutputAdapter.d.ts +33 -0
- package/types/core/Stream.d.ts +27 -0
- package/types/core/StreamEntry.d.ts +45 -0
- package/types/core/index.d.ts +9 -0
- package/types/functions.d.ts +3 -0
- package/types/index.d.ts +20 -0
- package/types/models/SimpleUser.d.ts +21 -0
- package/vitest.config.js +26 -0
package/src/README.md.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { describe, it, before, beforeEach } from "node:test"
|
|
2
|
+
import assert from "node:assert/strict"
|
|
3
|
+
import FS from "@nan0web/db-fs"
|
|
4
|
+
import { NoConsole } from "@nan0web/log"
|
|
5
|
+
import {
|
|
6
|
+
DatasetParser,
|
|
7
|
+
DocsParser,
|
|
8
|
+
runSpawn,
|
|
9
|
+
} from "@nan0web/test"
|
|
10
|
+
import {
|
|
11
|
+
App,
|
|
12
|
+
Component,
|
|
13
|
+
Frame,
|
|
14
|
+
InputMessage,
|
|
15
|
+
Model,
|
|
16
|
+
OutputMessage,
|
|
17
|
+
UIMessage,
|
|
18
|
+
UIForm,
|
|
19
|
+
UIStream,
|
|
20
|
+
View,
|
|
21
|
+
FormInput,
|
|
22
|
+
} from "./index.js"
|
|
23
|
+
import { Welcome } from "./Component/index.js"
|
|
24
|
+
|
|
25
|
+
const fs = new FS()
|
|
26
|
+
let pkg
|
|
27
|
+
|
|
28
|
+
// Load package.json once before tests
|
|
29
|
+
before(async () => {
|
|
30
|
+
const doc = await fs.loadDocument("package.json", {})
|
|
31
|
+
pkg = doc || {}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
let console = new NoConsole()
|
|
35
|
+
|
|
36
|
+
beforeEach((info) => {
|
|
37
|
+
console = new NoConsole()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Core test suite that also serves as the source for README generation.
|
|
42
|
+
*
|
|
43
|
+
* The block comments inside each `it` block are extracted to build
|
|
44
|
+
* the final `README.md`. Keeping the comments here ensures the
|
|
45
|
+
* documentation stays close to the code.
|
|
46
|
+
*/
|
|
47
|
+
function testRender() {
|
|
48
|
+
/**
|
|
49
|
+
* @docs
|
|
50
|
+
* # @nan0web/ui
|
|
51
|
+
*
|
|
52
|
+
* <!-- %PACKAGE_STATUS% -->
|
|
53
|
+
*
|
|
54
|
+
* A lightweight, agnostic UI framework designed with the **nan0web philosophy**
|
|
55
|
+
* — one application logic, many UI implementations.
|
|
56
|
+
*
|
|
57
|
+
* This library provides core classes and utilities for building structured user interfaces.
|
|
58
|
+
* It supports:
|
|
59
|
+
*
|
|
60
|
+
* - Messaging (Input/Output)
|
|
61
|
+
* - Forms with validation
|
|
62
|
+
* - Progress tracking
|
|
63
|
+
* - Component rendering
|
|
64
|
+
* - View management with Frame rendering
|
|
65
|
+
* - App structure with core and user apps
|
|
66
|
+
*
|
|
67
|
+
* Built to work in sync or async, terminal-based or web-based apps,
|
|
68
|
+
* focusing on type safety, minimalism, and pure JavaScript design.
|
|
69
|
+
*
|
|
70
|
+
* ## Installation
|
|
71
|
+
*/
|
|
72
|
+
it("How to install with npm?", () => {
|
|
73
|
+
/**
|
|
74
|
+
* ```bash
|
|
75
|
+
* npm install @nan0web/ui
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
assert.equal(pkg.name, "@nan0web/ui")
|
|
79
|
+
})
|
|
80
|
+
/**
|
|
81
|
+
* @docs
|
|
82
|
+
*/
|
|
83
|
+
it("How to install with pnpm?", () => {
|
|
84
|
+
/**
|
|
85
|
+
* ```bash
|
|
86
|
+
* pnpm add @nan0web/ui
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
assert.equal(pkg.name, "@nan0web/ui")
|
|
90
|
+
})
|
|
91
|
+
/**
|
|
92
|
+
* @docs
|
|
93
|
+
*/
|
|
94
|
+
it("How to install with yarn?", () => {
|
|
95
|
+
/**
|
|
96
|
+
* ```bash
|
|
97
|
+
* yarn add @nan0web/ui
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
assert.equal(pkg.name, "@nan0web/ui")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @docs
|
|
105
|
+
* ## Concepts & Architecture
|
|
106
|
+
*
|
|
107
|
+
* ### Message Flow
|
|
108
|
+
*
|
|
109
|
+
* UI communication is built around messages:
|
|
110
|
+
*
|
|
111
|
+
* - **`UIMessage`** – abstract message base class
|
|
112
|
+
* - **`InputMessage`** – user input message (value, options)
|
|
113
|
+
* - **`OutputMessage`** – system output (content, error, priority)
|
|
114
|
+
*
|
|
115
|
+
* Messages are simple, serializable data containers. They help build
|
|
116
|
+
* decoupled communication systems between UI components.
|
|
117
|
+
*/
|
|
118
|
+
it("How to create input and output messages?", () => {
|
|
119
|
+
//import { InputMessage, OutputMessage } from '@nan0web/ui'
|
|
120
|
+
|
|
121
|
+
const input = InputMessage.from({ value: 'Hello User' })
|
|
122
|
+
const output = OutputMessage.from({ content: ['Welcome to @nan0web/ui'] })
|
|
123
|
+
console.info(input.value) // ← Hello User
|
|
124
|
+
console.info(output.content[0]) // ← Welcome to @nan0web/ui
|
|
125
|
+
assert.equal(input.value, 'Hello User')
|
|
126
|
+
assert.equal(output.content[0], 'Welcome to @nan0web/ui')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @docs
|
|
131
|
+
* ### Forms
|
|
132
|
+
*
|
|
133
|
+
* `UIForm` supports field definitions, data management, and schema validation.
|
|
134
|
+
* Every form includes a title, fields, and current state.
|
|
135
|
+
*
|
|
136
|
+
* Field types include:
|
|
137
|
+
*
|
|
138
|
+
* - `text`
|
|
139
|
+
* - `email`
|
|
140
|
+
* - `number`
|
|
141
|
+
* - `select`
|
|
142
|
+
* - `checkbox`
|
|
143
|
+
* - `textarea`
|
|
144
|
+
*/
|
|
145
|
+
it("How to define and validate a UIForm?", () => {
|
|
146
|
+
//import { UIForm } from '@nan0web/ui'
|
|
147
|
+
|
|
148
|
+
const form = new UIForm({
|
|
149
|
+
title: "Contact Form",
|
|
150
|
+
fields: [
|
|
151
|
+
FormInput.from({ name: "email", label: "Email Address", type: "email", required: true }),
|
|
152
|
+
FormInput.from({ name: "message", label: "Your Message", type: "textarea", required: true })
|
|
153
|
+
],
|
|
154
|
+
state: {
|
|
155
|
+
email: "invalid-email",
|
|
156
|
+
message: "Hello!"
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const result = form.validate()
|
|
161
|
+
console.info(result.isValid) // ← false
|
|
162
|
+
console.info(result.errors.email) // ← Invalid email format
|
|
163
|
+
|
|
164
|
+
assert.equal(result.isValid, false)
|
|
165
|
+
assert.equal(result.errors.email, "Invalid email format")
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @docs
|
|
170
|
+
* ### Components
|
|
171
|
+
*
|
|
172
|
+
* Components render data as frame-ready output.
|
|
173
|
+
*
|
|
174
|
+
* - `Welcome` – greets user by name
|
|
175
|
+
* - `Process` – shows progress bar and time
|
|
176
|
+
*/
|
|
177
|
+
it("How to render the Welcome component?", () => {
|
|
178
|
+
//import { Welcome } from '@nan0web/ui'
|
|
179
|
+
|
|
180
|
+
const frame = Welcome({ user: { name: "Alice" } })
|
|
181
|
+
const firstLine = frame[0].join("")
|
|
182
|
+
console.info(firstLine) // ← Welcome Alice!
|
|
183
|
+
assert.equal(console.output()[0][1], "Welcome Alice!")
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @docs
|
|
188
|
+
* ### View Manager
|
|
189
|
+
*
|
|
190
|
+
* `View` combines components and renders frames.
|
|
191
|
+
*
|
|
192
|
+
* Every view has:
|
|
193
|
+
*
|
|
194
|
+
* - Locale – formatted text, numbers, currency
|
|
195
|
+
* - StdIn / StdOut – input/output streams
|
|
196
|
+
* - Frame – output buffer with visual properties
|
|
197
|
+
*/
|
|
198
|
+
it("How to render frame with View?", () => {
|
|
199
|
+
//import { View } from '@nan0web/ui'
|
|
200
|
+
|
|
201
|
+
const view = new View()
|
|
202
|
+
view.render(1)(["Hello, world"])
|
|
203
|
+
console.info(String(view.frame)) // ← "\rHello, world"
|
|
204
|
+
assert.ok(String(view.frame).includes("Hello, world"))
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @docs
|
|
209
|
+
* ### Frame Rendering
|
|
210
|
+
*
|
|
211
|
+
* `Frame` manages visual rendering with width and height limits.
|
|
212
|
+
* Useful for fixed-size terminals or UI blocks.
|
|
213
|
+
*/
|
|
214
|
+
it("How to create a Frame with fixed size?", () => {
|
|
215
|
+
//import { Frame } from '@nan0web/ui'
|
|
216
|
+
|
|
217
|
+
const frame = new Frame({
|
|
218
|
+
value: [["Frame content"]],
|
|
219
|
+
width: 20,
|
|
220
|
+
height: 5,
|
|
221
|
+
renderMethod: Frame.RenderMethod.APPEND,
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
const rendered = frame.render()
|
|
225
|
+
console.info(rendered.includes("Frame content")) // ← true
|
|
226
|
+
assert.ok(rendered.includes("Frame content"))
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @docs
|
|
231
|
+
* ### App Architecture
|
|
232
|
+
*
|
|
233
|
+
* `App` provides the main application logic.
|
|
234
|
+
*
|
|
235
|
+
* - Core – minimal UI layer
|
|
236
|
+
* - User – user-specific UI commands
|
|
237
|
+
*
|
|
238
|
+
* Each app registers commands and binds them to UI actions.
|
|
239
|
+
*/
|
|
240
|
+
it("How to create a basic user app that greets?", async () => {
|
|
241
|
+
//import { App, View } from '@nan0web/ui'
|
|
242
|
+
|
|
243
|
+
const app = new App.User.App({ name: "GreetApp" })
|
|
244
|
+
const view = new View()
|
|
245
|
+
view.register("Welcome", Welcome)
|
|
246
|
+
|
|
247
|
+
const cmd = App.Command.Message.parse("welcome --user Bob")
|
|
248
|
+
const result = await app.processCommand(cmd, new App.User.UI(app, view))
|
|
249
|
+
console.info(String(result)) // ← Welcome Bob!
|
|
250
|
+
assert.ok(console.output()[0][1].includes("Welcome Bob!"))
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @docs
|
|
255
|
+
* ### Models
|
|
256
|
+
*
|
|
257
|
+
* UI models are plain data objects managed by `Model` classes.
|
|
258
|
+
*
|
|
259
|
+
* - `User` – user data
|
|
260
|
+
*/
|
|
261
|
+
it("How to use a User model?", () => {
|
|
262
|
+
//import { Model } from '@nan0web/ui'
|
|
263
|
+
|
|
264
|
+
const user = new Model.User({ name: "Charlie", email: "charlie@example.com" })
|
|
265
|
+
console.info(user.name) // ← Charlie
|
|
266
|
+
console.info(user.email) // ← charlie@example.com
|
|
267
|
+
assert.equal(user.name, "Charlie")
|
|
268
|
+
assert.equal(user.email, "charlie@example.com")
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @docs
|
|
273
|
+
* ### Testing UI
|
|
274
|
+
*
|
|
275
|
+
* Core unit-tested to ensure stability in different environments.
|
|
276
|
+
*
|
|
277
|
+
* All components, adapters, and models are designed to be testable
|
|
278
|
+
* with minimal setup.
|
|
279
|
+
*/
|
|
280
|
+
it("How to test UI components with assertions?", () => {
|
|
281
|
+
//import { Welcome, InputMessage } from '@nan0web/ui'
|
|
282
|
+
|
|
283
|
+
const output = Welcome({ user: { name: "Test" } })
|
|
284
|
+
const input = InputMessage.from({ value: "test" })
|
|
285
|
+
console.log(output[0].join("")) // ← Welcome Test!
|
|
286
|
+
assert.equal(console.output()[0][1], "Welcome Test!")
|
|
287
|
+
assert.ok(input instanceof InputMessage)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @docs
|
|
292
|
+
* ## Playground Demos
|
|
293
|
+
*
|
|
294
|
+
* The library includes rich playground demos:
|
|
295
|
+
*
|
|
296
|
+
* - [Registration Form](./playground/registration.form.js)
|
|
297
|
+
* - [Currency Exchange](./playground/currency.exchange.js)
|
|
298
|
+
* - [Mobile Top-up](./playground/topup.telephone.js)
|
|
299
|
+
* - [Language Selector](./playground/language.form.js)
|
|
300
|
+
*
|
|
301
|
+
* Run to explore live functionality:
|
|
302
|
+
*/
|
|
303
|
+
it("How to run the playground?", async () => {
|
|
304
|
+
/**
|
|
305
|
+
* ```bash
|
|
306
|
+
* # Clone repository and run playground
|
|
307
|
+
* git clone https://github.com/nan0web/ui.git
|
|
308
|
+
* cd ui
|
|
309
|
+
* npm install
|
|
310
|
+
* npm run playground
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
assert.ok(String(pkg.scripts?.playground).includes("node playground"))
|
|
314
|
+
const response = await runSpawn("git", ["remote", "get-url", "origin"])
|
|
315
|
+
assert.ok(response.code === 0, "git command fails (e.g., not in a git repo)")
|
|
316
|
+
assert.ok(response.text.trim().endsWith(":nan0web/ui.git"))
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @docs
|
|
321
|
+
* ## API Documentation
|
|
322
|
+
*
|
|
323
|
+
* Detailed API docs are available in each class JSDoc.
|
|
324
|
+
* Explore:
|
|
325
|
+
*
|
|
326
|
+
* - [Messages](./src/core/Message/)
|
|
327
|
+
* - [Forms](./src/core/Form/)
|
|
328
|
+
* - [Stream](./src/core/Stream.js)
|
|
329
|
+
* - [Components](./src/Component/)
|
|
330
|
+
* - [View](./src/View/)
|
|
331
|
+
* - [App](./src/App/)
|
|
332
|
+
* - [Models](./src/Model/)
|
|
333
|
+
*
|
|
334
|
+
* ## Contributing
|
|
335
|
+
*/
|
|
336
|
+
it("How to contribute? - [check here](./CONTRIBUTING.md)", async () => {
|
|
337
|
+
assert.equal(pkg.scripts?.precommit, "npm test")
|
|
338
|
+
assert.equal(pkg.scripts?.prepush, "npm test")
|
|
339
|
+
assert.equal(pkg.scripts?.prepare, "husky")
|
|
340
|
+
const text = await fs.loadDocument("CONTRIBUTING.md")
|
|
341
|
+
const str = String(text)
|
|
342
|
+
assert.ok(str.includes("# Contributing"))
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @docs
|
|
347
|
+
* ## License
|
|
348
|
+
*/
|
|
349
|
+
it("How to license ISC? - [check here](./LICENSE)", async () => {
|
|
350
|
+
/** @docs */
|
|
351
|
+
const text = await fs.loadDocument("LICENSE")
|
|
352
|
+
assert.ok(String(text).includes("ISC"))
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
describe("README.md testing", testRender)
|
|
357
|
+
|
|
358
|
+
describe("Rendering README.md", async () => {
|
|
359
|
+
let text = ""
|
|
360
|
+
const format = new Intl.NumberFormat("en-US").format
|
|
361
|
+
const parser = new DocsParser()
|
|
362
|
+
text = String(parser.decode(testRender))
|
|
363
|
+
await fs.saveDocument("README.md", text)
|
|
364
|
+
const dataset = DatasetParser.parse(text, pkg.name)
|
|
365
|
+
await fs.saveDocument(".datasets/README.dataset.jsonl", dataset)
|
|
366
|
+
|
|
367
|
+
it(`document is rendered in README.md [${format(Buffer.byteLength(text))}b]`, async () => {
|
|
368
|
+
const text = await fs.loadDocument("README.md")
|
|
369
|
+
assert.ok(text.includes("## License"))
|
|
370
|
+
})
|
|
371
|
+
})
|
package/src/StdIn.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import EventProcessor from "@nan0web/event/oop"
|
|
2
|
+
import { CommandMessage } from "@nan0web/co"
|
|
3
|
+
import { typeOf } from "@nan0web/types"
|
|
4
|
+
import InputMessage from "./core/Message/InputMessage.js"
|
|
5
|
+
|
|
6
|
+
class Processor extends EventProcessor { }
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handles standard input stream with message buffering.
|
|
10
|
+
*/
|
|
11
|
+
class StdIn extends EventProcessor {
|
|
12
|
+
/** @type {number} Read interval in milliseconds */
|
|
13
|
+
static READ_INTERVAL = 99
|
|
14
|
+
|
|
15
|
+
/** @type {string[]} Messages to ignore */
|
|
16
|
+
static IGNORE_MESSAGES = ["", "undefined"]
|
|
17
|
+
|
|
18
|
+
/** @type {InputMessage[]} Input message buffer */
|
|
19
|
+
stream = []
|
|
20
|
+
|
|
21
|
+
/** @type {Processor} Input processor */
|
|
22
|
+
processor
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new StdIn instance.
|
|
26
|
+
* @param {object} props - StdIn properties
|
|
27
|
+
* @param {Processor} [props.processor] - Input processor
|
|
28
|
+
* @param {InputMessage[]} [props.stream=[]] - Initial input stream
|
|
29
|
+
*/
|
|
30
|
+
constructor(props = {}) {
|
|
31
|
+
super()
|
|
32
|
+
const {
|
|
33
|
+
processor = new Processor(),
|
|
34
|
+
stream = [],
|
|
35
|
+
} = props
|
|
36
|
+
this.processor = processor
|
|
37
|
+
this.stream = stream
|
|
38
|
+
this.processor?.on("data", (data) => {
|
|
39
|
+
this.write(data)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Checks if there are messages waiting in the input stream.
|
|
45
|
+
* @returns {boolean} True if waiting messages, false otherwise
|
|
46
|
+
*/
|
|
47
|
+
get waiting() {
|
|
48
|
+
return this.stream.length > 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Checks if the input stream has ended (no messages left).
|
|
53
|
+
* @returns {boolean} True if no messages left, false otherwise
|
|
54
|
+
*/
|
|
55
|
+
get ended() {
|
|
56
|
+
return 0 === this.stream.length
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reads a message from the input stream.
|
|
61
|
+
* Waits until messages are available if stream is empty.
|
|
62
|
+
* @returns {Promise<InputMessage>} Next input message
|
|
63
|
+
*/
|
|
64
|
+
async read() {
|
|
65
|
+
while (this.ended) {
|
|
66
|
+
await new Promise(resolve => setTimeout(resolve, StdIn.READ_INTERVAL))
|
|
67
|
+
}
|
|
68
|
+
return this.stream.shift() ?? new InputMessage()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Writes a message to the input stream.
|
|
73
|
+
* @param {string} message - Message to write
|
|
74
|
+
* @returns {boolean} True if message accepted, False if ignored
|
|
75
|
+
*/
|
|
76
|
+
write(message) {
|
|
77
|
+
// this.processor?.emit("data", message)
|
|
78
|
+
const text = String(message)
|
|
79
|
+
if (StdIn.IGNORE_MESSAGES.includes(text)) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
this.stream.push(this.decode(text))
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Decodes a message into an InputMessage instance.
|
|
88
|
+
* @param {InputMessage | string[] | any} message - Message to decode
|
|
89
|
+
* @returns {InputMessage} Decoded input message
|
|
90
|
+
*/
|
|
91
|
+
decode(message) {
|
|
92
|
+
if (message instanceof InputMessage) return message
|
|
93
|
+
if (Array.isArray(message) && message.every(typeOf(String))) {
|
|
94
|
+
const parsed = CommandMessage.parse(message)
|
|
95
|
+
return new InputMessage({ value: parsed })
|
|
96
|
+
}
|
|
97
|
+
return new InputMessage(message)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates a StdIn instance from the given input.
|
|
102
|
+
* @param {StdIn|object} input - The input to create from
|
|
103
|
+
* @returns {StdIn} A StdIn instance
|
|
104
|
+
*/
|
|
105
|
+
static from(input) {
|
|
106
|
+
if (input instanceof StdIn) return input
|
|
107
|
+
return new this(input)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default StdIn
|
package/src/StdOut.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { typeOf } from "@nan0web/types"
|
|
2
|
+
import EventProcessor from "@nan0web/event/oop"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handles standard output stream with formatting capabilities.
|
|
6
|
+
*/
|
|
7
|
+
class StdOut extends EventProcessor {
|
|
8
|
+
/** @type {string} End of line character */
|
|
9
|
+
static EOL = "\n"
|
|
10
|
+
|
|
11
|
+
/** @type {string} Beginning of line character */
|
|
12
|
+
static BOL = "\r"
|
|
13
|
+
|
|
14
|
+
/** @type {string} Reset formatting escape code */
|
|
15
|
+
static RESET = "\x1b[0m"
|
|
16
|
+
|
|
17
|
+
/** @type {string} Clear screen escape code */
|
|
18
|
+
static CLEAR = "\x1b[2J\x1b[H"
|
|
19
|
+
|
|
20
|
+
/** @type {object} Color escape codes */
|
|
21
|
+
static COLORS = {
|
|
22
|
+
red: "\x1b[31m",
|
|
23
|
+
green: "\x1b[32m",
|
|
24
|
+
yellow: "\x1b[33m",
|
|
25
|
+
blue: "\x1b[34m",
|
|
26
|
+
magenta: "\x1b[35m",
|
|
27
|
+
cyan: "\x1b[36m",
|
|
28
|
+
white: "\x1b[37m",
|
|
29
|
+
gray: "\x1b[90m",
|
|
30
|
+
black: "\x1b[30m",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @type {Record<string, string>} Style escape codes */
|
|
34
|
+
static STYLES = {
|
|
35
|
+
dim: "\x1b[2m",
|
|
36
|
+
bold: "\x1b[1m",
|
|
37
|
+
underline: "\x1b[4m",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @todo define go top by rows constants.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/** @type {string[]} Output stream buffer */
|
|
45
|
+
stream = []
|
|
46
|
+
|
|
47
|
+
/** @type {number[]} Window size [width, height] */
|
|
48
|
+
windowSize = []
|
|
49
|
+
|
|
50
|
+
/** @type {any} Output processor */
|
|
51
|
+
processor
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new StdOut instance.
|
|
55
|
+
* @param {object} props - StdOut properties
|
|
56
|
+
* @param {any} [props.processor] - Output processor
|
|
57
|
+
* @param {string[]} [props.stream=[]] - Initial output stream
|
|
58
|
+
* @param {number[]} [props.windowSize=[144, 33]] - Window size [width, height]
|
|
59
|
+
*/
|
|
60
|
+
constructor(props = {}) {
|
|
61
|
+
super()
|
|
62
|
+
const {
|
|
63
|
+
processor,
|
|
64
|
+
stream = [],
|
|
65
|
+
windowSize = [144, 33],
|
|
66
|
+
} = props
|
|
67
|
+
this.processor = processor
|
|
68
|
+
this.stream = stream
|
|
69
|
+
this.windowSize = windowSize
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Writes output to the output stream.
|
|
74
|
+
* Must be overwritten by other apps.
|
|
75
|
+
* @param {any} output - Output to write
|
|
76
|
+
* @param {Function} onError - Error handler callback
|
|
77
|
+
*/
|
|
78
|
+
write(output, onError = v => 1) {
|
|
79
|
+
/**
|
|
80
|
+
* @todo manage the
|
|
81
|
+
*/
|
|
82
|
+
if (typeOf(Array)(output)) {
|
|
83
|
+
output = output.join("\n")
|
|
84
|
+
}
|
|
85
|
+
this.stream.push(output)
|
|
86
|
+
this.processor?.write(output, onError)
|
|
87
|
+
this.emit("data", output)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Gets the window size.
|
|
92
|
+
* @returns {number[]} Window size [width, height]
|
|
93
|
+
*/
|
|
94
|
+
getWindowSize() {
|
|
95
|
+
return this.processor?.getWindowSize?.() ?? this.windowSize
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default StdOut
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import Frame from "../Frame/Frame.js"
|
|
2
|
+
|
|
3
|
+
class RenderOptions {
|
|
4
|
+
static DEFAULTS = {
|
|
5
|
+
resizeToView: false,
|
|
6
|
+
translateFrame: false,
|
|
7
|
+
render: true,
|
|
8
|
+
renderMethod: Frame.RenderMethod.VISIBLE,
|
|
9
|
+
}
|
|
10
|
+
/** @type {boolean} [false] */
|
|
11
|
+
resizeToView
|
|
12
|
+
/** @type {boolean} [false] */
|
|
13
|
+
translateFrame
|
|
14
|
+
/** @type {boolean} [true] */
|
|
15
|
+
render
|
|
16
|
+
/** @type {string} */
|
|
17
|
+
renderMethod
|
|
18
|
+
/** @type {number} */
|
|
19
|
+
width
|
|
20
|
+
/** @type {number} */
|
|
21
|
+
height
|
|
22
|
+
constructor(props = {}) {
|
|
23
|
+
const DEFAULTS = this.DEFAULTS
|
|
24
|
+
const {
|
|
25
|
+
resizeToView = DEFAULTS.resizeToView,
|
|
26
|
+
translateFrame = DEFAULTS.translateFrame,
|
|
27
|
+
render = DEFAULTS.render,
|
|
28
|
+
renderMethod = DEFAULTS.renderMethod,
|
|
29
|
+
width = 0,
|
|
30
|
+
height = 0,
|
|
31
|
+
} = props
|
|
32
|
+
this.resizeToView = resizeToView
|
|
33
|
+
this.translateFrame = translateFrame
|
|
34
|
+
this.render = render
|
|
35
|
+
this.renderMethod = renderMethod
|
|
36
|
+
this.width = width
|
|
37
|
+
this.height = height
|
|
38
|
+
}
|
|
39
|
+
get DEFAULTS() {
|
|
40
|
+
return /** @type {typeof RenderOptions} */ (this.constructor).DEFAULTS
|
|
41
|
+
}
|
|
42
|
+
static from(props = {}) {
|
|
43
|
+
if (props instanceof RenderOptions) return props
|
|
44
|
+
return new this(props)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default RenderOptions
|