#random #substitution #lookup-tables #zip-archive #txt-file #file-format #mad-lib

bin+lib twas

一个使用随机查找表生成文本的应用程序,其方式类似于 Mad Libs 游戏

1 个稳定版本

1.0.0 2023年11月11日

#612 in 文本处理

MPL-2.0 许可证

99KB
1.5K SLoC

TWAS - 随机文本替换应用程序和库

${holiday} 前的夜晚,整座 ${building} 中没有任何生物在动,甚至连 ${{id: animal, aan: true}} 都没有。

在万圣节前夜的夜晚,整个 barn 中没有任何生物在动,甚至连 elephant 都没有。

TWAS(Text With Arbitrary Substitutions 的缩写)是一个文本替换工具,用于用随机选择的列表中的随机词/短语查找表中的项替换标识符,如 ${animal}

关于

twas 是一个随机文本替换工具,用于生成随机故事、游戏事件和其他杂项。给定一个文本提示和一个或多个查找表,twas 将用查找表中的随机文本替换任何 ${...} 提示。如果选定的文本也包含 ${...} 提示,则这些也将被替换(递归)。这样,twas 可以从几个简单的表中生成复杂的故事或描述。

如何安装

安装 twas 应用程序

假设您已在计算机上安装了 cargo,只需运行以下命令即可安装(或更新)twas CLI 应用程序

 cargo install twas --features=app

安装 twas

在您的 Cargo.toml 文件中,只需将以下内容添加到依赖项部分

[dependencies]
twas="1"

用法

要使用 twas,您必须首先定义一个或多个随机查找表,以便在替换文本中引用。查找表可以是以下格式之一(格式细节在以下随机查找表格式部分描述):纯文本 (.txt)、逗号分隔值 (.csv)、JSON (.json) 和 YAML (.yml 或 .yaml)。可以一次性加载多个文件。您还可以包含一个目录,twas 将递归扫描支持文件格式并将其加载,使用相对于提供目录的文件路径作为前缀。也可以包含 .zip 文件,它们将被视为目录。

例如,这是一个简单的随机查找表,包含一组动物列表:animal.txt

antelope
bee
cat
dog
elephant
flying fish

然后,如果您运行 twas -i animal.txt "I have a pet ${animal}.",文本 ${animal} 将被从 animal.txt 中随机选择的一行替换,并将结果打印到终端。文本替换语法和选项在以下文本替换语法和选项部分描述。

文本替换语法和选项

文本替换的目标是通过一个 $ 美元符号后跟 {} 括号来识别,括号内包含要使用的查找表的 ID 或包含更多高级选项的 JSON 对象。例如,在文本字符串 "I have a pet ${animal}." 中的 ${animal} 或在文本字符串 "My pet is ${ {id: animal, aan: true} }." 中的 ${ {id: animal, aan: true} }

随机查找表 ID

要引用查找表,您需要指定其ID。对于.txt文件,ID就是文件名,不带.txt后缀(例如,animal.txt的ID是animal,可以用于文本替换,如${animal})。对于.csv文件,ID是文件名(不带.csv文件后缀),后面跟一个/反斜杠和列名,例如CSV文件zoo-animals.csv中列bird的ID是zoo-animals/bird,可以用于文本替换,如${zoo-animals/plural})。对于JSON和YAML文件,查找表可以嵌套,并且与.csv文件类似,层级用/反斜杠分隔,使用不带后缀的文件名作为基础,例如包含以下内容的JSON文件plant.json{"trees": {"evergreen": ["pine", "cedar"]}}包含IDplant/trees/evergreen。查找ID区分大小写,不允许包含$@。有关特定文件格式查找ID的详细信息,请参阅下面的相关随机查找表格式子部分。

如果您在ID前加上@(例如 ${@fav-pet}),则该ID被视为引用ID,此时它将重用之前的替换而不是从查找表中获取。例如:我有一只宠物 ${animal@fav-pet} 和一只宠物 ${animal}. ${@fav-pet} 是我最喜欢的。在第一句话中保存了第一次替换作为引用ID fav-pet,然后在第二句话中重用它。有关引用的使用,请参阅下面的引用部分。

基本替换语法

要在文本中进行简单的文本替换,只需在文本中添加一个紧跟的美元符号$,后面紧跟一对大括号{},其中包含您要用于替换的查找表ID,例如${ look-up ID }。ID前后的大白字符(例如空格)将被忽略,因此${animal}${ animal }被视为相同。

要将生成的替换文本保存为以后重用的参考,请附加@后跟参考ID(例如${animal@fav-pet})。要使用已保存的参考,只需使用@和参考ID。例如

我有一只宠物 ${animal@fav-pet} 和一只宠物 ${animal}. 我最喜欢的宠物是 ${@fav-pet} => 我有一只狗和一只猫. 狗是我的最爱.

