cli: Extract CLI configuration into separate crate
This patch adds the `tree-sitter-config` crate, which manages tree-sitter's configuration file. This new setup allows different components to define their own serializable configuration types, instead of having to create a single monolithic configuration type. But the configuration itself is still stored in a single JSON file. Before, the default location for the configuration file was `~/.tree-sitter/config.json`. This patch updates the default location to follow the XDG Base Directory spec (or other relevant platform- specific spec). So on Linux, for instance, the new default location is `~/.config/tree-sitter/config.json`. We will look in the new location _first_, and fall back on reading from the legacy location if we can't find anything.
This commit is contained in:
parent
ebae034b0c
commit
e841fcfa1b
13 changed files with 220 additions and 91 deletions
114
cli/config/src/lib.rs
Normal file
114
cli/config/src/lib.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
//! Manages tree-sitter's configuration file.
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
/// Holds the contents of tree-sitter's configuration file.
|
||||
///
|
||||
/// The file typically lives at `~/.config/tree-sitter/config.json`, but see the [`load`][] method
|
||||
/// for the full details on where it might be located.
|
||||
///
|
||||
/// This type holds the generic JSON content of the configuration file. Individual tree-sitter
|
||||
/// components will use the [`get`][] method to parse that JSON to extract configuration fields
|
||||
/// that are specific to that component.
|
||||
pub struct Config {
|
||||
location: PathBuf,
|
||||
config: Value,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn find_config_file() -> Result<PathBuf> {
|
||||
if let Ok(path) = env::var("TREE_SITTER_DIR") {
|
||||
let mut path = PathBuf::from(path);
|
||||
path.push("config.json");
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
let xdg_path = Self::xdg_config_file()?;
|
||||
if xdg_path.is_file() {
|
||||
return Ok(xdg_path);
|
||||
}
|
||||
|
||||
let legacy_path = dirs::home_dir()
|
||||
.ok_or(anyhow!("Cannot determine home directory"))?
|
||||
.join(".tree-sitter/config.json");
|
||||
if legacy_path.is_file() {
|
||||
return Ok(legacy_path);
|
||||
}
|
||||
|
||||
Err(anyhow!(concat!(
|
||||
"Cannot find a tree-sitter configuration file.\n",
|
||||
"Please run `tree-sitter init-config` to create one!"
|
||||
)))
|
||||
}
|
||||
|
||||
fn xdg_config_file() -> Result<PathBuf> {
|
||||
let xdg_path = dirs::config_dir()
|
||||
.ok_or(anyhow!("Cannot determine config directory"))?
|
||||
.join("tree-sitter/config.json");
|
||||
Ok(xdg_path)
|
||||
}
|
||||
|
||||
/// Locates and loads in the user's configuration file. We search for the configuration file
|
||||
/// in the following locations, in order:
|
||||
///
|
||||
/// - `$TREE_SITTER_DIR/config.json`, if the `TREE_SITTER_DIR` environment variable is set
|
||||
/// - `tree-sitter/config.json` in your default user configuration directory, as determined
|
||||
/// by [`dirs::config_dir`](https://docs.rs/dirs/*/dirs/fn.config_dir.html)
|
||||
/// - `$HOME/.tree-sitter/config.json` as a fallback from where tree-sitter _used_ to store
|
||||
/// its configuration
|
||||
pub fn load() -> Result<Config> {
|
||||
let location = Self::find_config_file()?;
|
||||
let content = fs::read_to_string(&location)?;
|
||||
let config = serde_json::from_str(&content)?;
|
||||
Ok(Config { location, config })
|
||||
}
|
||||
|
||||
/// Creates an empty initial configuration file. You can then use the [`add`][] method to add
|
||||
/// the component-specific configuration types for any components that want to add content to
|
||||
/// the default file, and then use [`save`][] to write the configuration to disk.
|
||||
///
|
||||
/// (Note that this is typically only done by the `tree-sitter init-config` command.)
|
||||
pub fn initial() -> Result<Config> {
|
||||
let location = Self::xdg_config_file()?;
|
||||
let config = serde_json::json!({});
|
||||
Ok(Config { location, config })
|
||||
}
|
||||
|
||||
/// Saves this configuration to the file that it was originally loaded from.
|
||||
pub fn save(&self) -> Result<()> {
|
||||
let json = serde_json::to_string_pretty(&self.config)?;
|
||||
fs::create_dir_all(self.location.parent().unwrap())?;
|
||||
fs::write(&self.location, json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses a component-specific configuration from the configuration file. The type `C` must
|
||||
/// be [deserializable](https://docs.rs/serde/*/serde/trait.Deserialize.html) from a JSON
|
||||
/// object, and must only include the fields relevant to that component.
|
||||
pub fn get<C>(&self) -> Result<C>
|
||||
where
|
||||
C: for<'de> Deserialize<'de>,
|
||||
{
|
||||
let config = serde_json::from_value(self.config.clone())?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Adds a component-specific configuration to the configuration file. The type `C` must be
|
||||
/// [serializable](https://docs.rs/serde/*/serde/trait.Serialize.html) into a JSON object, and
|
||||
/// must only include the fields relevant to that component.
|
||||
pub fn add<C>(&mut self, config: C) -> Result<()>
|
||||
where
|
||||
C: Serialize,
|
||||
{
|
||||
let mut config = serde_json::to_value(&config)?;
|
||||
self.config
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.append(config.as_object_mut().unwrap());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue