libmake/
utils.rs

1// Copyright notice and licensing information.
2// These lines indicate the copyright of the software and its licensing terms.
3// SPDX-License-Identifier: Apache-2.0 OR MIT indicates dual licensing under Apache 2.0 or MIT licenses.
4// Copyright © 2023-2024 LibMake. All rights reserved.
5
6use crate::macro_get_field;
7use std::{
8    env,
9    fs::{self, File},
10    io,
11    path::Path,
12};
13
14/// Reads a file and deserializes its content using the specified deserializer function.
15///
16/// # Arguments
17///
18/// * `file_path` - The path of the file to read.
19/// * `deserializer` - A function that takes a `File` and returns a deserialized value of type `T`.
20///
21/// # Returns
22///
23/// Returns a `Result<T, Box<dyn std::error::Error>>` containing the deserialized value, or an error if one occurs.
24///
25fn read_file<T, F>(
26    file_path: &Path,
27    deserializer: F,
28) -> Result<T, Box<dyn std::error::Error>>
29where
30    F: FnOnce(File) -> Result<T, Box<dyn std::error::Error>>,
31{
32    let file = File::open(file_path)?;
33    deserializer(file)
34}
35
36/// Reads a CSV file at the given file path and returns the value of
37/// the given field.
38///
39/// # Arguments
40///
41/// * `file_path` - An optional string slice that holds the file path of the CSV file to read.
42/// * `field_index` - The index of the field to retrieve.
43///
44pub fn get_csv_field(
45    file_path: Option<&str>,
46    field_index: usize,
47) -> Option<Vec<String>> {
48    file_path.and_then(|file_path| {
49        let current_dir = env::current_dir().ok()?;
50        let file_path = Path::new(&current_dir).join(file_path);
51        let file = File::open(file_path).ok()?;
52        let mut rdr = csv::Reader::from_reader(file);
53
54        let mut values = Vec::new();
55        for result in rdr.records() {
56            let record = result.ok()?;
57            if let Some(field_value) = record.get(field_index) {
58                values.push(field_value.to_string());
59            } else {
60                // Field index is out of range
61                return None;
62            }
63        }
64        if values.is_empty() {
65            None
66        } else {
67            Some(values)
68        }
69    })
70}
71
72macro_get_field!(get_ini_field, serde_ini::from_read);
73macro_get_field!(get_json_field, serde_json::from_reader);
74macro_get_field!(get_yaml_field, serde_yml::from_reader);
75
76/// Retrieves a specific field's value from a configuration file.
77///
78/// # Arguments
79///
80/// * `file_path` - An optional reference to the path of the configuration file.
81/// * `file_format` - The format of the configuration file ("json", "yaml", or "ini").
82/// * `field_name` - The name of the field to retrieve the value from.
83///
84/// # Returns
85///
86/// Returns a `Result<String, Box<dyn std::error::Error>>` containing the value of the specified field, or an error if one occurs.
87///
88pub fn get_config_field(
89    file_path: Option<&str>,
90    file_format: Option<&str>,
91    field_name: &str,
92) -> Result<String, Box<dyn std::error::Error>> {
93    // Ensure file_path is provided
94    let file_path = file_path.ok_or("File path is not provided")?;
95
96    // Ensure file_format is provided and is either 'json', 'yaml', or 'ini'
97    let format = file_format.ok_or("File format is not provided")?;
98    match format {
99        "ini" => get_ini_field(Some(file_path), field_name),
100        "json" => get_json_field(Some(file_path), field_name),
101        "yaml" => get_yaml_field(Some(file_path), field_name),
102        _ => Err(format!(
103            "Unsupported file format: {}. Supported formats are 'json', 'yaml', and 'ini'.",
104            format
105        )
106        .into()),
107    }
108}
109
110/// Creates a directory at the specified path.
111///
112/// # Arguments
113///
114/// * `path` - The path where the directory should be created.
115///
116/// # Errors
117///
118/// Returns an `io::Error` if the directory cannot be created. This could be due to
119/// various reasons such as insufficient permissions, the directory already existing,
120/// or other I/O-related errors.
121///
122pub fn create_directory(path: &Path) -> io::Result<()> {
123    fs::create_dir(path).or_else(|e| match e.kind() {
124        io::ErrorKind::AlreadyExists => Ok(()),
125        _ => Err(e),
126    })
127}