tree_sitter_config/
lib.rs1#![doc = include_str!("../README.md")]
2
3use std::{env, fs, path::PathBuf};
4
5use anyhow::{Context, Result};
6use etcetera::BaseStrategy as _;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10#[derive(Debug)]
19pub struct Config {
20 pub location: PathBuf,
21 pub config: Value,
22}
23
24impl Config {
25 pub fn find_config_file() -> Result<Option<PathBuf>> {
26 if let Ok(path) = env::var("TREE_SITTER_DIR") {
27 let mut path = PathBuf::from(path);
28 path.push("config.json");
29 if !path.exists() {
30 return Ok(None);
31 }
32 if path.is_file() {
33 return Ok(Some(path));
34 }
35 }
36
37 let xdg_path = Self::xdg_config_file()?;
38 if xdg_path.is_file() {
39 return Ok(Some(xdg_path));
40 }
41
42 if cfg!(target_os = "macos") {
43 let legacy_apple_path = etcetera::base_strategy::Apple::new()?
44 .data_dir() .join("tree-sitter")
46 .join("config.json");
47 if legacy_apple_path.is_file() {
48 fs::create_dir_all(xdg_path.parent().unwrap())?;
49 fs::rename(&legacy_apple_path, &xdg_path)?;
50 println!(
51 "Warning: your config.json file has been automatically migrated from \"{}\" to \"{}\"",
52 legacy_apple_path.display(),
53 xdg_path.display()
54 );
55 return Ok(Some(xdg_path));
56 }
57 }
58
59 let legacy_path = etcetera::home_dir()?
60 .join(".tree-sitter")
61 .join("config.json");
62 if legacy_path.is_file() {
63 return Ok(Some(legacy_path));
64 }
65
66 Ok(None)
67 }
68
69 fn xdg_config_file() -> Result<PathBuf> {
70 let xdg_path = etcetera::choose_base_strategy()?
71 .config_dir()
72 .join("tree-sitter")
73 .join("config.json");
74 Ok(xdg_path)
75 }
76
77 pub fn load(path: Option<PathBuf>) -> Result<Self> {
87 let location = if let Some(path) = path {
88 path
89 } else if let Some(path) = Self::find_config_file()? {
90 path
91 } else {
92 return Self::initial();
93 };
94
95 let content = fs::read_to_string(&location)
96 .with_context(|| format!("Failed to read {}", location.to_string_lossy()))?;
97 let config = serde_json::from_str(&content)
98 .with_context(|| format!("Bad JSON config {}", location.to_string_lossy()))?;
99 Ok(Self { location, config })
100 }
101
102 pub fn initial() -> Result<Self> {
109 let location = if let Ok(path) = env::var("TREE_SITTER_DIR") {
110 let mut path = PathBuf::from(path);
111 path.push("config.json");
112 path
113 } else {
114 Self::xdg_config_file()?
115 };
116 let config = serde_json::json!({});
117 Ok(Self { location, config })
118 }
119
120 pub fn save(&self) -> Result<()> {
122 let json = serde_json::to_string_pretty(&self.config)?;
123 fs::create_dir_all(self.location.parent().unwrap())?;
124 fs::write(&self.location, json)?;
125 Ok(())
126 }
127
128 pub fn get<C>(&self) -> Result<C>
132 where
133 C: for<'de> Deserialize<'de>,
134 {
135 let config = serde_json::from_value(self.config.clone())?;
136 Ok(config)
137 }
138
139 pub fn add<C>(&mut self, config: C) -> Result<()>
143 where
144 C: Serialize,
145 {
146 let mut config = serde_json::to_value(&config)?;
147 self.config
148 .as_object_mut()
149 .unwrap()
150 .append(config.as_object_mut().unwrap());
151 Ok(())
152 }
153}