有关使用参考的详细说明,请参阅下面的参考部分。

高级替换语法

文本替换还可以使用双大括号和JSON或YAML语法进行指定,其中查找表ID通过JSON/YAML对象的id字段提供。例如,${{id: animal}}${animal}完全等价。twas提供了通过JSON语法应用文本替换的多个额外选项。选项如下

id(必需)

用于此替换的查找表的ID。如果ID以@开头,则被视为引用ID。ID字段还可以包含使用$进行ID替换,以将ID的一部分替换为保存的引用。例如,在文本我有一只宠物 ${{id: animal@pet}}.中,如果${animal@pet}替换为dog,那么${{id: "names/$pet"}}变为${{id: "names/dog"}},然后从names/dog查找表中替换一个条目。有关引用使用的详细说明,请参阅下面的参考部分。

示例

我有一只宠物 ${{id: animal}}. => 我有一只宠物狗.

计数

count选项允许您从查找表中选取多个条目。您可以指定一个数字或使用RPG骰子表示法(例如,“1d6+1”)从查找表中随机选取一定数量的条目。count选项通常与sep: ", "last-sep: " and "一起使用,以生成逗号分隔的列表。另请参阅methodprefixsuffix

示例

  • 我有一只宠物 ${{id: animal, count: 2}}. => 我有一只宠物狗猫.
  • 我的宠物: ${{id: animal, count: 2, sep: ", ", last-sep: " and "}}. => 我的宠物: 狗和猫.
  • 我的宠物: ${{id: animal, count: 3, sep: ", ", last-sep: " and "}}. => 我的宠物:, 猫和猫.
  • 我的宠物: ${{id: animal, count: 3, sep: ", ", last-sep: " and ", method: shuffle}}. => 我的宠物:, 猫和鸟.

方法

method 选项指定在启用 count 选项抽取多个项目时,从查找表中抽取所使用的随机查找算法。支持的方法有 "random"shuffle。使用 "random" 时,同一项目可能连续多次被抽取。使用 shuffle 时,除非 count 大于查找表中的项目总数,否则同一项目不会再次被抽取。默认方法为 "random"

示例

  • 我的宠物: ${{id: animal, count: 3, sep: ", ", last-sep: " and "}}. => 我的宠物:, 猫和猫.
  • 我的宠物: ${{id: animal, count: 3, sep: ", ", last-sep: " and ", method: shuffle}}. => 我的宠物:, 猫和鸟.

分隔符

使用 count 选项时,提供的 sep 字符串被放置在每个项目之间。如果没有指定,默认值是一个空格字符。通常与 sep: ", "last-sep: " and " 结合使用,以创建逗号分隔的列表。另请参阅 methodprefixsuffix

示例

宠物列表: ${{id: animal, count: 2, sep: ", "}}. => 宠物列表: dog, cat.

last-sep

类似于上述的 sep,但仅放置在最后一个和倒数第二个项目之间

示例

  • 我的宠物: ${{id: animal, count: 3, sep: ", ", last-sep: " and ", method: shuffle}}. => 我的宠物:, 猫和鸟.

前缀

prefix 被添加到从随机查找表中抽取的每个项目之前。这在制作带有 count 选项的随机列表时特别有用。

示例

我的宠物:${{id:animal,计数: 2, "前缀": "\n * "}} =>

My pets:
 * dog
 * cat

后缀

suffix 被添加到从随机查找表中抽取的每个项目的末尾。这在通过 count 选项以结构化格式(如HTML)输出随机列表时特别有用。

示例

我的宠物:<br><ul>${{id:animal,计数: 2, "前缀": "\n<li>", "前缀": "</li>"}}</ul> =>

My pets:<br><ul>
<li>dog</li>
<li>cat</li></ul>

case

case 选项修改替换文本的大小写。通常假设随机查找表中的文本都是小写。支持的 case 值包括

case description 示例
original 无变化(默认) big blue 3D glasses
upper 全部大写字母 BIG BLUE 3D GLASSES
lower 全部小写 big blue 3d glasses
title 每个单词的首字母大写 Big Blue 3D Glasses
first 仅首字母大写 Big blue 3D glasses

ref

如果你使用 ref 选项,随机选择的项将从随机查找表中保存以供重新使用,并使用提供的引用ID。请参阅下面的 引用 部分以了解关于引用的详细描述。

示例

我有一只宠物 ${{id: animal, ref: "fav-pet"}} 和一只宠物 ${{id: animal}}. ${@fav-pet} 是我最喜欢的. => 我有一只宠物狗和一只宠物猫. 狗是我的最爱.

hidden

如果设置为 true,则 hidden 防止替换出现在文本中。这仅在与上面描述的 ref 选项结合使用时才有效,在这种情况下,隐藏的替换被保存为变量以供稍后文本中使用。

