fixed capitalization issue

This commit is contained in:
candle 2025-07-10 21:19:18 -04:00
parent 99e3eba54a
commit 673013f1fb
5 changed files with 78 additions and 57 deletions

View File

@ -2,6 +2,11 @@
`motd` is a command line utility which outputs a (semi) intelligible, randomly generated message to your standard output. `motd` is a command line utility which outputs a (semi) intelligible, randomly generated message to your standard output.
```
motd -t "$HOME is where the {noun} is."
/home/admin is where the dragon is.
```
## Build and Install ## Build and Install
##### Requirements ##### Requirements
`git`, `rust`/`rustup`/`cargo` `git`, `rust`/`rustup`/`cargo`
@ -22,7 +27,7 @@ You may remove the cloned directory.
`motd` is best when customized with your own word pools and templates. `motd` is best when customized with your own word pools and templates.
`motd` will look for a `$HOME/.motdrc`, `$XDG_CONFIG_HOME/.motdrc`, or `$XDG_CONFIG_HOME/motd/motdrc` file and use the settings there as defaults when running `motd` with no flags. If somehow `motd` is configured in a way that would result in an empty word list, it will fallback to the built in defaults. Run `motd -h` to view all options. `motd` will look for a `$HOME/.motdrc`, `$XDG_CONFIG_HOME/.motdrc`, or `$XDG_CONFIG_HOME/motd/motdrc` file and use the settings there as defaults when running `motd` with no flags. If somehow `motd` is configured in a way that would result in an empty word list, it will fallback to the built in defaults.
``` ```
# Example motdrc # Example motdrc
@ -86,7 +91,7 @@ motd -p adverbs > $XDG_CONFIG_HOME/motd/adverbs
motd -p locations > $XDG_CONFIG_HOME/motd/locations motd -p locations > $XDG_CONFIG_HOME/motd/locations
``` ```
Words are separated by line, meaning you can have multiple words treated as a single unit.\ Words are separated by line, meaning you can have multiple words treated as a single unit.\
Re-defining a word will not add an additional entry to the word pool. Re-defining a word will not add an additional entry to the word pool.\
Lines beginning with a `!` are actively removed from their respective word pool.\ Lines beginning with a `!` are actively removed from their respective word pool.\
Lines beginning with a `#` are ignored as comments. Lines beginning with a `#` are ignored as comments.
Lines with the format `$()` can be `bash` expressions which are evaluated at runtime.\ Lines with the format `$()` can be `bash` expressions which are evaluated at runtime.\

View File

