Repository: thomas-mauran/chess-tui (language: rust)

Issue:
Wrong path for chess-tui config file
<!--
Hi there, sorry `chess-tui` is not working as expected.
Please fill this bug report conscientiously.
A detailed and complete issue is more likely to be processed quickly.
-->

## Description
For now the chess-tui config file in which there is the engine path for the bot is created directly at the root of the home directory. **(~/.chess-tui)**
<!--
A clear and concise description of what the bug is.
-->

## To Reproduce
Run chess-tui with **-e** option for the engine path and the configuration file will be created.
<!--
Try to reduce the issue to a simple code sample exhibiting the problem.
Ideally, fork the project and add a test or an example.
-->

## Expected behavior
The configuration file should be located in a chess-tui directory in the .config directory as follow : **~/.config/chess-tui/config.toml**
Also, it would be better if the configuration file is created whether or not there is an option on launch of chess-tui.
<!--
A clear and concise description of what you expected to happen.
-->

<!--
If applicable, add screenshots, gifs or videos to help explain your problem.
-->

## Environment

<!--
Add a description of the systems where you are observing the issue. For example:
- OS: Linux
- Terminal Emulator: xterm
- Font: Inconsolata (Patched)
- Crate version: 0.7
- Backend: termion
-->

- OS: Arch Linux
- Terminal Emulator: Fish

<!--
Add any other context about the problem here.
If you already looked into the issue, include all the leads you have explored.
-->



Discussion / hints:
Hello ! 

I would like to take this issue if possible ! :smile: 
good idea in the new toml file we could have the following fields: 

```toml
engine_path = <path>
display_mode = <display_mode_from_enum>
```

make sure to change the way those 2 variables are implemented
Will do !


Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 95c9e0e..bb49f3d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -115,6 +115,7 @@ dependencies = [
  "crossterm 0.26.1",
  "dirs",
  "ratatui",
+ "toml",
  "uci",
 ]
 
@@ -473,6 +474,26 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
+[[package]]
+name = "serde"
+version = "1.0.193"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.193"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.39",
+]
+
 [[package]]
 name = "signal-hook"
 version = "0.3.17"
@@ -595,6 +616,15 @@ dependencies = [
  "syn 2.0.39",
 ]
 
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "uci"
 version = "0.1.3"
diff --git a/Cargo.toml b/Cargo.toml
index 9fefa89..b9134e2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ crossterm = "0.26.1"
 dirs = "5.0.1"
 ratatui = "0.26.1"
 uci = "0.1.3"
+toml = "0.5.8"
 
 [features]
 chess-tui = []
diff --git a/src/app.rs b/src/app.rs
index 9a7886c..c670182 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,5 +1,12 @@
-use crate::{board::Board, board::DisplayMode, constants::Pages};
-use std::error;
+use dirs::home_dir;
+use toml::Value;
+
+use crate::{board::Board, constants::DisplayMode, constants::Pages};
+use std::{
+    error,
+    fs::{self, File},
+    io::Write,
+};
 
 /// Application result type.
 pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;
@@ -84,10 +91,32 @@ impl App {
                     DisplayMode::ASCII => DisplayMode::DEFAULT,
                     DisplayMode::DEFAULT => DisplayMode::ASCII,
                 };
+                self.update_config();
             }
             3 => self.show_help_popup = true,
             4 => self.current_page = Pages::Credit,
             _ => {}
         }
     }
+
+    pub fn update_config(&self) {
+        let home_dir = home_dir().expect("Could not get home directory");
+        let config_path = home_dir.join(".config/chess-tui/config.toml");
+        let mut config = match fs::read_to_string(config_path.clone()) {
+            Ok(content) => content
+                .parse::<Value>()
+                .unwrap_or_else(|_| Value::Table(Default::default())),
+            Err(_) => Value::Table(Default::default()),
+        };
+
+        if let Some(table) = config.as_table_mut() {
+            table.insert(
+                "display_mode".to_string(),
+                Value::String(self.board.display_mode.to_string()),
+            );
+        }
+
+        let mut file = File::create(config_path.clone()).unwrap();
+        file.write_all(config.to_string().as_bytes()).unwrap();
+    }
 }