示例

${{id: animal, ref: pet, "hidden": true}}I have a pet ${{id: "@pet"}}. => I have a pet dog.

aan

如果将 aan 设置为 true,则在不定冠词 aan 前添加不定冠词,根据随机选择的查找表中的项的拼写进行适当添加。

示例

My favorite animal is ${{id: animal, aan: true}}. => My favorite animal is a dog.

参考

当您想在多个地方使用相同的结果时,您可以使用引用来保存生成的结果并重新使用。例如,假设您正在创建一个关于从 animal 查找表中随机选择的宠物的故事。由于故事多次引用了相同的宠物,您只想从 animal 随机查找表中抽取一次。为了实现这一点,您将第一个 animal 使用保存为引用 pet,然后您在任何需要使用相同引用的地方,指定 @pet 作为 ID 而不是 animal。因此,您的故事文本可能如下所示 "I have a pet ${animal@pet}. ${{id: "@pet", aan: true, "case": "first"}} is a good animal to have as a pet. I love my ${@pet}!",如果 ${animal@pet} 解析为 dog,则变为 "I have a pet dog. A dog is a good animal to have as a pet. I love my dog!"

创建引用

如果使用基本替换语法,您可以通过在查找表ID后直接跟一个参考ID来创建一个引用,例如 ${animal@pet}animal 查找表中随机选择一个条目,并将结果保存为参考ID pet。如果使用JSON语法,则使用 ref 选项将结果保存到指定的参考ID,例如 ${{id: animal, ref: pet}}

使用引用进行文本替换

引用通过提供的参考ID存储,参考ID的使用方式类似于查找表ID,但前面有一个 @ 前缀。例如,${@pet} 将被替换为保存的 pet 引用,同样,${{id: "@pet"}} 也是如此。

如果使用JSON语法,您仍然可以对引用的文本应用额外的选项,例如 aancase

使用引用进行ID替换

您可以用保存的引用的值替换ID字符串的一部分。在这种情况下,您使用 $ 后跟参考ID作为查找ID的一部分(例如 ${pet-names/$pet})。这允许您使用一个随机查找表的结果来决定使用哪个其他查找表。

例如,假设您加载以下 animal.txt 文件

dog
cat
bird
rat

并加载以下 pet-names.csv 文件

bird,cat,dog,rat
pip,paws,spot,whiskers
seed,mew,spike,cheesy
lala,claws,rolf,nibbler

然后您可以使用选择的动物来选择特定的名称,例如:My pet ${animal@pet}'s name is ${{id: "pet-names/$pet", "case": "title"}}. => My pet dog's name is Spot.

使用骰子符号进行随机数

您还可以使用RPG骰子标记将随机数字插入到文本中。数字替换以#井号符号开始,后跟包含骰子表达式的{}花括号,例如#{1d6+2}将被替换为一个介于3到8之间的随机数(表达式"1d6+2"表示"掷一个6面的骰子并加2")。有关支持的骰子表达式语法的更多详细信息,请参阅dicexp crate

随机查找表格式

支持多种格式来定义随机查找表。支持的格式在此处详细描述。

.txt

.txt文件中的每一行都将被解析为一个查找表条目,所有可能的值具有相等的权重。

IDs

此查找表的ID就是该文件名,不带.txt扩展名(例如,animal.txt的ID是animal)。

示例

以下是一个ID为animal的随机查找示例,它以相等的概率随机选择dogcatbirdratanimal.txt

dog
cat
bird
rat

.csv

.csv文件被解释为标准的逗号分隔值(CSV)文件(UTF-8编码),其中第一行是标题行,包含列名,所有后续行是每个列的可能值。每个列都是自己的随机查找表。所有行具有相等的概率,除非存在名为weight的列。如果存在weight列,则每行的概率将按相应的weight列中的十进制值加权。

IDs

CSV文件中每列的ID是filename/column(例如,在pet-names/dog文件中,列dog的ID是pet-names/dog)。

示例

以下示例创建了四个不同的随机查找表,ID分别为pet-names/birdpet-names/catpet-names/dogpet-names/ratpet-names.csv

bird,cat,dog,rat
pip,paws,spot,whiskers
seed,mew,spike,cheesy
lala,claws,rolf,nibbler

以下示例创建了一个ID为rarity/rarity的查找表,有60%的概率抽取"普通",30%的概率抽取"不常见",9%的概率抽取"稀有",1%的概率抽取"非常稀有":rarity.csv

weight,rarity
6,common
3,uncommon
0.9,rare
0.1,very rare

.yaml(和.yml)

YAML文件可以包含一个或多个随机查找表,具有任意层级的嵌套深度。在YAML文件中遇到的任何列表都将被解析为查找表,所有项目具有相同的概率,而使用字符串-数字映射来指定加权概率(例如rarity: {common: 6, uncommon: 3, rare: 0.9, "very rare": 0.1})。表可以通过嵌套的映射对象来组织,每个嵌套都会为查找表ID路径添加一个层级。