@ -1,6 +1,6 @@
use crate::cli::RcArgs; use crate::cli::RcArgs;
use crate::words::WordList; use crate::words::WordList;
use crate::words::{WordType, WORD_TYPES}; use crate::words::{WORD_TYPES, WordType};
use regex::Regex; use regex::Regex;
use std::env; use std::env;
use std::fs; use std::fs;
@ -27,7 +27,8 @@ impl Config {
let mut templates = Vec::new(); let mut templates = Vec::new();
let mut template_exclusions = Vec::new(); let mut template_exclusions = Vec::new();
for word_type in WORD_TYPES { for word_type in WORD_TYPES {
let (word_list, exclusion_list) = load_word_list_with_exclusions(&config_dir.join(word_type.name())); let (word_list, exclusion_list) =
load_word_list_with_exclusions(&config_dir.join(word_type.name()));
if !word_list.is_empty() { if !word_list.is_empty() {
match word_type { match word_type {
WordType::Adjective => words.adjectives = Some(word_list), WordType::Adjective => words.adjectives = Some(word_list),
@ -86,21 +87,21 @@ fn load_word_list_with_exclusions(path: &PathBuf) -> (Vec<String>, Vec<String>)
let mut words = Vec::new(); let mut words = Vec::new();
for line in lines { for line in lines {
if line.starts_with('!') { if let Some(stripped_line) = line.strip_prefix('!') {
// Remove the '!' prefix and add to exclusions exclusions.push(stripped_line.to_string());
exclusions.push(line[1..].to_string());
} else { } else {
words.push(line); words.push(line);
} }
} }
// Filter out any words that are in the exclusion list // Filter out any words that are in the exclusion list
let filtered_words = words.into_iter() let filtered_words = words
.into_iter()
.filter(|word| !exclusions.contains(word)) .filter(|word| !exclusions.contains(word))
.collect(); .collect();
(filtered_words, exclusions) (filtered_words, exclusions)
}, }
Err(_) => (Vec::new(), Vec::new()), Err(_) => (Vec::new(), Vec::new()),
} }
} }
@ -122,7 +123,7 @@ fn expand_env_vars(text: &str) -> String {
Some(v) => v.as_str(), Some(v) => v.as_str(),
None => return "".to_string(), None => return "".to_string(),
}; };
env::var(var_name).unwrap_or_else(|_| format!("${{{}}}", var_name)) env::var(var_name).unwrap_or_else(|_| format!("${{{var_name}}}"))
}) })
.to_string() .to_string()
} }
@ -150,10 +151,10 @@ fn execute_bash_command(command: &str) -> String {
if output.status.success() { if output.status.success() {
String::from_utf8_lossy(&output.stdout).trim().to_string() String::from_utf8_lossy(&output.stdout).trim().to_string()
} else { } else {
format!("$({})", command) format!("$({command})")
} }
} }
Err(_) => format!("$({})", command), Err(_) => format!("$({command})"),
} }
} }
@ -175,7 +176,7 @@ fn is_recursive_command(command: &str) -> bool {
return true; return true;
} }
if let Some(command_name) = token.split('/').last() { if let Some(command_name) = token.split('/').next_back() {
if command_name == program_name { if command_name == program_name {
return true; return true;
} }
@ -191,16 +192,22 @@ pub fn load_rc_file() -> RcArgs {
for path in rc_locations { for path in rc_locations {
if path.exists() { if path.exists() {
match fs::read_to_string(&path) { match fs::read_to_string(&path) {
Ok(content) => { Ok(content) => match toml::from_str::<RcArgs>(&content) {
match toml::from_str::<RcArgs>(&content) {
Ok(rc_args) => return rc_args, Ok(rc_args) => return rc_args,
Err(e) => { Err(e) => {
eprintln!("Warning: Failed to parse RC file '{}': {}", path.display(), e); eprintln!(
} "Warning: Failed to parse RC file '{}': {}",
} path.display(),
e
);
} }
},
Err(e) => { Err(e) => {
eprintln!("Warning: Failed to read RC file '{}': {}", path.display(), e); eprintln!(
"Warning: Failed to read RC file '{}': {}",
path.display(),
e
);
} }
} }
} }
@ -227,7 +234,12 @@ fn get_rc_locations() -> Vec<PathBuf> {
locations.push(PathBuf::from(xdg_config).join("motd").join("motdrc")); locations.push(PathBuf::from(xdg_config).join("motd").join("motdrc"));
} else if let Ok(home) = env::var("HOME") { } else if let Ok(home) = env::var("HOME") {
// Fallback to ~/.config/motd/motdrc if XDG_CONFIG_HOME is not set // Fallback to ~/.config/motd/motdrc if XDG_CONFIG_HOME is not set
locations.push(PathBuf::from(home).join(".config").join("motd").join("motdrc")); locations.push(
PathBuf::from(home)
.join(".config")
.join("motd")
.join("motdrc"),
);
} }
locations locations

View File

@ -27,8 +27,7 @@ impl fmt::Display for GeneratorError {
), ),
GeneratorError::CutOffExceeded { attempts, cut_off } => write!( GeneratorError::CutOffExceeded { attempts, cut_off } => write!(
f, f,
"Could not generate a message within {} characters after {} attempts.", "Could not generate a message within {cut_off} characters after {attempts} attempts.",
cut_off, attempts
), ),
} }
} }
@ -95,7 +94,13 @@ pub fn generate_message(arg_words: WordList, args: Args) -> Result<String, Gener
}; };
( (
word_type, word_type,
build_word_list(defaults, config_words, custom_words, exclusions, args.replace), build_word_list(
defaults,
config_words,
custom_words,
exclusions,
args.replace,
),
) )
}) })
.collect(); .collect();
@ -155,7 +160,7 @@ pub fn generate_message(arg_words: WordList, args: Args) -> Result<String, Gener
if attempts >= MAX_CUTOFF_ATTEMPTS { if attempts >= MAX_CUTOFF_ATTEMPTS {
return Err(GeneratorError::CutOffExceeded { return Err(GeneratorError::CutOffExceeded {
attempts, attempts,
cut_off: cut_off_len cut_off: cut_off_len,
}); });
} }

