kijetesantakaluotokieni/src/critters.rs

635 lines
23 KiB
Rust
Raw Normal View History

2022-05-02 23:15:30 +00:00
use super::kule::*;
2022-05-07 04:28:33 +00:00
use std::env;
2022-05-05 21:12:18 +00:00
use std::fs;
use std::io;
2022-05-27 04:36:03 +00:00
use std::path::Path;
use std::path::PathBuf;
2022-04-22 18:56:23 +00:00
use voca_rs::*;
2022-05-03 18:02:19 +00:00
// represents inherent structural information about a critter
#[derive(Clone)]
pub struct CritterTemplate {
2022-05-02 23:15:30 +00:00
// text column where speech line joins speech bubble
pub anchor: usize,
pub default_left_eye: String,
pub default_right_eye: String,
pub default_left_tongue: String,
pub default_right_tongue: String,
2022-05-02 23:15:30 +00:00
/* ascii art critter themself, with special optional formatting strings. all formatters render to one grapheme wide each unless otherwise stated
- $1$2 (left and right eyes)
- $3$4 (left and right tongue)
- $5$6$7 (forward leaning, upwards, and back leaning speech line)
- $8$9 (ansi escape formatting start and formatting stop markers; each renders to zero width)
- $0 object marker. defaults to a single space but can be as many graphemes as you want
*/
pub critter: String,
}
2022-05-03 18:02:19 +00:00
// pairs a critter with formatting information for the optional formatting strings.
#[derive(Clone)]
2022-04-22 18:56:23 +00:00
pub struct CritterConfig {
pub left_eye: String,
pub right_eye: String,
pub left_tongue: String,
pub right_tongue: String,
pub right_line: String,
pub up_line: String,
pub left_line: String,
pub object: String,
2022-05-02 23:15:30 +00:00
pub format: String,
pub template: CritterTemplate,
2022-04-22 18:56:23 +00:00
}
impl CritterConfig {
2022-05-02 23:15:30 +00:00
// tries to create a critter from relevant strings, falling back to sensible defaults where possible
pub fn config_from_string(
eyes: &Option<String>,
tongue: &Option<String>,
line: &Option<String>,
object: &Option<String>,
2022-05-02 23:15:30 +00:00
format: &Option<String>,
name: &Option<String>,
2022-05-26 21:08:48 +00:00
) -> Result<CritterConfig, (String, String)> {
let template: CritterTemplate;
if let Some(name) = name {
template = match name.as_str() {
// when you add a new hardcoded value here, also add it in list_files
2022-05-29 00:42:26 +00:00
"kijetesantakalu" | "default" => CritterTemplate {
anchor: 14,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $6
2022-05-02 23:15:30 +00:00
$8 /__ $9$6
$8 / $1$2\ $9$5
$8 | |$3$4
$8 | |
$8 (III|\|| $9$0"
.to_string(),
},
2022-05-25 21:20:02 +00:00
"kuletesantakalu" => CritterTemplate {
anchor: 14,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: " $6
$8\x1b[38;2;255;34;41m /__ $9$6
$8\x1b[38;2;253;232;26m / $1$2\\ $9$5
$8\x1b[38;2;0;230;78m | |$3$4
$8\x1b[38;2;64;128;255m | |
$8\x1b[38;2;200;41;200m (III|\\|| $9$0"
.to_string(),
},
"kijetonsitakalu" => CritterTemplate {
anchor: 14,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: " $6
$8\x1b[38;2;244;244;48m /__ $9$6
$8\x1b[38;2;232;232;232m / $1$2\\ $9$5
$8\x1b[38;2;209;89;209m | |$3$4
$8\x1b[38;2;78;78;78m (I|\\|| $9$0"
.to_string(),
},
"kijetonsitakatu" => CritterTemplate {
anchor: 14,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: " $6
$8\x1b[38;2;48;164;250m /__ $9$6
$8\x1b[38;2;245;169;184m / $1$2\\ $9$5
$8\x1b[38;2;232;232;232m | |$3$4
$8\x1b[38;2;245;169;184m | |
$8\x1b[38;2;48;164;250m (III|\\|| $9$0"
.to_string(),
},
2022-05-28 19:08:48 +00:00
"kijetesan" => CritterTemplate {
anchor: 17,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $5$6$7
$5 $6 $7
$8 /__ __\
/ $1$2\ /__ /$2$1 \
| |$3$4 / $1$2\$4$3| |
| | | |$3$4 | |
(III|\|| (I|\|| $9$0$8 ||/|III)$9"
.to_string(),
},
"kuletesan" => CritterTemplate {
anchor: 17,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: " $5$6$7
$5 $6 $7
$8 \x1b[38;2;48;164;250m/__ \x1b[38;2;255;34;41m__\\
\x1b[38;2;245;169;184m/ $1$2\\ \x1b[38;2;244;244;48m/__ \x1b[38;2;253;232;26m/$2$1 \\
\x1b[38;2;232;232;232m| |$3$4 \x1b[38;2;232;232;232m/ $1$2\\\x1b[38;2;0;230;78m$4$3| |
\x1b[38;2;245;169;184m| | \x1b[38;2;209;89;209m| |$3$4 \x1b[38;2;64;128;255m| |
\x1b[38;2;48;164;250m(III|\\|| \x1b[38;2;78;78;78m(I|\\|| $9$0$8 \x1b[38;2;200;41;200m||/|III)$9"
.to_string(),
},
"kije-lili" => CritterTemplate {
anchor: 13,
default_left_eye: "o".to_string(),
default_right_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $6
2022-05-02 23:15:30 +00:00
$8 /__ $9$6
$8 / $1$2\ $9$5
$8 | |$3$4
$8 (I|\|| $9$0"
.to_string(),
},
"soweli" => CritterTemplate {
anchor: 10,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $6
2022-05-02 23:15:30 +00:00
$8 ___ $9$6
$8 $1$2) $9$5
2022-05-26 05:58:10 +00:00
$8 ||||$3$9$0"
.to_string(),
},
"soweli-a" => CritterTemplate {
anchor: 10,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $5
$8 ___ ,
$8 $1$2)-
2022-05-26 05:58:10 +00:00
$8 ||||$3`$9$0"
.to_string(),
},
"waso" => CritterTemplate {
anchor: 9,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $6
$8 \ $9$6
$8 $1$2\ $9$6
$8 __\ $9$5
$8 |$3$4
$8 | $9$0"
.to_string(),
},
"kala" => CritterTemplate {
anchor: 14,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $6
$8 _ ___ $9$6
$8 -_- $1$2-_ $9$5
$8 _--_ $4$3_-
$8 --- $9$0"
.to_string(),
},
"pipi" => CritterTemplate {
anchor: 10,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r"$8 $4 $9$6
$8 $1$3$2 $9$5
$8 _|_
$8 _|_
$8 _|_
$8 | $9$0"
.to_string(),
},
"akesi" => CritterTemplate {
anchor: 11,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: "_".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $4 $6
$8 $1$3$2 $9$5
$8 _/_\_
$8 | |
$8 -|-|-
$8 V $9$0"
.to_string(),
},
"soko" => CritterTemplate {
anchor: 13,
default_right_eye: "_".to_string(),
default_left_eye: "_".to_string(),
default_left_tongue: "|".to_string(),
default_right_tongue: "|".to_string(),
critter: r" $6
$8 _--_ $9$6
$8 (_$1$2_) $9$5
$8 $3$4
$8 ||
$8 || $9$0"
.to_string(),
},
"kasi" => CritterTemplate {
anchor: 14,
default_right_eye: "_".to_string(),
default_left_eye: "_".to_string(),
default_left_tongue: "|".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $6
$8 _ $9$6
$8 _ ($2) $9$5
$8 ($1)$4 /
$8 \$3
$8 |
$8 | $9$0"
.to_string(),
},
"toki-pona" => CritterTemplate {
anchor: 14,
default_right_eye: " ".to_string(),
default_left_eye: " ".to_string(),
default_left_tongue: "-".to_string(),
default_right_tongue: "´".to_string(),
critter: r" $6
$8 \ | / $9$6
$8 _---_ $9$6
$8 - $1 $2 - $9$5
$8 - -
$8 - `$3$4 -
$8 `---´ $9$0"
.to_string(),
},
"mu" => CritterTemplate {
anchor: 14,
default_right_eye: " ".to_string(),
default_left_eye: " ".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: ".".to_string(),
critter: r" $9$6
$8 ()_---_() $9$6
$8 - - $9$5
$8 - $1$4$2 -
$8 - $3 -
$8 `---´ $9$0"
.to_string(),
},
"mani" => CritterTemplate {
anchor: 13,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: "_".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $6
$8 (_---_) $9$6
$8 - - $9$6
$8 - $1$4$2 -
$8 - $3 -
$8 `---´ $9$0"
.to_string(),
},
"mani-majuna" => CritterTemplate {
anchor: 9,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $9$7 $8^__^
$9$7 $8($1$2)\_______
$8(__)\ )\/\
$8$3$4 ||----w |
$9$0$8 || ||$9"
.to_string(),
},
2022-05-28 21:25:25 +00:00
"yupekosi" => CritterTemplate {
anchor: 20,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: "-".to_string(),
default_right_tongue: "-".to_string(),
critter: r" $7
$8 ////\|///\ $9$5
$8 ///\\\||///\ $9$7
$8 // _ _ \\ $9$5
$8 _||-($1)--($2)-||_
$8( || () || )
\ \\ ///\\\ // /
-// ||$3$4|| \\-
\\///||\\\//
\\\\||////$9 $0"
.to_string(),
},
name => CritterConfig::template_from_file(&name)?,
}
} else {
template = CritterTemplate {
anchor: 14,
default_right_eye: "o".to_string(),
default_left_eye: "o".to_string(),
default_left_tongue: " ".to_string(),
default_right_tongue: " ".to_string(),
critter: r" $6
$8 /__ $9$6
$8 / $1$2\ $9$5
$8 | |$3$4
$8 | |
$8 (III|\|| $9$0"
.to_string(),
}
}
let mut config = CritterConfig {
left_eye: template.default_left_eye.clone(),
right_eye: template.default_right_eye.clone(),
left_tongue: template.default_left_tongue.clone(),
right_tongue: template.default_right_tongue.clone(),
right_line: String::from("/"),
up_line: String::from("|"),
left_line: String::from("\\"),
object: String::from(" "),
2022-05-02 23:15:30 +00:00
format: reset(), // from kule
template: template,
};
if let Some(eyes) = eyes {
match count::count_graphemes(&eyes) {
0 => {
(config.left_eye, config.right_eye) = (
config.template.default_left_eye.clone(),
config.template.default_right_eye.clone(),
)
}
1 => {
(config.left_eye, config.right_eye) =
(chop::grapheme_at(&eyes, 0), chop::grapheme_at(&eyes, 0))
}
_ => {
(config.left_eye, config.right_eye) =
(chop::grapheme_at(&eyes, 0), chop::grapheme_at(&eyes, 1))
}
}
2022-04-22 18:56:23 +00:00
}
if let Some(tongue) = tongue {
match count::count_graphemes(&tongue) {
0 => {
(config.left_tongue, config.right_tongue) = (
config.template.default_left_tongue.clone(),
config.template.default_right_tongue.clone(),
)
}
1 => {
(config.left_tongue, config.right_tongue) = (
chop::grapheme_at(&tongue, 0),
config.template.default_right_tongue.clone(),
)
}
_ => {
(config.left_tongue, config.right_tongue) =
(chop::grapheme_at(&tongue, 0), chop::grapheme_at(&tongue, 1))
}
}
} else if let Some(line) = line {
match count::count_graphemes(&line) {
0 => (),
1 => {
(config.right_line, config.up_line, config.left_line) = (
chop::grapheme_at(&line, 0),
chop::grapheme_at(&line, 0),
chop::grapheme_at(&line, 0),
)
}
2 => {
(config.right_line, config.up_line) =
(chop::grapheme_at(&line, 0), chop::grapheme_at(&line, 1))
}
_ => {
(config.right_line, config.up_line, config.left_line) = (
chop::grapheme_at(&line, 0),
chop::grapheme_at(&line, 1),
chop::grapheme_at(&line, 2),
)
}
}
2022-04-22 18:56:23 +00:00
}
if let Some(object) = object {
match count::count_graphemes(&object) {
0 => (),
_ => config.object = object.clone(),
}
}
2022-05-02 23:15:30 +00:00
if let Some(format) = format {
config.format = format.to_string();
}
2022-04-22 18:56:23 +00:00
2022-05-05 21:12:18 +00:00
return Ok(config);
}
2022-04-22 18:56:23 +00:00
2022-05-02 23:15:30 +00:00
// gives a fully formatted version of the critter
pub fn format_critter(&self) -> String {
return self
.template
.critter
.replace("$1", &self.left_eye)
.replace("$2", &self.right_eye)
.replace("$3", &self.left_tongue)
.replace("$4", &self.right_tongue)
.replace("$5", &self.right_line)
.replace("$6", &self.up_line)
.replace("$7", &self.left_line)
2022-05-02 23:15:30 +00:00
.replace("$8", &self.format)
.replace("$9", &reset())
.replace("$0", &self.object);
}
2022-05-07 04:28:33 +00:00
// attempts to interpret file as a path, and if this fails, tries appending it to every location in the kijepath environment variable.
2022-05-26 21:08:48 +00:00
fn template_from_file(name: &str) -> Result<CritterTemplate, (String, String)> {
let mut file = fs::read_to_string(name);
let paths = path();
if file.is_err() && !paths.is_empty() {
for path in path() {
2022-05-27 04:36:03 +00:00
match fs::read_to_string(path.join(name)) {
Ok(f) => {
file = Ok(f);
break;
}
Err(e) => file = Err(e),
}
}
}
2022-05-26 21:08:48 +00:00
let file = file.map_err(|_| (
2022-05-27 04:36:03 +00:00
format!("mi ken ala lukin e nimi kije {}.\n - sina wile lukin e kije ale la o `kijetesantakaluotokieni --seme`\n - sina ken kepeken nimi suli, sama ni: /home/mi/kije\n - nimi pi poki lipu li lon nimi $NASINKIJE la ilo kijetesantakaluotokieni li\n alasa lon poki ni. o kipisi e nimi poki kepeken sitelen \":\" (ilo Windows la \";\").", name),
2022-05-26 21:08:48 +00:00
format!("couldn't find/read kijefile {}. check available critters with -l or --seme, try again with a full file path, or add colon-separated directories to $NASINKIJE", name)
))?;
2022-05-07 04:28:33 +00:00
let mut lines = file.lines().skip_while(|l| l.starts_with('#')); // skips comments
let anchor: usize;
if let Some(anchor_line) = lines.next() {
anchor = anchor_line.trim().parse().map_err(|_| {
2022-05-26 21:08:48 +00:00
(
"nanpa li nasa lon lipu kije. o pona e ona.".to_string(),
"couldn't parse anchor as number".to_string(),
)
})?;
2022-05-07 04:28:33 +00:00
} else {
2022-05-26 21:08:48 +00:00
return Err((
"ale li weka tan lipu kije. ona li wile e nanpa e sitelen.".to_string(),
"kijefile missing content".to_string(),
));
2022-05-07 04:28:33 +00:00
}
2022-05-25 21:20:02 +00:00
let default_left_eye = lines
.next()
2022-05-26 21:08:48 +00:00
.ok_or((
"lukin nanpa wan li lon ala lipu kije".to_string(),
"left eye missing from kijefile".to_string(),
))?
2022-05-25 21:20:02 +00:00
.to_string();
let default_right_eye = lines
.next()
2022-05-26 21:08:48 +00:00
.ok_or((
"lukin nanpa tu li lon ala lipu kije".to_string(),
"right eye missing from kijefile".to_string(),
))?
2022-05-25 21:20:02 +00:00
.to_string();
let default_left_tongue = lines
.next()
2022-05-26 21:08:48 +00:00
.ok_or((
"uta nanpa wan li lon ala lipu kije".to_string(),
"left tongue missing from kijefile".to_string(),
))?
2022-05-25 21:20:02 +00:00
.to_string();
let default_right_tongue = lines
.next()
2022-05-26 21:08:48 +00:00
.ok_or((
"uta nanpa tu li lon ala lipu kije".to_string(),
"right tongue missing from kijefile".to_string(),
))?
2022-05-25 21:20:02 +00:00
.to_string();
2022-05-07 04:28:33 +00:00
let mut critter = String::new();
2022-05-25 21:20:02 +00:00
lines.for_each(|l| critter.push_str(&format!("{}\n", l).to_string()));
2022-05-07 04:28:33 +00:00
Ok(CritterTemplate {
default_left_eye,
default_right_eye,
default_left_tongue,
default_right_tongue,
anchor,
critter,
})
2022-05-07 04:28:33 +00:00
}
2022-04-22 18:56:23 +00:00
}
2022-05-05 21:12:18 +00:00
2022-05-27 04:36:03 +00:00
fn path() -> Vec<PathBuf> {
2022-05-07 04:28:33 +00:00
match env::var("NASINKIJE") {
Err(_) => Vec::new(),
2022-05-27 04:36:03 +00:00
Ok(s) => s
.split(if cfg!(windows) { ";" } else { ":" })
.map(|s| Path::new(s.trim()).to_path_buf())
.collect(),
2022-05-05 21:12:18 +00:00
}
2022-05-07 04:28:33 +00:00
}
2022-05-26 21:08:48 +00:00
pub fn list_files() -> Result<Vec<String>, (String, String)> {
2022-05-07 04:28:33 +00:00
let mut files = Vec::new();
// must be updated alongside the name match statement in CritterConfig::config_from_string
for builtin in [
"kijetesantakalu",
2022-05-28 19:08:48 +00:00
"kije-lili",
2022-05-25 21:20:02 +00:00
"kuletesantakalu",
"kijetonsitakalu",
"kijetonsitakatu",
2022-05-28 19:08:48 +00:00
"kijetesan",
"kuletesan",
"soweli",
"soweli-a",
"waso",
"kala",
"pipi",
"akesi",
"soko",
"kasi",
"toki-pona",
"mu",
"mani",
"mani-majuna",
2022-05-28 21:26:54 +00:00
"yupekosi",
] {
files.push(builtin.to_string());
}
2022-05-07 04:28:33 +00:00
for i in path() {
2022-05-27 04:36:03 +00:00
let name = i
.to_str()
.unwrap_or("[mi ken ala sitelen UTF-8 e nimi poki.]");
2022-05-07 04:28:33 +00:00
match fs::read_dir(&i) {
Err(e) => match e.kind() {
io::ErrorKind::PermissionDenied => {
2022-05-26 21:08:48 +00:00
return Err((
2022-05-27 04:36:03 +00:00
format!("mi ken ala lukin e poki ni: {}", name),
format!("{}: permission denied", name),
2022-05-26 21:08:48 +00:00
))
2022-05-07 04:28:33 +00:00
}
io::ErrorKind::NotFound => {
2022-05-26 21:08:48 +00:00
return Err((
2022-05-27 04:36:03 +00:00
format!("poki ni li lon ala: {}", name),
format!("{}: directory not found", name),
2022-05-07 04:28:33 +00:00
))
}
_ => {
2022-05-26 21:08:48 +00:00
return Err((
2022-05-27 04:36:03 +00:00
format!("ijo li pakala lon ni: {}\n{:?}", name, e.kind()),
format!("{}: an error occurred: {:?}", name, e.kind()),
2022-05-26 21:08:48 +00:00
))
}
2022-05-07 04:28:33 +00:00
},
Ok(entries) => {
for read in entries {
let filename = read
2022-05-26 21:08:48 +00:00
.map_err(|e| {
(
2022-05-27 04:36:03 +00:00
format!(
"mi ken ala lukin e lipu lon ni: {}\n{}",
name,
e.to_string()
),
format!("can't read file: {}\n{}", name, e.to_string()),
2022-05-26 21:08:48 +00:00
)
})?
2022-05-07 04:28:33 +00:00
.file_name()
.into_string()
2022-05-26 21:08:48 +00:00
.map_err(|_| {
(
2022-05-27 04:36:03 +00:00
format!("mi ken ala sitelen UTF-8 e nimi lipu lon ni: {}", name),
format!("could not display file name in {} as utf-8", name),
2022-05-26 21:08:48 +00:00
)
})?;
2022-05-07 04:28:33 +00:00
files.push(filename);
}
}
}
}
return Ok(files);
2022-05-05 21:12:18 +00:00
}