cymbal 0.11.0

search for symbols in a codebase
mod raw;

use std::{collections::HashMap, path::Path};

use anyhow::Result;
use clap::ValueEnum;
use enum_assoc::Assoc;
use indexmap::IndexMap;
use serde::Deserialize;
use sqlx::Type as SqlxType;
use tree_sitter::Query as TreeSitterQuery;

use crate::{
  color,
  config::raw::RawConfig,
  ext::{PathExt, TomlExt},
  symbol::Kind,
  template::Template,
  utils::Lazy,
};

include!(concat!(env!("OUT_DIR"), "/", "grammars.rs"));

static DEFAULT_CONFIG: &str = include_str!("../default-config.toml");

pub struct Config {
  languages: HashMap<Language, Lazy<Queries>>,
}

impl Config {
  pub async fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
    let content = path.as_ref().read_bytes().await?;

    RawConfig::from_bytes(&content).map(Config::from)
  }

  pub fn contains_language(&self, language: Language) -> bool {
    self.languages.contains_key(&language)
  }

  pub fn for_language(self, language: Language) -> Self {
    Self {
      languages: self
        .languages
        .into_iter()
        .filter(|(config_lang, _)| config_lang == &language)
        .collect(),
    }
  }

  pub fn queries_for_language(&self, language: Language) -> Option<&Lazy<Queries>> {
    self.languages.get(&language)
  }
}

impl Default for Config {
  fn default() -> Self {
    RawConfig::from_toml_str(DEFAULT_CONFIG).unwrap().into()
  }
}

pub type Queries = IndexMap<Kind, Vec<Query>>;

pub struct Query {
  ts: TreeSitterQuery,
  leading: Option<Template>,
  trailing: Option<Template>,
}

impl Query {
  pub fn tree_sitter_query(&self) -> &TreeSitterQuery {
    &self.ts
  }

  pub fn leading(&self) -> Option<&Template> {
    self.leading.as_ref()
  }

  pub fn trailing(&self) -> Option<&Template> {
    self.trailing.as_ref()
  }
}

#[allow(clippy::unsafe_derive_deserialize)]
#[allow(clippy::upper_case_acronyms)]
#[derive(Assoc, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash, SqlxType, ValueEnum)]
#[serde(rename_all = "lowercase")]
#[value(rename_all = "lowercase")]
#[func(pub fn from_extension(s: &str) -> Option<Self>)]
#[func(pub const fn colored(&self) -> &'static str)]
pub enum Language {
  #[assoc(colored = color!("c   ", blue), from_extension = "c" | "h")]
  C,
  #[assoc(colored = color!("c++ ", blue), from_extension = "cpp" | "cc" | "hh")]
  CPP,
  #[assoc(colored = color!("fish", green), from_extension = "fish")]
  Fish,
  #[assoc(colored = color!("go  ", cyan), from_extension = "go")]
  Go,
  #[assoc(colored = color!("hs  ", magenta), from_extension = "hs")]
  Haskell,
  #[assoc(colored = color!("json", green), from_extension = "json")]
  JSON,
  #[assoc(colored = color!("flow", yellow), from_extension = "ml")]
  OCaml,
  #[assoc(colored = color!("odin", blue), from_extension = "odin")]
  Odin,
  #[assoc(colored = color!("py  ", bright_yellow), from_extension = "py")]
  Python,
  #[assoc(colored = color!("rs  ", yellow), from_extension = "rs")]
  Rust,
  #[assoc(colored = color!("ts  ", blue), from_extension = "js" | "jsx")]
  JavaScript,
  #[assoc(colored = color!("ts  ", blue), from_extension = "ts")]
  TypeScript,
  #[assoc(colored = color!("tsx ", blue), from_extension = "tsx")]
  TSX,
  #[assoc(colored = color!("ivy ", green), from_extension = "iv")]
  Ivy,
  #[assoc(colored = color!("vine", green), from_extension = "vi")]
  Vine,
  #[assoc(colored = color!("kak ", green), from_extension = "kak")]
  Kak,
  #[assoc(colored = color!("nu  ", blue), from_extension = "nu")]
  Nu,
}

languages_impl!(Language);

impl Language {
  pub fn from_file_path<P: AsRef<Path>>(file_path: P) -> Option<Self> {
    Self::from_extension(file_path.as_ref().extension()?.to_str()?)
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn config_default_no_panic() {
    Config::default();
  }
}