1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Copyright notice and licensing information.
// These lines indicate the copyright of the software and its licensing terms.
// SPDX-License-Identifier: Apache-2.0 OR MIT indicates dual licensing under Apache 2.0 or MIT licenses.
// Copyright © 2024 LibMake. All rights reserved.

use crate::macro_get_field;
use std::{
    env,
    fs::{self, File},
    io,
    path::Path,
};

/// Reads a file and deserializes its content using the specified deserializer function.
///
/// # Arguments
///
/// * `file_path` - The path of the file to read.
/// * `deserializer` - A function that takes a `File` and returns a deserialized value of type `T`.
///
/// # Returns
///
/// Returns a `Result<T, Box<dyn std::error::Error>>` containing the deserialized value, or an error if one occurs.
///
fn read_file<T, F>(
    file_path: &Path,
    deserializer: F,
) -> Result<T, Box<dyn std::error::Error>>
where
    F: FnOnce(File) -> Result<T, Box<dyn std::error::Error>>,
{
    let file = File::open(file_path)?;
    deserializer(file)
}

/// Reads a CSV file at the given file path and returns the value of
/// the given field.
///
/// # Arguments
///
/// * `file_path` - An optional string slice that holds the file path of the CSV file to read.
/// * `field_index` - The index of the field to retrieve.
///
pub fn get_csv_field(
    file_path: Option<&str>,
    field_index: usize,
) -> Option<Vec<String>> {
    file_path.and_then(|file_path| {
        let current_dir = env::current_dir().ok()?;
        let file_path = Path::new(&current_dir).join(file_path);
        let file = File::open(file_path).ok()?;
        let mut rdr = csv::Reader::from_reader(file);

        let mut values = Vec::new();
        for result in rdr.records() {
            let record = result.ok()?;
            if let Some(field_value) = record.get(field_index) {
                values.push(field_value.to_string());
            } else {
                // Field index is out of range
                return None;
            }
        }
        if values.is_empty() {
            None
        } else {
            Some(values)
        }
    })
}

macro_get_field!(get_ini_field, serde_ini::from_read);
macro_get_field!(get_json_field, serde_json::from_reader);
macro_get_field!(get_yaml_field, serde_yml::from_reader);

/// Retrieves a specific field's value from a configuration file.
///
/// # Arguments
///
/// * `file_path` - An optional reference to the path of the configuration file.
/// * `file_format` - The format of the configuration file ("json", "yaml", or "ini").
/// * `field_name` - The name of the field to retrieve the value from.
///
/// # Returns
///
/// Returns a `Result<String, Box<dyn std::error::Error>>` containing the value of the specified field, or an error if one occurs.
///
pub fn get_config_field(
    file_path: Option<&str>,
    file_format: Option<&str>,
    field_name: &str,
) -> Result<String, Box<dyn std::error::Error>> {
    // Ensure file_path is provided
    let file_path = file_path.ok_or("File path is not provided")?;

    // Ensure file_format is provided and is either 'json', 'yaml', or 'ini'
    let format = file_format.ok_or("File format is not provided")?;
    match format {
        "ini" => get_ini_field(Some(file_path), field_name),
        "json" => get_json_field(Some(file_path), field_name),
        "yaml" => get_yaml_field(Some(file_path), field_name),
        _ => Err(format!(
            "Unsupported file format: {}. Supported formats are 'json', 'yaml', and 'ini'.",
            format
        )
        .into()),
    }
}

/// Creates a directory at the specified path.
///
/// # Arguments
///
/// * `path` - The path where the directory should be created.
///
/// # Errors
///
/// Returns an `io::Error` if the directory cannot be created. This could be due to
/// various reasons such as insufficient permissions, the directory already existing,
/// or other I/O-related errors.
///
pub fn create_directory(path: &Path) -> io::Result<()> {
    fs::create_dir(path).or_else(|e| match e.kind() {
        io::ErrorKind::AlreadyExists => Ok(()),
        _ => Err(e),
    })
}