Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## [0.5.5] - 2025-02-07

### Added

- Support for **multiple fortune files** via `--file <PATH>` (repeatable).
- New configuration key `fortune_files` (list).
When populated, it takes priority over `default_file`.
- Automatic **config migration**: if `fortune_files` is missing or empty,
it is initialized with the value of `default_file`.
- Intelligent **no-repeat** mechanism:
rfortune avoids showing the **same quote twice in a row** from the **same file**.
- Unified JSON-based quote cache shared across multiple fortune files.

### Changed

- `files_fortune` is now deprecated and replaced by `fortune_files`.
Existing configurations remain compatible via `serde(alias)`.

### Removed

- Deprecated single-file `print_random()` function.

---

## [0.5.3] - 2025-11-05

### Fixed
Expand Down
22 changes: 21 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rfortune"
version = "0.5.3"
version = "0.5.5"
edition = "2024"
authors = ["Umpire274 <umpire274@gmail.com>"]
description = "A Rust-based clone of the classic UNIX 'fortune' command"
Expand Down Expand Up @@ -31,4 +31,5 @@ dirs = "6.0.0"
serde = { version = "1.0.228", features = ["derive"] }
serde_yaml = "0.9.33"
atty = "0.2.14"
serde_json = "1.0.145"

83 changes: 74 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,83 @@ rfortune cache clear

---

## 📁 File Format
### Configuration (`rfortune.conf`)

Example:

```yaml
default_file: "/home/user/.local/share/rfortune/rfortune.dat"
print_title: true
use_cache: true

# Optional: load additional quote files
fortune_files:
- "/usr/local/share/rfortune/philosophy.fort"
- "/usr/local/share/rfortune/tech.fort"
```

Priority order:

1. `--file <PATH>` CLI argument(s)
2. `fortune_files` list in config
3. `default_file`

---

### Multiple Sources Configuration

You can load quotes from multiple files and rfortune will automatically
choose one at random:

```bash
rfortune --file my_quotes.fort --file jokes.fort --file tech.fort
```

Or configure them permanently:

```yaml
fortune_files:
- "/path/to/my_quotes.fort"
- "/path/to/jokes.fort"
```

If both are present, **CLI always wins**.

### Smart Quote Repetition Avoidance

rfortune keeps a small cache and automatically avoids repeating
the **same quote twice in a row**, but **only for quotes from the same file**.

This keeps the output natural across multiple sources.

---

### Migration from older versions

If your previous configuration did not contain `fortune_files`,
rfortune will automatically migrate your config by adding it and setting:

```yaml
fortune_files:
- default_file
```

No manual action is required.

---

## 📁 Fortune File Format

Each fortune must be on one or more lines separated by `%`, like so:

