Rewrite description generator to use json config
This commit is contained in:
parent
e9510092c5
commit
50d41873cd
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1078,6 +1078,7 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio 0.3.2",
|
"tokio 0.3.2",
|
||||||
"tokio-compat-02",
|
"tokio-compat-02",
|
||||||
"toml",
|
"toml",
|
||||||
|
|
|
@ -11,6 +11,7 @@ anyhow = "1.0"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
reqwest = { version = "0.10", default-features = false, features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.10", default-features = false, features = ["json", "rustls-tls"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
tokio = { version = "0.3", features = ["full"] }
|
tokio = { version = "0.3", features = ["full"] }
|
||||||
tokio-compat-02 = "0.1"
|
tokio-compat-02 = "0.1"
|
||||||
|
|
|
@ -1,154 +1,3 @@
|
||||||
[names]
|
|
||||||
prefix = [
|
|
||||||
"feather",
|
|
||||||
"crow",
|
|
||||||
"bramble",
|
|
||||||
"hawk",
|
|
||||||
"fire",
|
|
||||||
"red",
|
|
||||||
"lion",
|
|
||||||
"misty",
|
|
||||||
"tawny",
|
|
||||||
"gray",
|
|
||||||
"running",
|
|
||||||
"golden",
|
|
||||||
"owl",
|
|
||||||
"rabbit",
|
|
||||||
"spider",
|
|
||||||
"leaf",
|
|
||||||
"squirrel",
|
|
||||||
"oak",
|
|
||||||
"storm",
|
|
||||||
"stone",
|
|
||||||
"sand",
|
|
||||||
"dark",
|
|
||||||
"long",
|
|
||||||
"one",
|
|
||||||
"half",
|
|
||||||
"holly",
|
|
||||||
"black",
|
|
||||||
"brindle",
|
|
||||||
"leopard",
|
|
||||||
"tiger",
|
|
||||||
"moth",
|
|
||||||
"spotted",
|
|
||||||
"raven",
|
|
||||||
"sorrel",
|
|
||||||
"cloud",
|
|
||||||
"bright",
|
|
||||||
"lost",
|
|
||||||
"cedar",
|
|
||||||
"ginger",
|
|
||||||
"blue",
|
|
||||||
"white",
|
|
||||||
"willow",
|
|
||||||
"mouse",
|
|
||||||
"dust",
|
|
||||||
"frost",
|
|
||||||
"speckle",
|
|
||||||
"small",
|
|
||||||
"patch",
|
|
||||||
"dapple",
|
|
||||||
"broken",
|
|
||||||
"stumpy",
|
|
||||||
"brown",
|
|
||||||
"wet",
|
|
||||||
"claw",
|
|
||||||
"little",
|
|
||||||
"night",
|
|
||||||
"dawn",
|
|
||||||
"ash",
|
|
||||||
"tall",
|
|
||||||
"crooked",
|
|
||||||
"yellow",
|
|
||||||
"cinder",
|
|
||||||
"bracken",
|
|
||||||
"dead",
|
|
||||||
"bark",
|
|
||||||
"torn",
|
|
||||||
"morning",
|
|
||||||
"mud",
|
|
||||||
"shade",
|
|
||||||
"loud",
|
|
||||||
"silver",
|
|
||||||
"swift",
|
|
||||||
"thorn",
|
|
||||||
"heavy",
|
|
||||||
"fern",
|
|
||||||
"rowan",
|
|
||||||
"gorse",
|
|
||||||
"moss",
|
|
||||||
"russet",
|
|
||||||
"jagged",
|
|
||||||
"rain",
|
|
||||||
"soot",
|
|
||||||
"shrew",
|
|
||||||
"talon",
|
|
||||||
"weasel",
|
|
||||||
"robin",
|
|
||||||
"thistle",
|
|
||||||
"vole",
|
|
||||||
"swallow",
|
|
||||||
"splash",
|
|
||||||
"smoke",
|
|
||||||
"birch",
|
|
||||||
"larch",
|
|
||||||
"berry",
|
|
||||||
"hazel",
|
|
||||||
"web",
|
|
||||||
"rush",
|
|
||||||
"beech",
|
|
||||||
"reed",
|
|
||||||
"mole",
|
|
||||||
"honey",
|
|
||||||
"poppy",
|
|
||||||
"ivy",
|
|
||||||
"snow",
|
|
||||||
"kestrel",
|
|
||||||
"hare",
|
|
||||||
"breeze",
|
|
||||||
"minnow",
|
|
||||||
"pounce",
|
|
||||||
"pebble",
|
|
||||||
"ripple",
|
|
||||||
]
|
|
||||||
suffix = [
|
|
||||||
"kit",
|
|
||||||
"paw",
|
|
||||||
"star",
|
|
||||||
"leaf",
|
|
||||||
"tail",
|
|
||||||
"heart",
|
|
||||||
"claw",
|
|
||||||
"storm",
|
|
||||||
"stripe",
|
|
||||||
"wind",
|
|
||||||
"pelt",
|
|
||||||
"fur",
|
|
||||||
"face",
|
|
||||||
"flower",
|
|
||||||
"ear",
|
|
||||||
"eye",
|
|
||||||
"foot",
|
|
||||||
"nose",
|
|
||||||
"cloud",
|
|
||||||
"fang",
|
|
||||||
"poppy",
|
|
||||||
"whisker",
|
|
||||||
"belly",
|
|
||||||
"stream",
|
|
||||||
"pool",
|
|
||||||
"throat",
|
|
||||||
"brook",
|
|
||||||
"tooth",
|
|
||||||
"step",
|
|
||||||
"frost",
|
|
||||||
"wing",
|
|
||||||
"fall",
|
|
||||||
"bird",
|
|
||||||
"feather",
|
|
||||||
]
|
|
||||||
|
|
||||||
[timing]
|
[timing]
|
||||||
duration = 360
|
duration = 360
|
||||||
|
|
||||||
|
|
297
Description.json
Normal file
297
Description.json
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
{
|
||||||
|
"root": "pelt",
|
||||||
|
"entries": [{
|
||||||
|
"name": "descriptor",
|
||||||
|
"variants": [
|
||||||
|
"sleek",
|
||||||
|
"large",
|
||||||
|
"small",
|
||||||
|
"swift",
|
||||||
|
"skinny",
|
||||||
|
"thin",
|
||||||
|
"young",
|
||||||
|
"old",
|
||||||
|
"long-legged",
|
||||||
|
"long-haired",
|
||||||
|
"long-tailed",
|
||||||
|
"huge",
|
||||||
|
"massive",
|
||||||
|
"battle-scarred",
|
||||||
|
"pretty",
|
||||||
|
"handsome",
|
||||||
|
"beautiful",
|
||||||
|
"mottled"
|
||||||
|
],
|
||||||
|
"next": [{
|
||||||
|
"from": [
|
||||||
|
"massive",
|
||||||
|
"huge",
|
||||||
|
"large"
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
"long-legged",
|
||||||
|
"long-haired",
|
||||||
|
"old",
|
||||||
|
"young",
|
||||||
|
"long-tailed",
|
||||||
|
"battle-scarred",
|
||||||
|
"handsome",
|
||||||
|
"pretty",
|
||||||
|
"beautiful",
|
||||||
|
"mottled"
|
||||||
|
],
|
||||||
|
"chance": 0.5,
|
||||||
|
"join": ", "
|
||||||
|
}, {
|
||||||
|
"from": [
|
||||||
|
"small"
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
"sleek",
|
||||||
|
"swift",
|
||||||
|
"skinny",
|
||||||
|
"thin",
|
||||||
|
"old",
|
||||||
|
"young",
|
||||||
|
"long-haired",
|
||||||
|
"battle-scarred",
|
||||||
|
"handsome",
|
||||||
|
"pretty",
|
||||||
|
"beautiful",
|
||||||
|
"mottled"
|
||||||
|
],
|
||||||
|
"chance": 0.5,
|
||||||
|
"join": ", "
|
||||||
|
}, {
|
||||||
|
"from": [
|
||||||
|
"handsome",
|
||||||
|
"pretty",
|
||||||
|
"beautiful"
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
"old",
|
||||||
|
"young"
|
||||||
|
],
|
||||||
|
"chance": 0.5,
|
||||||
|
"join": ", "
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"name": "eye-color",
|
||||||
|
"variants": [
|
||||||
|
"blue",
|
||||||
|
"amber",
|
||||||
|
"green",
|
||||||
|
"brown"
|
||||||
|
],
|
||||||
|
"depends": [{
|
||||||
|
"variants": "any",
|
||||||
|
"depends": "color-modifier",
|
||||||
|
"chance": 0.3,
|
||||||
|
"order": "{color-modifier} {base}"
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"name": "post-descriptor",
|
||||||
|
"variants": [
|
||||||
|
"long claws",
|
||||||
|
"a twisted paw",
|
||||||
|
"eyes",
|
||||||
|
"paws",
|
||||||
|
"stripes",
|
||||||
|
"spots",
|
||||||
|
"splotches"
|
||||||
|
],
|
||||||
|
"depends": [{
|
||||||
|
"variants": ["eyes"],
|
||||||
|
"depends": "eye-color",
|
||||||
|
"chance": 1,
|
||||||
|
"order": "{eye-color} {base}"
|
||||||
|
}, {
|
||||||
|
"variants": ["paws", "stripes", "spots", "splotches"],
|
||||||
|
"depends": "pelt-color",
|
||||||
|
"chance": 1,
|
||||||
|
"order": "{pelt-color} {base}"
|
||||||
|
}],
|
||||||
|
"next": [{
|
||||||
|
"from": "any",
|
||||||
|
"to": "any",
|
||||||
|
"chance": 0.4,
|
||||||
|
"join": " and "
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"name": "solid",
|
||||||
|
"variants": ["solid"]
|
||||||
|
}, {
|
||||||
|
"name": "very",
|
||||||
|
"variants": ["very"]
|
||||||
|
}, {
|
||||||
|
"name": "color-modifier",
|
||||||
|
"variants": [
|
||||||
|
"dark",
|
||||||
|
"light",
|
||||||
|
"pale",
|
||||||
|
"bright"
|
||||||
|
],
|
||||||
|
"depends": [{
|
||||||
|
"variants": "any",
|
||||||
|
"depends": "very",
|
||||||
|
"chance": 0.2,
|
||||||
|
"order": "{very} {base}"
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"name": "pelt-color",
|
||||||
|
"variants": [
|
||||||
|
"black",
|
||||||
|
"black-and-white",
|
||||||
|
"black-and-gray",
|
||||||
|
"gray",
|
||||||
|
"gray-and-white",
|
||||||
|
"silver",
|
||||||
|
"blue-gray",
|
||||||
|
"ginger",
|
||||||
|
"brown",
|
||||||
|
"reddish-brown",
|
||||||
|
"golden-brown",
|
||||||
|
"dusky-brown",
|
||||||
|
"white"
|
||||||
|
],
|
||||||
|
"depends": [{
|
||||||
|
"variants": [
|
||||||
|
"black",
|
||||||
|
"gray",
|
||||||
|
"silver",
|
||||||
|
"blue-gray",
|
||||||
|
"ginger",
|
||||||
|
"brown",
|
||||||
|
"reddish-brown",
|
||||||
|
"golden-brown",
|
||||||
|
"dusky-brown",
|
||||||
|
"white"
|
||||||
|
],
|
||||||
|
"depends": "solid",
|
||||||
|
"chance": 0.2,
|
||||||
|
"order": "{solid} {base}"
|
||||||
|
}, {
|
||||||
|
"variants": ["black"],
|
||||||
|
"depends": "color-modifier",
|
||||||
|
"chance": 0.3,
|
||||||
|
"order": "{color-modifier} {base}",
|
||||||
|
"weights": [
|
||||||
|
[1, "dark"]
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
"variants": [
|
||||||
|
"gray",
|
||||||
|
"silver",
|
||||||
|
"blue-gray",
|
||||||
|
"ginger",
|
||||||
|
"brown",
|
||||||
|
"reddish-brown",
|
||||||
|
"golden-brown"
|
||||||
|
],
|
||||||
|
"depends": "color-modifier",
|
||||||
|
"chance": 0.3,
|
||||||
|
"order": "{color-modifier} {base}",
|
||||||
|
"weights": [
|
||||||
|
[1, "light"],
|
||||||
|
[1, "pale"],
|
||||||
|
[1, "bright"]
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
"variants": ["white"],
|
||||||
|
"depends": "color-modifier",
|
||||||
|
"chance": 0.3,
|
||||||
|
"order": "{color-modifier} {base}",
|
||||||
|
"weights": [
|
||||||
|
[1, "bright"]
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"name": "gembder",
|
||||||
|
"variants": [
|
||||||
|
"she-cat",
|
||||||
|
"queen",
|
||||||
|
"tom"
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
"name": "pelt",
|
||||||
|
"variants": [
|
||||||
|
"tortoiseshell",
|
||||||
|
"tabby",
|
||||||
|
"cat"
|
||||||
|
],
|
||||||
|
"silent": ["cat"],
|
||||||
|
"depends": [{
|
||||||
|
"variants": ["tortoiseshell", "tabby"],
|
||||||
|
"depends": "pelt-color",
|
||||||
|
"chance": 0.5,
|
||||||
|
"order": "{pelt-color} {base}"
|
||||||
|
}, {
|
||||||
|
"variants": ["cat"],
|
||||||
|
"depends": "pelt-color",
|
||||||
|
"chance": 1,
|
||||||
|
"order": "{pelt-color} {base}"
|
||||||
|
}, {
|
||||||
|
"variants": ["tortoiseshell"],
|
||||||
|
"depends": "gembder",
|
||||||
|
"chance": 0.5,
|
||||||
|
"order": "{base} {gembder}",
|
||||||
|
"weights": [
|
||||||
|
[80, "she-cat"],
|
||||||
|
[20, "queen"],
|
||||||
|
[1, "tom"]
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
"variants": ["tabby"],
|
||||||
|
"depends": "gembder",
|
||||||
|
"chance": 0.5,
|
||||||
|
"order": "{base} {gembder}"
|
||||||
|
}, {
|
||||||
|
"variants": ["cat"],
|
||||||
|
"depends": "gembder",
|
||||||
|
"chance": 0.5,
|
||||||
|
"order": "{base} {gembder}",
|
||||||
|
"default": "cat"
|
||||||
|
}, {
|
||||||
|
"variants": "any",
|
||||||
|
"depends": "descriptor",
|
||||||
|
"chance": 0.5,
|
||||||
|
"order": "{descriptor} {base}"
|
||||||
|
}, {
|
||||||
|
"variants": ["tabby"],
|
||||||
|
"depends": "post-descriptor",
|
||||||
|
"chance": 0.5,
|
||||||
|
"order": "{base} with {post-descriptor}",
|
||||||
|
"weights": [
|
||||||
|
[2, "long claws"],
|
||||||
|
[1, "a twisted paw"],
|
||||||
|
[4, "eyes"],
|
||||||
|
[3, "paws"],
|
||||||
|
[2, "stripes"]
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
"variants": ["tortoiseshell"],
|
||||||
|
"depends": "post-descriptor",
|
||||||
|
"chance": 0.5,
|
||||||
|
"order": "{base} with {post-descriptor}",
|
||||||
|
"weights": [
|
||||||
|
[2, "long claws"],
|
||||||
|
[1, "a twisted paw"],
|
||||||
|
[4, "eyes"],
|
||||||
|
[3, "paws"],
|
||||||
|
[2, "splotches"]
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
"variants": ["cat"],
|
||||||
|
"depends": "post-descriptor",
|
||||||
|
"chance": 0.5,
|
||||||
|
"order": "{base} with {post-descriptor}",
|
||||||
|
"weights": [
|
||||||
|
[2, "long claws"],
|
||||||
|
[1, "a twisted paw"],
|
||||||
|
[4, "eyes"],
|
||||||
|
[3, "paws"],
|
||||||
|
[2, "spots"]
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
152
Name.json
Normal file
152
Name.json
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
{
|
||||||
|
"prefix": [
|
||||||
|
"feather",
|
||||||
|
"crow",
|
||||||
|
"bramble",
|
||||||
|
"hawk",
|
||||||
|
"fire",
|
||||||
|
"red",
|
||||||
|
"lion",
|
||||||
|
"misty",
|
||||||
|
"tawny",
|
||||||
|
"gray",
|
||||||
|
"running",
|
||||||
|
"golden",
|
||||||
|
"owl",
|
||||||
|
"rabbit",
|
||||||
|
"spider",
|
||||||
|
"leaf",
|
||||||
|
"squirrel",
|
||||||
|
"oak",
|
||||||
|
"storm",
|
||||||
|
"stone",
|
||||||
|
"sand",
|
||||||
|
"dark",
|
||||||
|
"long",
|
||||||
|
"one",
|
||||||
|
"half",
|
||||||
|
"holly",
|
||||||
|
"black",
|
||||||
|
"brindle",
|
||||||
|
"leopard",
|
||||||
|
"tiger",
|
||||||
|
"moth",
|
||||||
|
"spotted",
|
||||||
|
"raven",
|
||||||
|
"sorrel",
|
||||||
|
"cloud",
|
||||||
|
"bright",
|
||||||
|
"lost",
|
||||||
|
"cedar",
|
||||||
|
"ginger",
|
||||||
|
"blue",
|
||||||
|
"white",
|
||||||
|
"willow",
|
||||||
|
"mouse",
|
||||||
|
"dust",
|
||||||
|
"frost",
|
||||||
|
"speckle",
|
||||||
|
"small",
|
||||||
|
"patch",
|
||||||
|
"dapple",
|
||||||
|
"broken",
|
||||||
|
"stumpy",
|
||||||
|
"brown",
|
||||||
|
"wet",
|
||||||
|
"claw",
|
||||||
|
"little",
|
||||||
|
"night",
|
||||||
|
"dawn",
|
||||||
|
"ash",
|
||||||
|
"tall",
|
||||||
|
"crooked",
|
||||||
|
"yellow",
|
||||||
|
"cinder",
|
||||||
|
"bracken",
|
||||||
|
"dead",
|
||||||
|
"bark",
|
||||||
|
"torn",
|
||||||
|
"morning",
|
||||||
|
"mud",
|
||||||
|
"shade",
|
||||||
|
"loud",
|
||||||
|
"silver",
|
||||||
|
"swift",
|
||||||
|
"thorn",
|
||||||
|
"heavy",
|
||||||
|
"fern",
|
||||||
|
"rowan",
|
||||||
|
"gorse",
|
||||||
|
"moss",
|
||||||
|
"russet",
|
||||||
|
"jagged",
|
||||||
|
"rain",
|
||||||
|
"soot",
|
||||||
|
"shrew",
|
||||||
|
"talon",
|
||||||
|
"weasel",
|
||||||
|
"robin",
|
||||||
|
"thistle",
|
||||||
|
"vole",
|
||||||
|
"swallow",
|
||||||
|
"splash",
|
||||||
|
"smoke",
|
||||||
|
"birch",
|
||||||
|
"larch",
|
||||||
|
"berry",
|
||||||
|
"hazel",
|
||||||
|
"web",
|
||||||
|
"rush",
|
||||||
|
"beech",
|
||||||
|
"reed",
|
||||||
|
"mole",
|
||||||
|
"honey",
|
||||||
|
"poppy",
|
||||||
|
"ivy",
|
||||||
|
"snow",
|
||||||
|
"kestrel",
|
||||||
|
"hare",
|
||||||
|
"breeze",
|
||||||
|
"minnow",
|
||||||
|
"pounce",
|
||||||
|
"pebble",
|
||||||
|
"ripple"
|
||||||
|
],
|
||||||
|
"suffix": [
|
||||||
|
"kit",
|
||||||
|
"paw",
|
||||||
|
"star",
|
||||||
|
"leaf",
|
||||||
|
"tail",
|
||||||
|
"heart",
|
||||||
|
"claw",
|
||||||
|
"storm",
|
||||||
|
"stripe",
|
||||||
|
"wind",
|
||||||
|
"pelt",
|
||||||
|
"fur",
|
||||||
|
"face",
|
||||||
|
"flower",
|
||||||
|
"ear",
|
||||||
|
"eye",
|
||||||
|
"foot",
|
||||||
|
"nose",
|
||||||
|
"cloud",
|
||||||
|
"fang",
|
||||||
|
"poppy",
|
||||||
|
"whisker",
|
||||||
|
"belly",
|
||||||
|
"stream",
|
||||||
|
"pool",
|
||||||
|
"throat",
|
||||||
|
"brook",
|
||||||
|
"tooth",
|
||||||
|
"step",
|
||||||
|
"frost",
|
||||||
|
"wing",
|
||||||
|
"fall",
|
||||||
|
"bird",
|
||||||
|
"feather"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -1,728 +1,272 @@
|
||||||
use rand::Rng;
|
use anyhow::Result;
|
||||||
use std::{collections::HashSet, fmt};
|
use rand::{seq::SliceRandom, Rng};
|
||||||
|
use std::{collections::HashSet, path::Path};
|
||||||
|
|
||||||
fn select_weighted<T>(weights: Vec<(u8, T)>, rng: &mut impl Rng, nothing_weight: u8) -> Option<T> {
|
fn select(variants: &HashSet<String>, weights: &[(u64, String)], rng: &mut impl Rng) -> String {
|
||||||
let total = weights.iter().fold(0, |acc, (i, _)| acc + i) + nothing_weight;
|
let filtered = weights
|
||||||
|
.iter()
|
||||||
if total == 0 {
|
.filter(|(_, name)| variants.contains(name))
|
||||||
return None;
|
.collect::<Vec<_>>();
|
||||||
}
|
|
||||||
|
|
||||||
|
let total = filtered.iter().fold(0, |acc, (weight, _)| acc + weight);
|
||||||
let selected = rng.gen_range(0, total);
|
let selected = rng.gen_range(0, total);
|
||||||
|
|
||||||
let (_, opt) = weights
|
let (s, _) = filtered
|
||||||
.into_iter()
|
.iter()
|
||||||
.fold((selected, None), |(selected, acc), (weight, item)| {
|
.fold((None, selected), |(opt, count), (weight, item)| {
|
||||||
if acc.is_some() {
|
if opt.is_some() {
|
||||||
return (selected, acc);
|
return (opt, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if selected.saturating_sub(weight) == 0 {
|
let new_count = count.saturating_sub(*weight);
|
||||||
return (0, Some(item));
|
|
||||||
|
if new_count == 0 {
|
||||||
|
return (Some(item), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
(selected.saturating_sub(weight), None)
|
(None, new_count)
|
||||||
});
|
});
|
||||||
|
|
||||||
opt
|
s.unwrap().to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(serde::Deserialize)]
|
||||||
enum Descriptor {
|
struct Entry {
|
||||||
Sleek,
|
name: String,
|
||||||
Large,
|
variants: HashSet<String>,
|
||||||
Small,
|
|
||||||
Swift,
|
#[serde(default)]
|
||||||
Skinny,
|
silent: HashSet<String>,
|
||||||
Thin,
|
|
||||||
Young,
|
#[serde(default)]
|
||||||
Old,
|
next: Vec<Next>,
|
||||||
LongLeg,
|
|
||||||
LongHair,
|
#[serde(default)]
|
||||||
LongTail,
|
depends: Vec<Depend>,
|
||||||
Huge,
|
|
||||||
Massive,
|
|
||||||
Scars,
|
|
||||||
Pretty,
|
|
||||||
Handsome,
|
|
||||||
Beautiful,
|
|
||||||
Mottled,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Descriptor {
|
impl Entry {
|
||||||
fn weights(variant: PeltVariant) -> Vec<(u8, Descriptor)> {
|
fn gen(
|
||||||
if PeltVariant::Base == variant {
|
&self,
|
||||||
vec![
|
weights: Option<&[(u64, String)]>,
|
||||||
(1, Descriptor::Sleek),
|
entries: &[Entry],
|
||||||
(1, Descriptor::Large),
|
rng: &mut impl Rng,
|
||||||
(1, Descriptor::Small),
|
) -> String {
|
||||||
(1, Descriptor::Swift),
|
let variant = self.gen_variant(weights, rng);
|
||||||
(1, Descriptor::Skinny),
|
|
||||||
(1, Descriptor::Thin),
|
let building = self.gen_depends(&variant, entries, rng);
|
||||||
(1, Descriptor::Young),
|
|
||||||
(1, Descriptor::Old),
|
let mut seen = HashSet::new();
|
||||||
(1, Descriptor::LongLeg),
|
seen.insert(variant.clone());
|
||||||
(1, Descriptor::LongHair),
|
self.gen_next(building, &variant, weights, entries, &mut seen, rng)
|
||||||
(1, Descriptor::LongTail),
|
}
|
||||||
(1, Descriptor::Huge),
|
|
||||||
(1, Descriptor::Massive),
|
fn gen_variant(&self, weights: Option<&[(u64, String)]>, rng: &mut impl Rng) -> String {
|
||||||
(1, Descriptor::Scars),
|
if let Some(weights) = weights {
|
||||||
(1, Descriptor::Handsome),
|
return select(&self.variants, &weights, rng);
|
||||||
(1, Descriptor::Pretty),
|
|
||||||
(1, Descriptor::Beautiful),
|
|
||||||
(1, Descriptor::Mottled),
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
vec![
|
|
||||||
(1, Descriptor::Sleek),
|
|
||||||
(1, Descriptor::Large),
|
|
||||||
(1, Descriptor::Small),
|
|
||||||
(1, Descriptor::Swift),
|
|
||||||
(1, Descriptor::Skinny),
|
|
||||||
(1, Descriptor::Thin),
|
|
||||||
(1, Descriptor::Young),
|
|
||||||
(1, Descriptor::Old),
|
|
||||||
(1, Descriptor::LongLeg),
|
|
||||||
(1, Descriptor::LongHair),
|
|
||||||
(1, Descriptor::LongTail),
|
|
||||||
(1, Descriptor::Huge),
|
|
||||||
(1, Descriptor::Massive),
|
|
||||||
(1, Descriptor::Scars),
|
|
||||||
(1, Descriptor::Handsome),
|
|
||||||
(1, Descriptor::Pretty),
|
|
||||||
(1, Descriptor::Beautiful),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn next_weights(&self, variant: PeltVariant) -> Vec<(u8, Descriptor)> {
|
self.variants
|
||||||
match self {
|
|
||||||
Descriptor::Sleek => vec![],
|
|
||||||
Descriptor::Massive | Descriptor::Huge | Descriptor::Large
|
|
||||||
if variant == PeltVariant::Base =>
|
|
||||||
{
|
|
||||||
vec![
|
|
||||||
(1, Descriptor::LongLeg),
|
|
||||||
(1, Descriptor::LongHair),
|
|
||||||
(1, Descriptor::Old),
|
|
||||||
(1, Descriptor::Young),
|
|
||||||
(1, Descriptor::LongTail),
|
|
||||||
(1, Descriptor::Scars),
|
|
||||||
(1, Descriptor::Handsome),
|
|
||||||
(1, Descriptor::Pretty),
|
|
||||||
(1, Descriptor::Beautiful),
|
|
||||||
(1, Descriptor::Mottled),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Descriptor::Massive | Descriptor::Huge | Descriptor::Large => vec![
|
|
||||||
(1, Descriptor::LongLeg),
|
|
||||||
(1, Descriptor::LongHair),
|
|
||||||
(1, Descriptor::Old),
|
|
||||||
(1, Descriptor::Young),
|
|
||||||
(1, Descriptor::LongTail),
|
|
||||||
(1, Descriptor::Scars),
|
|
||||||
(1, Descriptor::Handsome),
|
|
||||||
(1, Descriptor::Pretty),
|
|
||||||
(1, Descriptor::Beautiful),
|
|
||||||
],
|
|
||||||
Descriptor::Small if variant == PeltVariant::Base => vec![
|
|
||||||
(1, Descriptor::Sleek),
|
|
||||||
(1, Descriptor::Swift),
|
|
||||||
(1, Descriptor::Skinny),
|
|
||||||
(1, Descriptor::Thin),
|
|
||||||
(1, Descriptor::Old),
|
|
||||||
(1, Descriptor::Young),
|
|
||||||
(1, Descriptor::LongHair),
|
|
||||||
(1, Descriptor::Scars),
|
|
||||||
(1, Descriptor::Handsome),
|
|
||||||
(1, Descriptor::Pretty),
|
|
||||||
(1, Descriptor::Beautiful),
|
|
||||||
(1, Descriptor::Mottled),
|
|
||||||
],
|
|
||||||
Descriptor::Small => vec![
|
|
||||||
(1, Descriptor::Sleek),
|
|
||||||
(1, Descriptor::Swift),
|
|
||||||
(1, Descriptor::Skinny),
|
|
||||||
(1, Descriptor::Thin),
|
|
||||||
(1, Descriptor::Old),
|
|
||||||
(1, Descriptor::Young),
|
|
||||||
(1, Descriptor::LongHair),
|
|
||||||
(1, Descriptor::Scars),
|
|
||||||
(1, Descriptor::Handsome),
|
|
||||||
(1, Descriptor::Pretty),
|
|
||||||
(1, Descriptor::Beautiful),
|
|
||||||
],
|
|
||||||
Descriptor::Swift => vec![],
|
|
||||||
Descriptor::Skinny => vec![],
|
|
||||||
Descriptor::Thin => vec![],
|
|
||||||
Descriptor::Young => vec![],
|
|
||||||
Descriptor::Old => vec![],
|
|
||||||
Descriptor::LongLeg => vec![],
|
|
||||||
Descriptor::LongHair => vec![],
|
|
||||||
Descriptor::LongTail => vec![],
|
|
||||||
Descriptor::Scars => vec![],
|
|
||||||
Descriptor::Handsome => vec![(1, Descriptor::Old), (1, Descriptor::Young)],
|
|
||||||
Descriptor::Pretty => vec![(1, Descriptor::Old), (1, Descriptor::Young)],
|
|
||||||
Descriptor::Beautiful => vec![(1, Descriptor::Old), (1, Descriptor::Young)],
|
|
||||||
Descriptor::Mottled => vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Descriptor {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let s = match self {
|
|
||||||
Descriptor::Sleek => "sleek",
|
|
||||||
Descriptor::Large => "large",
|
|
||||||
Descriptor::Small => "small",
|
|
||||||
Descriptor::Swift => "swift",
|
|
||||||
Descriptor::Skinny => "skinny",
|
|
||||||
Descriptor::Thin => "thin",
|
|
||||||
Descriptor::Young => "young",
|
|
||||||
Descriptor::Old => "old",
|
|
||||||
Descriptor::LongLeg => "long-legged",
|
|
||||||
Descriptor::LongHair => "long-haired",
|
|
||||||
Descriptor::LongTail => "long-tailed",
|
|
||||||
Descriptor::Huge => "huge",
|
|
||||||
Descriptor::Massive => "massive",
|
|
||||||
Descriptor::Scars => "battle-scarred",
|
|
||||||
Descriptor::Handsome => "handsome",
|
|
||||||
Descriptor::Pretty => "pretty",
|
|
||||||
Descriptor::Beautiful => "beautiful",
|
|
||||||
Descriptor::Mottled => "mottled",
|
|
||||||
};
|
|
||||||
|
|
||||||
write!(f, "{}", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Descriptors {
|
|
||||||
inner: Vec<Descriptor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Descriptors {
|
|
||||||
fn gen(rng: &mut impl Rng, variant: PeltVariant) -> Self {
|
|
||||||
let mut v = vec![];
|
|
||||||
if let Some(first) = select_weighted(Descriptor::weights(variant), rng, 10) {
|
|
||||||
let mut first = first;
|
|
||||||
let mut next_weights;
|
|
||||||
loop {
|
|
||||||
next_weights = first.next_weights(variant);
|
|
||||||
v.push(first);
|
|
||||||
if let Some(next) = select_weighted(next_weights, rng, 10) {
|
|
||||||
first = next;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Descriptors { inner: v }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.inner.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Descriptors {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let s = self
|
|
||||||
.inner
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|d| d.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ");
|
.choose(rng)
|
||||||
|
|
||||||
write!(f, "{}", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum PostDescriptor {
|
|
||||||
LongClaw,
|
|
||||||
TwistedPaw,
|
|
||||||
Eyes(EyeColor),
|
|
||||||
Paw(PeltColor),
|
|
||||||
Stripe(PeltColor),
|
|
||||||
Spot(PeltColor),
|
|
||||||
Splotch(PeltColor),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
||||||
enum PostDescriptorVariant {
|
|
||||||
LongClaw,
|
|
||||||
TwistedPaw,
|
|
||||||
Eyes,
|
|
||||||
Paw,
|
|
||||||
Stripe,
|
|
||||||
Spot,
|
|
||||||
Splotch,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PostDescriptorVariant {
|
|
||||||
fn weights(pelt_variant: PeltVariant) -> Vec<(u8, PostDescriptorVariant)> {
|
|
||||||
match pelt_variant {
|
|
||||||
PeltVariant::Tabby => vec![
|
|
||||||
(2, PostDescriptorVariant::LongClaw),
|
|
||||||
(1, PostDescriptorVariant::TwistedPaw),
|
|
||||||
(4, PostDescriptorVariant::Eyes),
|
|
||||||
(3, PostDescriptorVariant::Paw),
|
|
||||||
(3, PostDescriptorVariant::Stripe),
|
|
||||||
],
|
|
||||||
PeltVariant::Tortoiseshell => vec![
|
|
||||||
(2, PostDescriptorVariant::LongClaw),
|
|
||||||
(1, PostDescriptorVariant::TwistedPaw),
|
|
||||||
(4, PostDescriptorVariant::Eyes),
|
|
||||||
(3, PostDescriptorVariant::Paw),
|
|
||||||
(2, PostDescriptorVariant::Splotch),
|
|
||||||
],
|
|
||||||
PeltVariant::Base => vec![
|
|
||||||
(2, PostDescriptorVariant::LongClaw),
|
|
||||||
(1, PostDescriptorVariant::TwistedPaw),
|
|
||||||
(4, PostDescriptorVariant::Eyes),
|
|
||||||
(3, PostDescriptorVariant::Paw),
|
|
||||||
(2, PostDescriptorVariant::Spot),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_post_descriptor(self, rng: &mut impl Rng) -> PostDescriptor {
|
|
||||||
match self {
|
|
||||||
PostDescriptorVariant::LongClaw => PostDescriptor::LongClaw,
|
|
||||||
PostDescriptorVariant::TwistedPaw => PostDescriptor::TwistedPaw,
|
|
||||||
PostDescriptorVariant::Eyes => PostDescriptor::Eyes(EyeColor::gen(rng)),
|
|
||||||
PostDescriptorVariant::Paw => PostDescriptor::Paw(PeltColor::gen(rng, true)),
|
|
||||||
PostDescriptorVariant::Stripe => PostDescriptor::Stripe(PeltColor::gen(rng, true)),
|
|
||||||
PostDescriptorVariant::Spot => PostDescriptor::Spot(PeltColor::gen(rng, true)),
|
|
||||||
PostDescriptorVariant::Splotch => PostDescriptor::Splotch(PeltColor::gen(rng, true)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for PostDescriptor {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
PostDescriptor::LongClaw => write!(f, "long claws"),
|
|
||||||
PostDescriptor::TwistedPaw => write!(f, "a twisted paw"),
|
|
||||||
PostDescriptor::Eyes(color) => write!(f, "{} eyes", color),
|
|
||||||
PostDescriptor::Paw(color) => write!(f, "{} paws", color),
|
|
||||||
PostDescriptor::Stripe(color) => write!(f, "{} stripes", color),
|
|
||||||
PostDescriptor::Spot(color) => write!(f, "{} spots", color),
|
|
||||||
PostDescriptor::Splotch(color) => write!(f, "{} splotches", color),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct PostDescriptors {
|
|
||||||
inner: Vec<PostDescriptor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PostDescriptors {
|
|
||||||
fn gen(rng: &mut impl Rng, pelt_variant: PeltVariant) -> Self {
|
|
||||||
let mut v = vec![];
|
|
||||||
let mut hs = HashSet::new();
|
|
||||||
while let Some(variant) =
|
|
||||||
select_weighted(PostDescriptorVariant::weights(pelt_variant), rng, 10)
|
|
||||||
{
|
|
||||||
let new = hs.insert(variant);
|
|
||||||
if !new {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
v.push(variant.into_post_descriptor(rng));
|
|
||||||
}
|
|
||||||
PostDescriptors { inner: v }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.inner.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for PostDescriptors {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
if self.inner.len() == 0 {
|
|
||||||
return write!(f, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
let joined = self
|
|
||||||
.inner
|
|
||||||
.iter()
|
|
||||||
.map(|pd| pd.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" and ");
|
|
||||||
|
|
||||||
write!(f, "with {}", joined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum ColorModifier {
|
|
||||||
Dark,
|
|
||||||
Light,
|
|
||||||
Pale,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ColorModifier {
|
|
||||||
fn gen(rng: &mut impl Rng) -> Option<Self> {
|
|
||||||
select_weighted(
|
|
||||||
vec![
|
|
||||||
(1, ColorModifier::Dark),
|
|
||||||
(1, ColorModifier::Light),
|
|
||||||
(1, ColorModifier::Pale),
|
|
||||||
],
|
|
||||||
rng,
|
|
||||||
9,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ColorModifier {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let s = match self {
|
|
||||||
ColorModifier::Dark => "dark",
|
|
||||||
ColorModifier::Light => "light",
|
|
||||||
ColorModifier::Pale => "pale",
|
|
||||||
};
|
|
||||||
|
|
||||||
write!(f, "{}", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct PeltColor {
|
|
||||||
pelt_color: InnerPeltColor,
|
|
||||||
modifier: Option<(bool, ColorModifier)>,
|
|
||||||
solid: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PeltColor {
|
|
||||||
fn gen(rng: &mut impl Rng, can_be_solid: bool) -> Self {
|
|
||||||
let pelt_color = InnerPeltColor::gen(rng);
|
|
||||||
let solid = can_be_solid && pelt_color.is_solid() && rng.gen_bool(0.2);
|
|
||||||
PeltColor {
|
|
||||||
pelt_color,
|
|
||||||
modifier: ColorModifier::gen(rng).map(|modifier| (rng.gen_bool(0.2), modifier)),
|
|
||||||
solid,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_solid(&self) -> bool {
|
|
||||||
self.solid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for PeltColor {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match &self.modifier {
|
|
||||||
Some((true, modifier)) => write!(f, "very {} {}", modifier, self.pelt_color),
|
|
||||||
Some((false, modifier)) => write!(f, "{} {}", modifier, self.pelt_color),
|
|
||||||
None if self.is_solid() => write!(f, "solid {}", self.pelt_color),
|
|
||||||
None => write!(f, "{}", self.pelt_color),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
enum InnerPeltColor {
|
|
||||||
Black(Option<BlackSecondColor>),
|
|
||||||
Gray(Option<GraySecondColor>),
|
|
||||||
Silver,
|
|
||||||
BlueGray,
|
|
||||||
Ginger,
|
|
||||||
Brown,
|
|
||||||
ReddishBrown,
|
|
||||||
GoldenBrown,
|
|
||||||
DuskyBrown,
|
|
||||||
White,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerPeltColor {
|
|
||||||
fn gen(rng: &mut impl Rng) -> Self {
|
|
||||||
select_weighted(
|
|
||||||
vec![
|
|
||||||
(1, InnerPeltColor::Black(Some(BlackSecondColor::Gray))),
|
|
||||||
(1, InnerPeltColor::Black(Some(BlackSecondColor::White))),
|
|
||||||
(2, InnerPeltColor::Black(None)),
|
|
||||||
(1, InnerPeltColor::Gray(Some(GraySecondColor::White))),
|
|
||||||
(2, InnerPeltColor::Gray(None)),
|
|
||||||
(2, InnerPeltColor::Silver),
|
|
||||||
(2, InnerPeltColor::BlueGray),
|
|
||||||
(2, InnerPeltColor::Ginger),
|
|
||||||
(2, InnerPeltColor::Brown),
|
|
||||||
(2, InnerPeltColor::ReddishBrown),
|
|
||||||
(2, InnerPeltColor::GoldenBrown),
|
|
||||||
(2, InnerPeltColor::DuskyBrown),
|
|
||||||
(2, InnerPeltColor::White),
|
|
||||||
],
|
|
||||||
rng,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_solid(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
InnerPeltColor::Black(Some(_)) | InnerPeltColor::Gray(Some(_)) => false,
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for InnerPeltColor {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let s = match self {
|
|
||||||
InnerPeltColor::Black(Some(BlackSecondColor::Gray)) => "black-and-gray",
|
|
||||||
InnerPeltColor::Black(Some(BlackSecondColor::White)) => "black-and-white",
|
|
||||||
InnerPeltColor::Black(None) => "black",
|
|
||||||
InnerPeltColor::Gray(Some(GraySecondColor::White)) => "gray-and-white",
|
|
||||||
InnerPeltColor::Gray(None) => "gray",
|
|
||||||
InnerPeltColor::Silver => "silver",
|
|
||||||
InnerPeltColor::BlueGray => "bluegray",
|
|
||||||
InnerPeltColor::Ginger => "ginger",
|
|
||||||
InnerPeltColor::Brown => "brown",
|
|
||||||
InnerPeltColor::ReddishBrown => "reddish-brown",
|
|
||||||
InnerPeltColor::GoldenBrown => "golden-brown",
|
|
||||||
InnerPeltColor::DuskyBrown => "dusky-brown",
|
|
||||||
InnerPeltColor::White => "white",
|
|
||||||
};
|
|
||||||
|
|
||||||
write!(f, "{}", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
enum BlackSecondColor {
|
|
||||||
Gray,
|
|
||||||
White,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
enum GraySecondColor {
|
|
||||||
White,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Pelt {
|
|
||||||
Tortoiseshell(Option<ColorModifier>, Option<Gembder>),
|
|
||||||
Tabby(Option<PeltColor>, Option<Gembder>),
|
|
||||||
Base(PeltColor, Option<Gembder>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pelt {
|
|
||||||
fn gen(rng: &mut impl Rng) -> Pelt {
|
|
||||||
select_weighted(PeltVariant::weights(), rng, 0)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_pelt(rng)
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_variant(&self) -> PeltVariant {
|
fn gen_depends(&self, variant: &str, entries: &[Entry], rng: &mut impl Rng) -> String {
|
||||||
match self {
|
let base = if self.silent.contains(variant) {
|
||||||
Pelt::Tortoiseshell(_, _) => PeltVariant::Tortoiseshell,
|
String::new()
|
||||||
Pelt::Tabby(_, _) => PeltVariant::Tabby,
|
} else {
|
||||||
Pelt::Base(_, _) => PeltVariant::Base,
|
variant.to_string()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Pelt {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Pelt::Tortoiseshell(modifier, gembder) => {
|
|
||||||
if let Some(m) = modifier {
|
|
||||||
write!(f, "{} ", m)?;
|
|
||||||
}
|
|
||||||
write!(f, "tortoiseshell")?;
|
|
||||||
if let Some(g) = gembder {
|
|
||||||
write!(f, " {}", g)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Pelt::Tabby(color, gembder) => {
|
|
||||||
if let Some(c) = color {
|
|
||||||
write!(f, "{} ", c)?;
|
|
||||||
}
|
|
||||||
write!(f, "tabby")?;
|
|
||||||
if let Some(g) = gembder {
|
|
||||||
write!(f, " {}", g)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Pelt::Base(color, Some(gembder)) => write!(f, "{} {}", color, gembder),
|
|
||||||
Pelt::Base(color, None) => write!(f, "{} cat", color),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
||||||
enum PeltVariant {
|
|
||||||
Tortoiseshell,
|
|
||||||
Tabby,
|
|
||||||
Base,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PeltVariant {
|
|
||||||
fn weights() -> Vec<(u8, PeltVariant)> {
|
|
||||||
vec![
|
|
||||||
(1, PeltVariant::Tortoiseshell),
|
|
||||||
(3, PeltVariant::Tabby),
|
|
||||||
(5, PeltVariant::Base),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_pelt(self, rng: &mut impl Rng) -> Pelt {
|
|
||||||
let has_color = rng.gen_bool(0.4);
|
|
||||||
|
|
||||||
match self {
|
|
||||||
PeltVariant::Tortoiseshell => {
|
|
||||||
Pelt::Tortoiseshell(ColorModifier::gen(rng), Gembder::gen_skewed(rng))
|
|
||||||
}
|
|
||||||
PeltVariant::Tabby if has_color => {
|
|
||||||
Pelt::Tabby(Some(PeltColor::gen(rng, false)), Gembder::gen(rng))
|
|
||||||
}
|
|
||||||
PeltVariant::Tabby => Pelt::Tabby(None, Gembder::gen(rng)),
|
|
||||||
PeltVariant::Base => Pelt::Base(PeltColor::gen(rng, true), Gembder::gen(rng)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Gembder {
|
|
||||||
Queen,
|
|
||||||
SheCat,
|
|
||||||
Tom,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gembder {
|
|
||||||
fn weights_skewed() -> Vec<(u8, Gembder)> {
|
|
||||||
vec![
|
|
||||||
(1, Gembder::Tom),
|
|
||||||
(80, Gembder::SheCat),
|
|
||||||
(20, Gembder::Queen),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn weights() -> Vec<(u8, Gembder)> {
|
|
||||||
vec![(5, Gembder::Tom), (3, Gembder::SheCat), (2, Gembder::Queen)]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen(rng: &mut impl Rng) -> Option<Self> {
|
|
||||||
select_weighted(Self::weights(), rng, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen_skewed(rng: &mut impl Rng) -> Option<Self> {
|
|
||||||
select_weighted(Self::weights_skewed(), rng, 101)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Gembder {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let s = match self {
|
|
||||||
Gembder::Queen => "queen",
|
|
||||||
Gembder::SheCat => "she-cat",
|
|
||||||
Gembder::Tom => "tom",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
write!(f, "{}", s)
|
self.depends
|
||||||
|
.iter()
|
||||||
|
.fold(base, |acc, depend| depend.gen(acc, variant, entries, rng))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_next(
|
||||||
|
&self,
|
||||||
|
building: String,
|
||||||
|
variant: &str,
|
||||||
|
weights: Option<&[(u64, String)]>,
|
||||||
|
entries: &[Entry],
|
||||||
|
seen: &mut HashSet<String>,
|
||||||
|
rng: &mut impl Rng,
|
||||||
|
) -> String {
|
||||||
|
let (s, _, _) = self.next.iter().fold(
|
||||||
|
(building, variant.to_string(), seen),
|
||||||
|
|(acc, v, seen), next| {
|
||||||
|
if let Some(variant) = next.gen(&v, &self.variants, weights, rng) {
|
||||||
|
if seen.contains(&variant) {
|
||||||
|
return (acc, variant, seen);
|
||||||
|
}
|
||||||
|
seen.insert(variant.clone());
|
||||||
|
|
||||||
|
let depends = self.gen_depends(&variant, entries, rng);
|
||||||
|
let building = next.join(acc, &depends);
|
||||||
|
|
||||||
|
let building = self.gen_next(building, &variant, weights, entries, seen, rng);
|
||||||
|
|
||||||
|
return (building, variant, seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
(acc, v, seen)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(serde::Deserialize)]
|
||||||
struct EyeColor {
|
struct Next {
|
||||||
eye_color: InnerEyeColor,
|
from: Anyable,
|
||||||
modifier: Option<ColorModifier>,
|
to: Anyable,
|
||||||
|
chance: f64,
|
||||||
|
join: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EyeColor {
|
impl Next {
|
||||||
fn gen(rng: &mut impl Rng) -> EyeColor {
|
fn join(&self, building: String, new: &str) -> String {
|
||||||
EyeColor {
|
building + &self.join + new
|
||||||
eye_color: InnerEyeColor::gen(rng),
|
}
|
||||||
modifier: ColorModifier::gen(rng),
|
|
||||||
|
fn gen(
|
||||||
|
&self,
|
||||||
|
variant: &str,
|
||||||
|
variants: &HashSet<String>,
|
||||||
|
weights: Option<&[(u64, String)]>,
|
||||||
|
rng: &mut impl Rng,
|
||||||
|
) -> Option<String> {
|
||||||
|
if !self.from.contains(variant) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rng.gen_bool(self.chance) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(weights) = weights {
|
||||||
|
let hs: HashSet<_> = weights
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, s)| {
|
||||||
|
if self.to.contains(s) {
|
||||||
|
Some(s.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return Some(select(&hs, weights, rng));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(
|
||||||
|
variants
|
||||||
|
.iter()
|
||||||
|
.filter(|s| self.to.contains(s))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.choose(rng)
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum Anyable {
|
||||||
|
Specified(HashSet<String>),
|
||||||
|
|
||||||
|
Any(Any),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
enum Any {
|
||||||
|
#[serde(rename = "any")]
|
||||||
|
Any,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Anyable {
|
||||||
|
fn contains(&self, variant: &str) -> bool {
|
||||||
|
match self {
|
||||||
|
Anyable::Specified(ref hs) => hs.contains(variant),
|
||||||
|
Anyable::Any(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for EyeColor {
|
#[derive(serde::Deserialize)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
struct Depend {
|
||||||
match &self.modifier {
|
variants: Anyable,
|
||||||
Some(modifier) => write!(f, "{} {}", modifier, self.eye_color),
|
depends: String,
|
||||||
None => write!(f, "{}", self.eye_color),
|
chance: f64,
|
||||||
}
|
order: String,
|
||||||
}
|
weights: Option<Vec<(u64, String)>>,
|
||||||
|
default: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl Depend {
|
||||||
enum InnerEyeColor {
|
fn gen(&self, acc: String, variant: &str, entries: &[Entry], rng: &mut impl Rng) -> String {
|
||||||
Blue,
|
if !self.variants.contains(variant) {
|
||||||
Amber,
|
return acc;
|
||||||
Green,
|
|
||||||
Brown,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerEyeColor {
|
|
||||||
fn weights() -> Vec<(u8, InnerEyeColor)> {
|
|
||||||
vec![
|
|
||||||
(1, InnerEyeColor::Blue),
|
|
||||||
(1, InnerEyeColor::Amber),
|
|
||||||
(1, InnerEyeColor::Green),
|
|
||||||
(3, InnerEyeColor::Brown),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen(rng: &mut impl Rng) -> InnerEyeColor {
|
|
||||||
select_weighted(Self::weights(), rng, 0).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for InnerEyeColor {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let s = match self {
|
|
||||||
InnerEyeColor::Blue => "blue",
|
|
||||||
InnerEyeColor::Amber => "amber",
|
|
||||||
InnerEyeColor::Green => "green",
|
|
||||||
InnerEyeColor::Brown => "brown",
|
|
||||||
};
|
|
||||||
|
|
||||||
write!(f, "{}", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Builder {
|
|
||||||
descriptors: Descriptors,
|
|
||||||
pelt: Pelt,
|
|
||||||
post_descriptors: PostDescriptors,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Builder {
|
|
||||||
fn gen(rng: &mut impl Rng) -> Self {
|
|
||||||
let pelt = Pelt::gen(rng);
|
|
||||||
let variant = pelt.as_variant();
|
|
||||||
|
|
||||||
Builder {
|
|
||||||
descriptors: Descriptors::gen(rng, variant),
|
|
||||||
pelt,
|
|
||||||
post_descriptors: PostDescriptors::gen(rng, variant),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Builder {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
if !self.descriptors.is_empty() {
|
|
||||||
write!(f, "{} ", self.descriptors)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(f, "{}", self.pelt)?;
|
if !rng.gen_bool(self.chance) {
|
||||||
|
if let Some(default) = self.default.as_ref() {
|
||||||
|
if acc.is_empty() {
|
||||||
|
return default.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
if !self.post_descriptors.is_empty() {
|
return self.join(acc, default);
|
||||||
write!(f, " {}", self.post_descriptors)?;
|
}
|
||||||
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
let entry = entries
|
||||||
|
.into_iter()
|
||||||
|
.find(|entry| entry.name == self.depends)
|
||||||
|
.expect(&format!("Missing entry for {}", self.depends));
|
||||||
|
|
||||||
|
let value = entry.gen(self.weights.as_ref().map(Vec::as_slice), entries, rng);
|
||||||
|
|
||||||
|
if acc.is_empty() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.join(acc, &value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join(&self, acc: String, value: &str) -> String {
|
||||||
|
self.order
|
||||||
|
.replace("{base}", &acc)
|
||||||
|
.replace(&format!("{{{}}}", self.depends), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run(rng: &mut impl Rng) -> String {
|
#[derive(serde::Deserialize)]
|
||||||
Builder::gen(rng).to_string()
|
pub(crate) struct Config {
|
||||||
|
root: String,
|
||||||
|
entries: Vec<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub(crate) fn gen(&self, rng: &mut impl Rng) -> String {
|
||||||
|
let root = self
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.find(|entry| entry.name == self.root)
|
||||||
|
.expect(&format!("Invalid config: no entry called {}", self.root));
|
||||||
|
|
||||||
|
root.gen(None, &self.entries, rng)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn config(path: impl AsRef<Path>) -> Result<Config> {
|
||||||
|
let bytes = tokio::fs::read(path).await?;
|
||||||
|
let config: Config = serde_json::from_slice(&bytes)?;
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
60
src/main.rs
60
src/main.rs
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use reqwest::{header::HeaderMap, Client};
|
use reqwest::{header::HeaderMap, Client};
|
||||||
use std::{path::Path, time::Duration};
|
use std::{path::Path, time::Duration};
|
||||||
use tokio::{fs::File, prelude::*, time::interval};
|
use tokio::{fs::File, prelude::*, time::interval};
|
||||||
|
@ -7,16 +7,14 @@ use tokio_compat_02::FutureExt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
mod description;
|
mod description;
|
||||||
|
mod name;
|
||||||
|
|
||||||
const DEFAULT_PREFIX: &str = "fire";
|
|
||||||
const DEFAULT_SUFFIX: &str = "paw";
|
|
||||||
const USER_AGENT: &str = "warriors-cats";
|
const USER_AGENT: &str = "warriors-cats";
|
||||||
const TOKEN_PATH: &str = "/oauth/token";
|
const TOKEN_PATH: &str = "/oauth/token";
|
||||||
const STATUS_URL: &str = "/api/v1/statuses";
|
const STATUS_URL: &str = "/api/v1/statuses";
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
names: Names,
|
|
||||||
timing: Timing,
|
timing: Timing,
|
||||||
mastodon: Mastodon,
|
mastodon: Mastodon,
|
||||||
}
|
}
|
||||||
|
@ -34,17 +32,11 @@ struct Timing {
|
||||||
duration: u32,
|
duration: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
|
||||||
struct Names {
|
|
||||||
prefix: Vec<String>,
|
|
||||||
suffix: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
server: Url,
|
server: Url,
|
||||||
client: Client,
|
client: Client,
|
||||||
prefix: Vec<String>,
|
name: name::Config,
|
||||||
suffix: Vec<String>,
|
description: description::Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
|
@ -128,9 +120,13 @@ impl Config {
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn to_state(self, token_path: impl AsRef<Path>) -> Result<State> {
|
async fn to_state(
|
||||||
|
self,
|
||||||
|
token_path: impl AsRef<Path>,
|
||||||
|
name: name::Config,
|
||||||
|
description: description::Config,
|
||||||
|
) -> Result<State> {
|
||||||
let Config {
|
let Config {
|
||||||
names,
|
|
||||||
timing: _,
|
timing: _,
|
||||||
mastodon,
|
mastodon,
|
||||||
} = self;
|
} = self;
|
||||||
|
@ -160,16 +156,14 @@ impl Config {
|
||||||
Ok(State {
|
Ok(State {
|
||||||
server,
|
server,
|
||||||
client,
|
client,
|
||||||
prefix: names.prefix,
|
name,
|
||||||
suffix: names.suffix,
|
description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
async fn post(&self, rng: &mut impl Rng) -> Result<()> {
|
async fn post(&self, rng: &mut impl Rng) -> Result<()> {
|
||||||
let name = self.generate(rng);
|
|
||||||
let description = description::run(rng);
|
|
||||||
let mut url = self.server.clone();
|
let mut url = self.server.clone();
|
||||||
url.set_path(STATUS_URL);
|
url.set_path(STATUS_URL);
|
||||||
|
|
||||||
|
@ -177,7 +171,7 @@ impl State {
|
||||||
.client
|
.client
|
||||||
.post(url.as_str())
|
.post(url.as_str())
|
||||||
.form(&Status {
|
.form(&Status {
|
||||||
status: format!("{} - {}", name, description),
|
status: self.gen(rng),
|
||||||
visibility: "unlisted",
|
visibility: "unlisted",
|
||||||
})
|
})
|
||||||
.send()
|
.send()
|
||||||
|
@ -191,40 +185,26 @@ impl State {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate(&self, rng: &mut impl Rng) -> String {
|
fn gen(&self, rng: &mut impl Rng) -> String {
|
||||||
let prefix = self
|
let name = self.name.gen(rng);
|
||||||
.prefix
|
let description = self.description.gen(rng);
|
||||||
.choose(rng)
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or(DEFAULT_PREFIX);
|
|
||||||
|
|
||||||
let mut suffix;
|
format!("{} - {}", name, description)
|
||||||
while {
|
|
||||||
suffix = self
|
|
||||||
.suffix
|
|
||||||
.choose(rng)
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or(DEFAULT_SUFFIX);
|
|
||||||
suffix == prefix
|
|
||||||
} {}
|
|
||||||
|
|
||||||
let (first, rest) = prefix.split_at(1);
|
|
||||||
|
|
||||||
first.to_uppercase() + rest + &suffix
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let config = Config::open("Config.toml").await?;
|
let config = Config::open("Config.toml").await?;
|
||||||
|
let name = name::config("Name.json").await?;
|
||||||
|
let description = description::config("Description.json").await?;
|
||||||
|
|
||||||
let duration = Duration::from_secs(60) * config.timing.duration;
|
let duration = Duration::from_secs(60) * config.timing.duration;
|
||||||
let mut ticker = interval(duration);
|
let mut ticker = interval(duration);
|
||||||
|
|
||||||
let state = config.to_state("Token.toml").await?;
|
let state = config.to_state("Token.toml", name, description).await?;
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
ticker.tick().await;
|
ticker.tick().await;
|
||||||
state.post(&mut rng).await?;
|
state.post(&mut rng).await?;
|
||||||
|
|
34
src/name.rs
Normal file
34
src/name.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use rand::{seq::SliceRandom, Rng};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub(crate) struct Config {
|
||||||
|
prefix: Vec<String>,
|
||||||
|
suffix: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub(crate) fn gen(&self, rng: &mut impl Rng) -> String {
|
||||||
|
let prefix = self.prefix.choose(rng).map(|s| s.as_str()).unwrap();
|
||||||
|
|
||||||
|
let suffixes = self
|
||||||
|
.suffix
|
||||||
|
.iter()
|
||||||
|
.filter(|s| *s != prefix)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let suffix = suffixes.choose(rng).unwrap().to_string();
|
||||||
|
|
||||||
|
let (first, rest) = prefix.split_at(1);
|
||||||
|
|
||||||
|
first.to_uppercase() + rest + &suffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn config(path: impl AsRef<Path>) -> Result<Config> {
|
||||||
|
let bytes = tokio::fs::read(path).await?;
|
||||||
|
let config: Config = serde_json::from_slice(&bytes)?;
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
Loading…
Reference in a new issue