fixed capitalization issue
This commit is contained in:
parent
99e3eba54a
commit
673013f1fb
@ -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.\
|
||||||
|
|||||||
@ -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),
|
||||||
@ -80,27 +81,27 @@ fn load_word_list_with_exclusions(path: &PathBuf) -> (Vec<String>, Vec<String>)
|
|||||||
.filter(|line| !line.is_empty() && !line.starts_with('#'))
|
.filter(|line| !line.is_empty() && !line.starts_with('#'))
|
||||||
.map(|line| expand_line(&line))
|
.map(|line| expand_line(&line))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Separate exclusions (lines starting with '!') from regular words
|
// Separate exclusions (lines starting with '!') from regular words
|
||||||
let mut exclusions = Vec::new();
|
let mut exclusions = Vec::new();
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -187,48 +188,59 @@ fn is_recursive_command(command: &str) -> bool {
|
|||||||
|
|
||||||
pub fn load_rc_file() -> RcArgs {
|
pub fn load_rc_file() -> RcArgs {
|
||||||
let rc_locations = get_rc_locations();
|
let rc_locations = get_rc_locations();
|
||||||
|
|
||||||
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!(
|
||||||
eprintln!("Warning: Failed to parse RC file '{}': {}", path.display(), e);
|
"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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RcArgs::default()
|
RcArgs::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rc_locations() -> Vec<PathBuf> {
|
fn get_rc_locations() -> Vec<PathBuf> {
|
||||||
let mut locations = Vec::new();
|
let mut locations = Vec::new();
|
||||||
|
|
||||||
// ~/.motdrc
|
// ~/.motdrc
|
||||||
if let Ok(home) = env::var("HOME") {
|
if let Ok(home) = env::var("HOME") {
|
||||||
locations.push(PathBuf::from(home).join(".motdrc"));
|
locations.push(PathBuf::from(home).join(".motdrc"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// $XDG_CONFIG_HOME/.motdrc
|
// $XDG_CONFIG_HOME/.motdrc
|
||||||
if let Ok(xdg_config) = env::var("XDG_CONFIG_HOME") {
|
if let Ok(xdg_config) = env::var("XDG_CONFIG_HOME") {
|
||||||
locations.push(PathBuf::from(xdg_config).join(".motdrc"));
|
locations.push(PathBuf::from(xdg_config).join(".motdrc"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// $XDG_CONFIG_HOME/motd/motdrc
|
// $XDG_CONFIG_HOME/motd/motdrc
|
||||||
if let Ok(xdg_config) = env::var("XDG_CONFIG_HOME") {
|
if let Ok(xdg_config) = env::var("XDG_CONFIG_HOME") {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,7 +55,7 @@ pub fn generate_message(arg_words: WordList, args: Args) -> Result<String, Gener
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Build word lists once
|
// Build word lists once
|
||||||
let word_lists: HashMap<WordType, Vec<String>> = WORD_TYPES
|
let word_lists: HashMap<WordType, Vec<String>> = WORD_TYPES
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&word_type| *word_type != WordType::Template)
|
.filter(|&word_type| *word_type != WordType::Template)
|
||||||
.map(|&word_type| {
|
.map(|&word_type| {
|
||||||
@ -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();
|
||||||
@ -150,15 +155,15 @@ pub fn generate_message(arg_words: WordList, args: Args) -> Result<String, Gener
|
|||||||
if final_message.len() <= cut_off_len as usize {
|
if final_message.len() <= cut_off_len as usize {
|
||||||
return Ok(final_message);
|
return Ok(final_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've exceeded max attempts, return an error
|
// If we've exceeded max attempts, return an error
|
||||||
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue the loop to try again
|
// Continue the loop to try again
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
@ -203,12 +208,12 @@ fn build_word_list(
|
|||||||
fn build_template_list(config_templates: &[String], exclusions: &[String]) -> Vec<String> {
|
fn build_template_list(config_templates: &[String], exclusions: &[String]) -> Vec<String> {
|
||||||
let mut templates: Vec<String> = TEMPLATES.iter().map(|s| s.to_string()).collect();
|
let mut templates: Vec<String> = TEMPLATES.iter().map(|s| s.to_string()).collect();
|
||||||
templates.extend(config_templates.iter().cloned());
|
templates.extend(config_templates.iter().cloned());
|
||||||
|
|
||||||
// Apply exclusions if they exist
|
// Apply exclusions if they exist
|
||||||
if !exclusions.is_empty() {
|
if !exclusions.is_empty() {
|
||||||
templates.retain(|template| !exclusions.contains(template));
|
templates.retain(|template| !exclusions.contains(template));
|
||||||
}
|
}
|
||||||
|
|
||||||
templates
|
templates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
src/main.rs
21
src/main.rs
@ -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}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user