diff --git a/src/board.rs b/src/board.rs
index eba0a57..26a7b1c 100644
--- a/src/board.rs
+++ b/src/board.rs
@@ -1,5 +1,5 @@
 use crate::{
-    constants::{BLACK, UNDEFINED_POSITION, WHITE},
+    constants::{DisplayMode, BLACK, UNDEFINED_POSITION, WHITE},
     pieces::{PieceColor, PieceMove, PieceType},
     utils::{
         col_to_letter, convert_notation_into_position, convert_position_into_notation,
@@ -16,11 +16,6 @@ use ratatui::{
 };
 use uci::Engine;
 
-pub enum DisplayMode {
-    DEFAULT,
-    ASCII,
-}
-
 pub struct Board {
     pub board: [[Option<(PieceType, PieceColor)>; 8]; 8],
     pub cursor_coordinates: [i8; 2],
@@ -627,9 +622,11 @@ impl Board {
 
     fn is_latest_move_promotion(&self) -> bool {
         if let Some(last_move) = self.move_history.last() {
-            if let Some(piece_type_to) = get_piece_type(self.board, [last_move.to_y, last_move.to_x])
+            if let Some(piece_type_to) =
+                get_piece_type(self.board, [last_move.to_y, last_move.to_x])
             {
-                if let Some(piece_color) = get_piece_color(self.board, [last_move.to_y, last_move.to_x])
+                if let Some(piece_color) =
+                    get_piece_color(self.board, [last_move.to_y, last_move.to_x])
                 {
                     let last_row = if piece_color == PieceColor::White {
                         0
diff --git a/src/constants.rs b/src/constants.rs
index b0486b2..298fa83 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -1,3 +1,6 @@
+use core::fmt;
+use std::path::PathBuf;
+
 use ratatui::style::Color;
 
 pub const UNDEFINED_POSITION: i8 = -1;
@@ -13,6 +16,27 @@ pub const TITLE: &str = r#"
  ╚═════╝╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝      ╚═╝    ╚═════╝ ╚═╝
 "#;
 
+pub enum DisplayMode {
+    DEFAULT,
+    ASCII,
+}
+
+impl fmt::Display for DisplayMode {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            DisplayMode::ASCII => write!(f, "ASCII"),
+            DisplayMode::DEFAULT => write!(f, "DEFAULT"),
+        }
+    }
+}
+
+pub fn home_dir() -> Result<PathBuf, &'static str> {
+    match dirs::home_dir() {
+        Some(dir) => Ok(dir),
+        None => Err("Could not get home directory"),
+    }
+}
+
 #[derive(Debug, PartialEq)]
 pub enum Pages {
     Home,
diff --git a/src/main.rs b/src/main.rs
index de6755d..8858ed6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,7 @@
 extern crate chess_tui;
 
 use chess_tui::app::{App, AppResult};
+use chess_tui::constants::{home_dir, DisplayMode};
 use chess_tui::event::{Event, EventHandler};
 use chess_tui::handler::handle_key_events;
 use chess_tui::tui::Tui;
@@ -10,6 +11,8 @@ use ratatui::backend::CrosstermBackend;
 use ratatui::Terminal;
 use std::fs::{self, File};
 use std::io::{self, Write};
+use std::path::Path;
+use toml::Value;
 
 /// Simple program to greet a person
 #[derive(Parser, Debug)]
@@ -24,26 +27,33 @@ fn main() -> AppResult<()> {
     // Parse the cli arguments
     let args = Args::parse();
 
-    let config_path = dirs::home_dir().unwrap().join(".chess-tui");
+    let home_dir = home_dir()?;
+    let folder_path = home_dir.join(".config/chess-tui");
+    let config_path = home_dir.join(".config/chess-tui/config.toml");
 
-    if !args.engine_path.is_empty() {
-        if !config_path.exists() {
-            File::create(&config_path)?;
-        }
-
-        let mut file = File::create(&config_path)?;
-        file.write_all(args.engine_path.as_bytes())?;
-    }
+    // Create the configuration file
+    config_create(&args, &folder_path, &config_path)?;
 
     // Create an application.
-    let mut app = App::default();
+    let mut app: App = App::default();
 
     // We store the chess engine path if there is one
     if let Ok(content) = fs::read_to_string(config_path) {
         if content.trim().is_empty() {
-            app.chess_engine_path = None
+            app.chess_engine_path = None;
         } else {
-            app.chess_engine_path = Some(content)
+            let config = content.parse::<toml::Value>().unwrap();
+            if let Some(engine_path) = config.get("engine_path") {
+                app.chess_engine_path = Some(engine_path.as_str().unwrap().to_string());
+            }
+            // Set the display mode based on the configuration file
+            if let Some(display_mode) = config.get("display_mode") {
+                app.board.display_mode = match display_mode.as_str() {
+                    Some("ASCII") => DisplayMode::ASCII,
+                    Some("DEFAULT") => DisplayMode::DEFAULT,
+                    _ => DisplayMode::DEFAULT,
+                };
+            }
         }
     } else {
         println!("Error reading the file or the file does not exist");
@@ -74,3 +84,84 @@ fn main() -> AppResult<()> {
     tui.exit()?;
     Ok(())
 }
+
+fn config_create(args: &Args, folder_path: &Path, config_path: &Path) -> AppResult<()> {
+    std::fs::create_dir_all(folder_path)?;
+
+    if !config_path.exists() {
+        //write to console
+        File::create(config_path)?;
+    }
+
+    // Attempt to read the configuration file and parse it as a TOML Value.
+    // If we encounter any issues (like the file not being readable or not being valid TOML), we start with a new, empty TOML table instead.
+    let mut config = match fs::read_to_string(config_path) {
+        Ok(content) => content
+            .parse::<Value>()
+            .unwrap_or_else(|_| Value::Table(Default::default())),
+        Err(_) => Value::Table(Default::default()),
+    };
+
+    // We update the configuration with the engine_path and display_mode.
+    // If these keys are already in the configuration, we leave them as they are.
+    // If they're not, we add them with default values.
+    if let Some(table) = config.as_table_mut() {
+        // Only update the engine_path in the configuration if it's not empty
+        if !args.engine_path.is_empty() {
+            table.insert(
+                "engine_path".to_string(),
+                Value::String(args.engine_path.clone()),
+            );
+        } else {
+            table
+                .entry("engine_path".to_string())
+                .or_insert(Value::String("".to_string()));
+        }
+        table
+            .entry("display_mode".to_string())
+            .or_insert(Value::String("DEFAULT".to_string()));
+    }
+
+    let mut file = File::create(config_path)?;
+    file.write_all(config.to_string().as_bytes())?;
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::fs;
+    use toml::Value;
+
+    #[test]
+    fn test_config_create() {
+        let args = Args {
+            engine_path: "test_engine_path".to_string(),
+        };
+
+        let home_dir = home_dir().expect("Failed to get home directory");
+        let folder_path = home_dir.join(".test/chess-tui");
+        let config_path = home_dir.join(".test/chess-tui/config.toml");
+
+        let result = config_create(&args, &folder_path, &config_path);
+
+        assert!(result.is_ok());
+        assert!(config_path.exists());
+
+        let content = fs::read_to_string(config_path).unwrap();
+        let config: Value = content.parse().unwrap();
+        let table = config.as_table().unwrap();
+
+        assert_eq!(
+            table.get("engine_path").unwrap().as_str().unwrap(),
+            "test_engine_path"
+        );
+        assert_eq!(
+            table.get("display_mode").unwrap().as_str().unwrap(),
+            "DEFAULT"
+        );
+        let removed = fs::remove_dir_all(home_dir.join(".test"));
+        assert!(removed.is_ok());
+    }
+}
diff --git a/src/pieces/bishop.rs b/src/pieces/bishop.rs
index 9c7f1a1..051da99 100644
--- a/src/pieces/bishop.rs
+++ b/src/pieces/bishop.rs
@@ -1,5 +1,5 @@
 use super::{Movable, PieceColor, PieceMove, PieceType, Position};
-use crate::board::DisplayMode;
+use crate::constants::DisplayMode;
 use crate::utils::{
     cleaned_positions, get_piece_color, impossible_positions_king_checked, is_cell_color_ally,
     is_piece_opposite_king, is_valid,
diff --git a/src/pieces/king.rs b/src/pieces/king.rs
index 22835f6..b9cbb4f 100644
--- a/src/pieces/king.rs
+++ b/src/pieces/king.rs
@@ -1,5 +1,5 @@
 use super::{Movable, PieceColor, PieceMove, PieceType, Position};
-use crate::board::DisplayMode;
+use crate::constants::DisplayMode;
 use crate::utils::{
     cleaned_positions, did_piece_already_move, get_all_protected_cells, get_piece_type,
     is_cell_color_ally, is_valid, is_vec_in_array,
diff --git a/src/pieces/knight.rs b/src/pieces/knight.rs
index fccd14d..9214915 100644
--- a/src/pieces/knight.rs
+++ b/src/pieces/knight.rs
@@ -1,5 +1,5 @@
 use super::{Movable, PieceColor, PieceMove, PieceType, Position};
-use crate::board::DisplayMode;
+use crate::constants::DisplayMode;
 use crate::utils::{
     cleaned_positions, impossible_positions_king_checked, is_cell_color_ally, is_valid,
 };
diff --git a/src/pieces/mod.rs b/src/pieces/mod.rs
index 10ff849..9b2cc40 100644
--- a/src/pieces/mod.rs
+++ b/src/pieces/mod.rs
@@ -1,5 +1,5 @@
 use self::{bishop::Bishop, king::King, knight::Knight, pawn::Pawn, queen::Queen, rook::Rook};
-use super::board::DisplayMode;
+use super::constants::DisplayMode;
 
 pub mod bishop;
 pub mod king;
diff --git a/src/pieces/pawn.rs b/src/pieces/pawn.rs
index 37b0031..d0d50a0 100644
--- a/src/pieces/pawn.rs
+++ b/src/pieces/pawn.rs
@@ -1,5 +1,5 @@
 use super::{Movable, PieceColor, PieceMove, PieceType, Position};
-use crate::board::DisplayMode;
+use crate::constants::DisplayMode;
 use crate::utils::{
     cleaned_positions, get_latest_move, get_piece_color, impossible_positions_king_checked,
     is_cell_color_ally, is_valid,
diff --git a/src/pieces/queen.rs b/src/pieces/queen.rs
index 19ea8ed..be143eb 100644
--- a/src/pieces/queen.rs
+++ b/src/pieces/queen.rs
@@ -1,6 +1,6 @@
 use super::rook::Rook;
 use super::{Movable, PieceColor, PieceMove, PieceType, Position};
-use crate::board::DisplayMode;
+use crate::constants::DisplayMode;
 use crate::pieces::bishop::Bishop;
 use crate::utils::{cleaned_positions, impossible_positions_king_checked};
 
diff --git a/src/pieces/rook.rs b/src/pieces/rook.rs
index 873e63b..32a2846 100644
--- a/src/pieces/rook.rs
+++ b/src/pieces/rook.rs
@@ -1,5 +1,5 @@
 use super::{Movable, PieceColor, PieceMove, PieceType, Position};
-use crate::board::DisplayMode;
+use crate::constants::DisplayMode;
 use crate::utils::{
     cleaned_positions, get_piece_color, impossible_positions_king_checked, is_cell_color_ally,
     is_piece_opposite_king, is_valid,
diff --git a/src/ui.rs b/src/ui.rs
index bfa0067..9a4802c 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -9,8 +9,7 @@ use ratatui::{
 
 use crate::{
     app::App,
-    board::DisplayMode,
-    constants::{Pages, TITLE},
+    constants::{DisplayMode, Pages, TITLE},
     pieces::PieceColor,
     popups::{
         render_credit_popup, render_end_popup, render_engine_path_error_popup, render_help_popup,
diff --git a/src/utils.rs b/src/utils.rs
index 78945ef..db66ca7 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,6 +1,6 @@
 use crate::{
-    board::{Board, DisplayMode},
-    constants::UNDEFINED_POSITION,
+    board::Board,
+    constants::{DisplayMode, UNDEFINED_POSITION},
     pieces::{PieceColor, PieceMove, PieceType},
 };
 use ratatui::{


Test patch: