selene 0.24.0

A blazing-fast modern Lua linter written in Rust
use std::{
    fs,
    path::{Path, PathBuf},
};

use color_eyre::eyre::Context;
use selene_lib::{
    standard_library::{v1, StandardLibrary},
    CheckerConfig,
};

pub fn collect_standard_library<V>(
    config: &CheckerConfig<V>,
    standard_library_name: &str,
    directory: &Path,
    config_directory: &Option<PathBuf>,
) -> color_eyre::Result<Option<StandardLibrary>> {
    let mut standard_library: Option<StandardLibrary> = None;

    for segment in standard_library_name.split('+') {
        let segment_library = match from_name(config, segment, directory, config_directory)? {
            Some(segment_library) => segment_library,
            None => {
                if cfg!(feature = "roblox") && segment == "roblox" {
                    collect_roblox_standard_library(config, directory)?
                } else {
                    color_eyre::eyre::bail!("Could not find the standard library `{segment}`")
                }
            }
        };

        match standard_library.as_mut() {
            Some(standard_library) => {
                standard_library.extend(segment_library);
            }

            None => {
                standard_library = Some(segment_library);
            }
        }
    }

    Ok(standard_library)
}

#[cfg(feature = "roblox")]
fn collect_roblox_standard_library<V>(
    config: &CheckerConfig<V>,
    directory: &Path,
) -> color_eyre::Result<StandardLibrary> {
    crate::roblox::collect_roblox_standard_library(config, directory)
}

#[cfg(not(feature = "roblox"))]
fn collect_roblox_standard_library<V>(
    _config: &CheckerConfig<V>,
    _directory: &Path,
) -> color_eyre::Result<StandardLibrary> {
    unreachable!()
}

fn from_name<V>(
    config: &CheckerConfig<V>,
    standard_library_name: &str,
    directory: &Path,
    config_directory: &Option<PathBuf>,
) -> color_eyre::Result<Option<StandardLibrary>> {
    let mut library: Option<StandardLibrary> = None;

    let mut directories = vec![directory];
    if let Some(directory) = config_directory {
        directories.push(directory);
    };

    for directory in directories {
        let toml_file = directory.join(format!("{standard_library_name}.toml"));
        if toml_file.exists() {
            let content = fs::read_to_string(&toml_file)?;

            let v1_library: v1::StandardLibrary = toml::from_str(&content)
                .with_context(|| format!("failed to read {}", toml_file.display()))?;

            library = Some(v1_library.into());
            break;
        } else {
            let mut yaml_file = directory.join(format!("{standard_library_name}.yml"));
            if !yaml_file.exists() {
                yaml_file = directory.join(format!("{standard_library_name}.yaml"));
            }

            if yaml_file.exists() {
                let content = fs::read_to_string(&yaml_file)
                    .with_context(|| format!("failed to read {}", yaml_file.display()))?;
                library = Some(serde_yaml::from_str(&content)?);
                break;
            }
        }
    }

    match library {
        Some(mut library) => {
            if let Some(base_name) = &library.base {
                if let Some(base) =
                    collect_standard_library(config, base_name, directory, config_directory)
                        .with_context(|| {
                            format!("failed to collect base standard library `{base_name}`")
                        })?
                {
                    library.extend(base);
                }
            }

            Ok(Some(library))
        }
        None => Ok(StandardLibrary::from_name(standard_library_name)),
    }
}