View File

@ -36,38 +36,37 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
match word_type.to_lowercase().as_str() { match word_type.to_lowercase().as_str() {
"adjectives" | "adjective" => { "adjectives" | "adjective" => {
for adj in adjectives::ADJECTIVES { for adj in adjectives::ADJECTIVES {
println!("{}", adj); println!("{adj}");
} }
} }
"nouns" | "noun" => { "nouns" | "noun" => {
for noun in nouns::NOUNS { for noun in nouns::NOUNS {
println!("{}", noun); println!("{noun}");
} }
} }
"verbs" | "verb" => { "verbs" | "verb" => {
for verb in verbs::VERBS { for verb in verbs::VERBS {
println!("{}", verb); println!("{verb}");
} }
} }
"adverbs" | "adverb" => { "adverbs" | "adverb" => {
for adverb in adverbs::ADVERBS { for adverb in adverbs::ADVERBS {
println!("{}", adverb); println!("{adverb}");
} }
} }
"locations" | "location" => { "locations" | "location" => {
for location in locations::LOCATIONS { for location in locations::LOCATIONS {
println!("{}", location); println!("{location}");
} }
} }
"templates" | "template" => { "templates" | "template" => {
for template in TEMPLATES { for template in TEMPLATES {
println!("{}", template); println!("{template}");
} }
} }
_ => { _ => {
eprintln!( eprintln!(
"Unknown word type: {}. Valid types are: adjectives, nouns, verbs, adverbs, locations, templates", "Unknown word type: {word_type}. Valid types are: adjectives, nouns, verbs, adverbs, locations, templates",
word_type
); );
std::process::exit(1); std::process::exit(1);
} }
@ -104,13 +103,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let message = generate_message(word_list, args)?; let message = generate_message(word_list, args)?;
match output { match output {
Some(output_path) => { Some(output_path) => {
std::fs::write(&output_path, format!("{}\n", message)).unwrap_or_else(|e| { std::fs::write(&output_path, format!("{message}\n")).unwrap_or_else(|e| {
eprintln!("Failed to write to file '{}': {}", output_path, e); eprintln!("Failed to write to file '{output_path}': {e}");
std::process::exit(1); std::process::exit(1);
}); });
} }
None => { None => {
println!("{}", message); println!("{message}");
} }
} }

View File

@ -49,11 +49,11 @@ pub fn capitalize_sentences(text: &str, uppercase: bool, lowercase: bool) -> Str
if capitalize_next && ch.is_alphabetic() { if capitalize_next && ch.is_alphabetic() {
result.push(ch.to_uppercase().next().unwrap_or(ch)); result.push(ch.to_uppercase().next().unwrap_or(ch));
capitalize_next = false; capitalize_next = false;
} else if matches!(ch, ' ') {
result.push(ch);
} else { } else {
result.push(ch); result.push(ch);
if matches!(ch, '.' | '!' | '?') { capitalize_next = matches!(ch, '.' | '!' | '?');
capitalize_next = true;
}
} }
} }
} }