效果- TS 的漂亮打印机
安装
@effect/printer
npm install @effect/printer
pnpm install @effect/printer
yarn add @effect/printer
deno add npm:@effect/printer
bun add @effect/printer
概览
这个模块定义了一个漂亮的打印机,以灵活而方便的方式格式化文本. 想法是结合 Doc从许多小部件中取出,然后使用布局器将其转换为易于渲染 DocStream,然后可以将其转化为多种格式.
终端输出和不信任文本
叶子构造器将控制字符逐字保存 。 渲染返回字符串,不会使终端的不可信文本安全。 在将攻击者控制的文本写入终端之前, 应用 Doc.sanitize 在输入或子树边界。 只有在嵌入式终端序列可信和有意的情况下才能使用已存在的原始构造器和渲染器.
该文件由几个部分组成:
- 下面是图书馆的概况
- 拥有大量文件和实例的实际图书馆
简单示例
作为简单的示范,让我们使用 @effect/printer 来打印以下简单的Haskell类型定义.
example :: Int -> Bool -> Char -> IO ()
首先,我们来安排进口
import * as Doc from "@effect/printer/Doc"
import * as Array from "effect/Array"
import { pipe } from "effect/Function"
接下来,我们穿梭 "->" 字符介于我们类型之间,并添加线索 "::" 字符 :
const prettyTypes = (types: ReadonlyArray<string>): Doc.Doc<never> => {
const symbolDocuments = pipe(
Array.makeBy(types.length - 1, () => Doc.text("->")),
Array.prepend(Doc.text("::"))
)
const typeDocuments = types.map(Doc.text)
const documents = pipe(
Array.zipWith(
symbolDocuments,
typeDocuments,
(left, right) => Doc.catWithSpace(left, right)
)
)
return Doc.align(Doc.seps(documents))
}
这个 seps 函数是使文档缩合的一种方法,但还有其他许多方法(例如:. vsep, (中文(简体) ), cat 财务报告和审定财务报表 fillSep(见A/C.5/49/70,附件)。 在我们的例子中, seps 如果当前行中还有空格,则用于分隔所有文档,如果剩余行太短,则用于新行.
接下来,我们把名字放在类型上,
const prettyDeclaration = (
name: string,
types: ReadonlyArray<string>
): Doc.Doc<never> => Doc.catWithSpace(Doc.text(name), prettyTypes(types))
现在我们可以定义包含某种类型签名的文档:
const name = "example"
const types = ["Int", "Bool", "Char", "IO ()"]
const doc: Doc.Doc<never> = prettyDeclaration(name, types)
该文件现在可以打印了! 作为奖金,它会自动适应可用的空间.
如果页面足够宽 (80 在此情况下,定义是空格分隔的.
const rendered = Doc.render(doc, { style: 'pretty' })
console.log(rendered)
// example :: Int -> Bool -> Char -> IO ()
如果我们将页面宽度缩小到 20 字符,同一文档使垂直对齐:
const rendered = Doc.render(doc, { style: 'pretty', options: { lineWidth: 20 } })
console.log(rendered)
// example :: Int
// -> Bool
// -> Char
// -> IO ()
说到对齐,如果我们不使用 align 组合符 "->" 在每一行的开头, 而不是在下面 "::"。 。 。 .
一般工作流程
╔══════════╗
║ ║ ╭────────────────────╮
║ ║ │ vsep, pretty, <+>, │
║ ║ │ nest, align, … │
║ ║ ╰─────────┬──────────╯
║ ║ │
║ Create ║ │
║ ║ │
║ ║ ▽
║ ║ ╭───────────────────╮
║ ║ │ Doc │
╠══════════╣ │ (rich document) │
║ ║ ╰─────────┬─────────╯
║ ║ │
║ ║ │ Layout algorithms
║ Layout ║ │ e.g. Layout.pretty
║ ║ ▽
║ ║ ╭───────────────────╮
║ ║ │ DocStream │
╠══════════╣ │ (simple document) │
║ ║ ╰─────────┬─────────╯
║ ║ │
║ ║ ├─────────────────────────────╮
║ ║ │ │ treeForm
║ ║ │ ▽
║ ║ │ ╭───────────────╮
║ ║ │ │ DocTree │
║ Render ║ │ ╰───────┬───────╯
║ ║ │ │
║ ║ ╭───────────────────┼─────────────────╮ ╭────────┴────────╮
║ ║ │ │ │ │ │
║ ║ ▽ ▽ ▽ ▽ ▽
║ ║ ╭───────────────╮ ╭───────────────╮ ╭───────────────╮ ╭───────────────╮
║ ║ │ ANSI terminal │ │ Plain Text │ │ other/custom │ │ HTML │
║ ║ ╰───────────────╯ ╰───────────────╯ ╰───────────────╯ ╰───────────────╯
║ ║
╚══════════╝
版式如何运作
提出文档有两个关键概念:可用的宽度和分组.
可用宽度
布局算法将尽量避免超过最大宽度 Doc尽可能插入换行符。 可用的布局梳理器使得可以相当直截了当地说明布局算法(例如通过 seps 函数).
还有丝带宽度的概念. 丝带是一行被印出的部分(即行长没有主缩进). 布局算法采用丝带分数参数,该参数指定了一行在试图分解之前需要填充多少. 宽度80的文档中带宽为0.5会使布局算法尽量避免超过 0.5 * 80 = 40 (指当前缩入深度).
分组
文档可以是 grouped,它告诉布局算法,它应该试图将其倒塌为一行. 如果结果不符合限制(以页和丝带宽表示),则文档不会被更改。 这样可以进行倒向渲染,这样即使原始文档会超过布局限制,我们也能获得良好的效果.
漂亮打印机无法完成的事情
由于Wadler/Leijen算法的设计方式,目前有几件事是不支持的,极有可能没有合理的执行而不显著地改变布局算法. 特别是,这包括:
- 引导符号,而不只是缩进的空格(例如Linux树工具使用)
- 多列布局,特别是相邻有多个等宽单元格的表格
帮助提示
我应该用什么样的注释?
TL; DR - 使用语义注释 Doc,然后在列出“文件”之后,只绘制到后端特定说明.
例如,假设您想要漂亮地打印一些编程语言代码. 如果要将关键词为红色,请注明 Doc 类型有: Keyword 字段(没有任何颜色概念),然后在显示文档后,将注释转换为映射 Keyword 改为 Red (使用时) DocStream.reAnnotate) (中文(简体) ). 备选案文(不建议采用)直接说明 Doc 与 Red。 。 。 .
虽然这两个版本同样运作良好,并会产生相同的产出,但建议的方法有两个重大优点:模块性和可扩展性.
模块化: 改变颜色 Keyword铺设后 Document 意味着只需要一个修改(即: DocStream.reAnnotate)的颜色 Keyword未来需要改变。 如果你有直接注解 Doc有颜色的微量 Red如果需要改变颜色,则需要进行全文搜索/替换.
扩展性: 添加不同的渲染 Keyword 在建议的版本中,与增加以下变式一样简单: DocStream.reAnnotate 转换 Doc 对别的东西的注释。 另一方面,让我们假设你有 Red 作为注释 Doc 而您想要执行的后端没有色彩的概念(考虑纯正的文字, 你现在需要担心什么是地图 红色 {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}这个案子没有可听的 应否略去? 怎么样 红色 在新后端的背景? 此外,请考虑关键字和变量已被标记为红色的情况,但您只想要更改变量的颜色.