```txt
%
The best way to get a good idea is to get a lot of ideas.
%
Do or do not. There is no try.
%
To iterate is human, to recurse divine.
%
```txt
%
The best way to get a good idea is to get a lot of ideas.
%
Do or do not. There is no try.
%
To iterate is human, to recurse divine.
%
```

You may optionally add a title at the top of the file by starting the first line with #. The title will be printed
Expand Down
5 changes: 3 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use clap::ArgAction;
use clap::{Parser, Subcommand};

#[derive(Parser, Debug)]
Expand Down Expand Up @@ -27,8 +28,8 @@ while preserving the spirit of the original UNIX command.",
)]
pub struct Cli {
/// Fortune file to use instead of the default (rfortune.dat)
#[arg(short, long)]
pub file: Option<String>,
#[arg(long = "file", value_name = "FILE", num_args = 1.., action = ArgAction::Append)]
pub files: Option<Vec<String>>,

#[command(subcommand)]
pub command: Option<Commands>,
Expand Down
38 changes: 35 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ pub struct Config {
pub default_file: Option<String>,
pub print_title: Option<bool>,
pub use_cache: Option<bool>,
#[serde(default)]
pub fortune_files: Vec<String>,
}

pub(crate) fn app_dir() -> PathBuf {
let mut base = data_dir().unwrap_or_else(|| {
// fallback molto conservativo
let mut p = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let mut p = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
p.push(".rfortune");
p
});
Expand Down Expand Up @@ -60,6 +62,7 @@ pub fn init_config_file() -> std::io::Result<()> {
default_file: Some(get_default_path().to_string_lossy().to_string()),
print_title: Some(true),
use_cache: Some(true),
fortune_files: vec![],
};
let yaml = serde_yaml::to_string(&cfg).expect("Failed to serialize config");
fs::write(path, yaml)?;
Expand Down Expand Up @@ -92,8 +95,19 @@ In Rust we trust.
/// Carica la configurazione se presente
pub fn load_config() -> Option<Config> {
let path = get_config_path();
let content = fs::read_to_string(path).ok()?;
serde_yaml::from_str(&content).ok()
let content = fs::read_to_string(&path).ok()?;
let mut cfg: Config = serde_yaml::from_str(&content).ok()?;

// ✅ MIGRATION AUTOMATICA
if cfg.fortune_files.is_empty()
&& let Some(df) = &cfg.default_file
{
cfg.fortune_files = vec![df.clone()];
}

let _ = cfg.save(); // ignoriamo eventuali errori non critici

Some(cfg)
}

/// Tenta di migrare una vecchia configurazione `config.yaml` a `rfortune.conf`
Expand Down Expand Up @@ -190,3 +204,21 @@ pub fn run_config_edit(editor_arg: Option<String>) -> std::io::Result<()> {

Ok(())
}

impl Config {
/// Salva la configurazione corrente su disco (YAML).
pub fn save(&self) -> Result<(), String> {
let path = get_config_path();
let parent = path.parent().unwrap();

if !parent.exists() {
fs::create_dir_all(parent)
.map_err(|e| format!("Could not create config directory: {e}"))?;
}

let yaml =
serde_yaml::to_string(&self).map_err(|e| format!("Could not serialize config: {e}"))?;

fs::write(&path, yaml).map_err(|e| format!("Could not write config file: {e}"))
}
}
40 changes: 27 additions & 13 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use clap::Parser;
use rfortune::config::Config;
use rfortune::log::ConsoleLog;
use rfortune::utils::ensure_app_initialized;
use rfortune::{config, loader, utils};
use rfortune::{config, utils};
use std::path::Path;

mod cli;
mod commands;
Expand All @@ -18,6 +20,17 @@ fn main() {
return;
}

// ✅ CARICHIAMO LA CONFIG UNA VOLTA QUI
let config = config::load_config().unwrap_or_else(|| {
ConsoleLog::warn("No configuration file found. Using defaults.");
Config {
default_file: Some(config::get_default_path().to_string_lossy().to_string()),
print_title: Some(true),
use_cache: Some(true),
fortune_files: vec![],
}
});

match cli.command {
// ---------------- CONFIG ----------------
Some(Commands::Config { action }) => match action {
Expand Down Expand Up @@ -45,19 +58,20 @@ fn main() {

// ---------------- DEFAULT: print random fortune ----------------
None => {
let file_path = if let Some(path) = cli.file {
std::path::PathBuf::from(path)
} else {
config::get_default_path()
};
// 1. Risolve la PRIORITÀ delle sorgenti
let sources = utils::resolve_fortune_sources(cli.files.clone(), &config);

if sources.is_empty() {
ConsoleLog::ko("No fortune sources configured or provided.");
return;
}

// 2. Convertiamo in Path
let paths: Vec<&Path> = sources.iter().map(Path::new).collect();

match loader::FortuneFile::from_file(&file_path) {
Ok(fortune_file) => {
if let Err(e) = utils::print_random(&fortune_file, &file_path) {
ConsoleLog::ko(format!("Failed to print fortune: {e}"));
}
}
Err(e) => ConsoleLog::ko(format!("Error loading fortune file: {e}")),
// 3. Stampa citazione casuale da più file
if let Err(e) = utils::print_random_from_files(&paths) {
ConsoleLog::ko(format!("Failed to print fortune: {e}"));
}
}
}
Expand Down
Loading