kijetesantakaluotokieni/src/critters.rs
2022-05-28 17:42:26 -07:00

635 lines
23 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::kule::*;
use std::env;
use std::fs;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use voca_rs::*;
// represents inherent structural information about a critter
#[derive(Clone)]
pub struct CritterTemplate {
// 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,
/* 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,
}
// pairs a critter with formatting information for the optional formatting strings.
#[derive(Clone)]
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,
pub format: String,
pub template: CritterTemplate,
}
impl CritterConfig {
// 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>,
format: &Option<String>,
name: &Option<String>,
) -> 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
"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
$8 /__ $9$6
$8 / $1$2\ $9$5
$8 | |$3$4
$8 | |
$8 (III|\|| $9$0"
.to_string(),
},
"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(),
},
"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
$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
$8 ___ $9$6
$8 $1$2) $9$5
$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)-
$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(),
},
"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(" "),
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))
}
}
}
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),
)
}
}
}
if let Some(object) = object {
match count::count_graphemes(&object) {
0 => (),
_ => config.object = object.clone(),
}
}
if let Some(format) = format {
config.format = format.to_string();
}
return Ok(config);
}
// 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)
.replace("$8", &self.format)
.replace("$9", &reset())
.replace("$0", &self.object);
}
// attempts to interpret file as a path, and if this fails, tries appending it to every location in the kijepath environment variable.
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() {
match fs::read_to_string(path.join(name)) {
Ok(f) => {
file = Ok(f);
break;
}
Err(e) => file = Err(e),
}
}
}
let file = file.map_err(|_| (
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),
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)
))?;
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(|_| {
(
"nanpa li nasa lon lipu kije. o pona e ona.".to_string(),
"couldn't parse anchor as number".to_string(),
)
})?;
} else {
return Err((
"ale li weka tan lipu kije. ona li wile e nanpa e sitelen.".to_string(),
"kijefile missing content".to_string(),
));
}
let default_left_eye = lines
.next()
.ok_or((
"lukin nanpa wan li lon ala lipu kije".to_string(),
"left eye missing from kijefile".to_string(),
))?
.to_string();
let default_right_eye = lines
.next()
.ok_or((
"lukin nanpa tu li lon ala lipu kije".to_string(),
"right eye missing from kijefile".to_string(),
))?
.to_string();
let default_left_tongue = lines
.next()
.ok_or((
"uta nanpa wan li lon ala lipu kije".to_string(),
"left tongue missing from kijefile".to_string(),
))?
.to_string();
let default_right_tongue = lines
.next()
.ok_or((
"uta nanpa tu li lon ala lipu kije".to_string(),
"right tongue missing from kijefile".to_string(),
))?
.to_string();
let mut critter = String::new();
lines.for_each(|l| critter.push_str(&format!("{}\n", l).to_string()));
Ok(CritterTemplate {
default_left_eye,
default_right_eye,
default_left_tongue,
default_right_tongue,
anchor,
critter,
})
}
}
fn path() -> Vec<PathBuf> {
match env::var("NASINKIJE") {
Err(_) => Vec::new(),
Ok(s) => s
.split(if cfg!(windows) { ";" } else { ":" })
.map(|s| Path::new(s.trim()).to_path_buf())
.collect(),
}
}
pub fn list_files() -> Result<Vec<String>, (String, String)> {
let mut files = Vec::new();
// must be updated alongside the name match statement in CritterConfig::config_from_string
for builtin in [
"kijetesantakalu",
"kije-lili",
"kuletesantakalu",
"kijetonsitakalu",
"kijetonsitakatu",
"kijetesan",
"kuletesan",
"soweli",
"soweli-a",
"waso",
"kala",
"pipi",
"akesi",
"soko",
"kasi",
"toki-pona",
"mu",
"mani",
"mani-majuna",
"yupekosi",
] {
files.push(builtin.to_string());
}
for i in path() {
let name = i
.to_str()
.unwrap_or("[mi ken ala sitelen UTF-8 e nimi poki.]");
match fs::read_dir(&i) {
Err(e) => match e.kind() {
io::ErrorKind::PermissionDenied => {
return Err((
format!("mi ken ala lukin e poki ni: {}", name),
format!("{}: permission denied", name),
))
}
io::ErrorKind::NotFound => {
return Err((
format!("poki ni li lon ala: {}", name),
format!("{}: directory not found", name),
))
}
_ => {
return Err((
format!("ijo li pakala lon ni: {}\n{:?}", name, e.kind()),
format!("{}: an error occurred: {:?}", name, e.kind()),
))
}
},
Ok(entries) => {
for read in entries {
let filename = read
.map_err(|e| {
(
format!(
"mi ken ala lukin e lipu lon ni: {}\n{}",
name,
e.to_string()
),
format!("can't read file: {}\n{}", name, e.to_string()),
)
})?
.file_name()
.into_string()
.map_err(|_| {
(
format!("mi ken ala sitelen UTF-8 e nimi lipu lon ni: {}", name),
format!("could not display file name in {} as utf-8", name),
)
})?;
files.push(filename);
}
}
}
}
return Ok(files);
}