IDs

YAML文件中每个随机查找的ID等于文件名(不包含文件后缀)后跟文件中查找表的对象-映射路径。

在简单的只包含列表的文件的情况下(见以下示例中的animal.yaml),ID只是文件名。对于文件底层的对象,ID将是filename/name(见以下示例中的treasure.yaml),而嵌套对象的ID将使用反斜杠分隔符表示嵌套路径,即filename/level1/level2/.../name,见以下colors.yaml示例)。

示例

以下是一个ID为animal的随机查找示例,该查找随机解析为birdcatdograt中的任何一个,具有相同的概率:animal.yaml

- bird
- cat
- dog
- rat

以下示例创建了一个查找表,创建了两个ID为treasure/moneytreasure/junk的表,其中treasure/money中的条目具有不同的概率,但treasure/junk中的所有条目都具有相同的概率:treasure.yaml

money:
  "100 copper pennies": 4
  "10 silver dollars": 1.5
  "1 gold ingot": 0.5
junk:
  - old boot
  - pocket lint
  - broken toy boat

以下示例创建了三个ID为colors/light/primarycolors/paint/primarycolors/paint/secondary/pastel的查找表:colors.yaml

light:
  primary:
    - red
    - green
    - blue
paint:
  primary:
    - red
    - yellow
    - blue
  secondary:
    pastel:
      - peach
      - light green
      - lavender

.json

JSON文件的工作方式与YAML完全相同(见上文)。

IDs

与上述YAML解析相同。

示例

以下是一个ID为animal的随机查找示例,该查找随机解析为birdcatdograt中的任何一个,具有相同的概率:animal.json

[
 "bird",
 "cat",
 "dog",
 "rat"
]

以下示例创建了一个查找表,创建了两个ID为treasure/moneytreasure/junk的表,其中treasure/money中的条目具有不同的概率,但treasure/junk中的所有条目都具有相同的概率:treasure.json

{
 "money": {
  "100 copper pennies": 4,
  "10 silver dollars": 1.5,
  "1 gold ingot": 0.5
 },
 "junk": [
  "old boot",
  "pocket lint",
  "broken toy boat"
 ]
}

以下示例创建三个查找表,其ID分别为:colors/light/primarycolors/paint/primarycolors/paint/secondary/pastelcolors.json

{
 "light": {
  "primary": [
   "red",
   "green",
   "blue"
  ]
 },
 "paint": {
  "primary": [
   "red",
   "yellow",
   "blue"
  ],
  "secondary": {
   "pastel": [
    "peach",
    "light green",
    "lavender"
   ]
  }
 }
}

目录

当你加载一个目录时,twas会递归地扫描目录中所有支持的文件格式并加载所有这些文件。所有加载的文件的ID都将以其在加载目录中的相对文件路径为前缀。因此,如果你加载目录foo/bar,文件foo/bar/animal.txt将具有ID animal,但文件foo/bar/vehicles/cars.txt将具有ID vehicles/cars

IDs

目录中加载的文件的ID将以其在加载目录中的相对子目录路径为前缀。

示例

给定以下目录结构和上面的文件示例

resources/
 ├─ adventure/
 │   ├─ rarity.csv
 │   └─ treasure.yaml
 ├─ colors.yaml
 └─ pets/
     ├─ animal.txt
     └─ pet-names.csv

则加载resources目录将注册以下随机查找表ID列表

adventure/rarity/rarity
adventure/treasure/money
adventure/treasure/junk
colors/light/primary
colors/paint/primary
colors/paint/secondary/pastel
pets/animal
pets/pet-names/bird
pets/pet-names/cat
pets/pet-names/dog
pets/pet-names/rat

.zip

twas加载一个.zip文件时,它会提取它并将它的内容视为目录(见上面)。

IDs

加载到zip存档文件中的文件的ID将以其在加载zip存档中的相对子目录路径为前缀。

示例

给定以下zip存档结构和上面的文件示例

resources.zip
 ├─ adventure/
 │   ├─ rarity.csv
 │   └─ treasure.yaml
 ├─ colors.yaml
 └─ pets/
     ├─ animal.txt
     └─ pet-names.csv

则加载resources目录将注册以下随机查找表ID列表

adventure/rarity/rarity
adventure/treasure/money
adventure/treasure/junk
colors/light/primary
colors/paint/primary
colors/paint/secondary/pastel
pets/animal
pets/pet-names/bird
pets/pet-names/cat
pets/pet-names/dog
pets/pet-names/rat

许可和再分发

twas源代码受Mozilla Public License,v. 2.0条款约束。

依赖项

~12–24MB
~335K SLoC