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
|
|
@ -1,69 +0,0 @@
|
|||
use super::highlight::Theme;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{env, fs, io};
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
#[serde(skip)]
|
||||
pub binary_directory: PathBuf,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "parser-directories")]
|
||||
pub parser_directories: Vec<PathBuf>,
|
||||
|
||||
#[serde(default)]
|
||||
pub theme: Theme,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn get_path(home_dir: &Path) -> PathBuf {
|
||||
env::var("TREE_SITTER_DIR")
|
||||
.map(|p| p.into())
|
||||
.unwrap_or_else(|_| home_dir.join(".tree-sitter"))
|
||||
}
|
||||
|
||||
pub fn load(home_dir: &Path) -> Self {
|
||||
let tree_sitter_dir = Self::get_path(home_dir);
|
||||
let config_path = tree_sitter_dir.join("config.json");
|
||||
let mut result = fs::read_to_string(&config_path)
|
||||
.map_err(drop)
|
||||
.and_then(|json| serde_json::from_str(&json).map_err(drop))
|
||||
.unwrap_or_else(|_| Self::default());
|
||||
result.init(home_dir, &tree_sitter_dir);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn save(&self, home_dir: &Path) -> io::Result<()> {
|
||||
let tree_sitter_dir = Self::get_path(home_dir);
|
||||
let config_path = tree_sitter_dir.join("config.json");
|
||||
let json = serde_json::to_string_pretty(self).expect("Failed to serialize config");
|
||||
fs::write(config_path, json)
|
||||
}
|
||||
|
||||
pub fn new(home_dir: &Path) -> Self {
|
||||
let tree_sitter_dir = Self::get_path(home_dir);
|
||||
let mut result = Self::default();
|
||||
result.init(home_dir, &tree_sitter_dir);
|
||||
result
|
||||
}
|
||||
|
||||
fn init(&mut self, home_dir: &Path, tree_sitter_dir: &Path) {
|
||||
if self.parser_directories.is_empty() {
|
||||
self.parser_directories = vec![
|
||||
home_dir.join("github"),
|
||||
home_dir.join("src"),
|
||||
home_dir.join("source"),
|
||||
]
|
||||
}
|
||||
|
||||
let binary_path = tree_sitter_dir.join("bin");
|
||||
self.binary_directory = binary_path;
|
||||
fs::create_dir_all(&self.binary_directory).unwrap_or_else(|error| {
|
||||
panic!(
|
||||
"Could not find or create parser binary directory {:?}. Error: {}",
|
||||
self.binary_directory, error
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ use anyhow::Result;
|
|||
use lazy_static::lazy_static;
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
|
|
@ -56,6 +57,12 @@ pub struct Theme {
|
|||
pub highlight_names: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct ThemeConfig {
|
||||
#[serde(default)]
|
||||
pub theme: Theme,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn load(path: &path::Path) -> io::Result<Self> {
|
||||
let json = fs::read_to_string(path)?;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
pub mod config;
|
||||
pub mod generate;
|
||||
pub mod highlight;
|
||||
pub mod logger;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use glob::glob;
|
|||
use std::path::Path;
|
||||
use std::{env, fs, u64};
|
||||
use tree_sitter_cli::{
|
||||
config, generate, highlight, logger, parse, query, tags, test, test_highlight, util, wasm,
|
||||
web_ui,
|
||||
generate, highlight, logger, parse, query, tags, test, test_highlight, util, wasm, web_ui,
|
||||
};
|
||||
use tree_sitter_config::Config;
|
||||
use tree_sitter_loader as loader;
|
||||
|
||||
const BUILD_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
|
@ -174,14 +174,15 @@ fn run() -> Result<()> {
|
|||
)
|
||||
.get_matches();
|
||||
|
||||
let home_dir = dirs::home_dir().expect("Failed to read home directory");
|
||||
let current_dir = env::current_dir().unwrap();
|
||||
let config = config::Config::load(&home_dir);
|
||||
let mut loader = loader::Loader::new(config.binary_directory.clone());
|
||||
let config = Config::load()?;
|
||||
let mut loader = loader::Loader::new()?;
|
||||
|
||||
if matches.subcommand_matches("init-config").is_some() {
|
||||
let config = config::Config::new(&home_dir);
|
||||
config.save(&home_dir)?;
|
||||
let mut config = Config::initial()?;
|
||||
config.add(tree_sitter_loader::Config::initial())?;
|
||||
config.add(tree_sitter_cli::highlight::ThemeConfig::default())?;
|
||||
config.save()?;
|
||||
} else if let Some(matches) = matches.subcommand_matches("generate") {
|
||||
let grammar_path = matches.value_of("grammar-path");
|
||||
let report_symbol_name = matches.value_of("report-states-for-rule").or_else(|| {
|
||||
|
|
@ -257,7 +258,8 @@ fn run() -> Result<()> {
|
|||
|
||||
let max_path_length = paths.iter().map(|p| p.chars().count()).max().unwrap();
|
||||
let mut has_error = false;
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
|
||||
let should_track_stats = matches.is_present("stat");
|
||||
let mut stats = parse::Stats::default();
|
||||
|
|
@ -300,7 +302,8 @@ fn run() -> Result<()> {
|
|||
} else if let Some(matches) = matches.subcommand_matches("query") {
|
||||
let ordered_captures = matches.values_of("captures").is_some();
|
||||
let paths = collect_paths(matches.value_of("paths-file"), matches.values_of("paths"))?;
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
let language = loader.select_language(
|
||||
Path::new(&paths[0]),
|
||||
¤t_dir,
|
||||
|
|
@ -321,7 +324,8 @@ fn run() -> Result<()> {
|
|||
should_test,
|
||||
)?;
|
||||
} else if let Some(matches) = matches.subcommand_matches("tags") {
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
let paths = collect_paths(matches.value_of("paths-file"), matches.values_of("paths"))?;
|
||||
tags::generate_tags(
|
||||
&loader,
|
||||
|
|
@ -331,8 +335,10 @@ fn run() -> Result<()> {
|
|||
matches.is_present("time"),
|
||||
)?;
|
||||
} else if let Some(matches) = matches.subcommand_matches("highlight") {
|
||||
loader.configure_highlights(&config.theme.highlight_names);
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
let theme_config: tree_sitter_cli::highlight::ThemeConfig = config.get()?;
|
||||
loader.configure_highlights(&theme_config.theme.highlight_names);
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
|
||||
let time = matches.is_present("time");
|
||||
let quiet = matches.is_present("quiet");
|
||||
|
|
@ -368,10 +374,11 @@ fn run() -> Result<()> {
|
|||
|
||||
if let Some(highlight_config) = language_config.highlight_config(language)? {
|
||||
let source = fs::read(path)?;
|
||||
let theme_config = config.get()?;
|
||||
if html_mode {
|
||||
highlight::html(
|
||||
&loader,
|
||||
&config.theme,
|
||||
&theme_config,
|
||||
&source,
|
||||
highlight_config,
|
||||
quiet,
|
||||
|
|
@ -380,7 +387,7 @@ fn run() -> Result<()> {
|
|||
} else {
|
||||
highlight::ansi(
|
||||
&loader,
|
||||
&config.theme,
|
||||
&theme_config,
|
||||
&source,
|
||||
highlight_config,
|
||||
time,
|
||||
|
|
@ -402,7 +409,8 @@ fn run() -> Result<()> {
|
|||
let open_in_browser = !matches.is_present("quiet");
|
||||
web_ui::serve(¤t_dir, open_in_browser);
|
||||
} else if matches.subcommand_matches("dump-languages").is_some() {
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config)?;
|
||||
for (configuration, language_path) in loader.get_all_language_configurations() {
|
||||
println!(
|
||||
concat!(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use tree_sitter_loader::Loader;
|
|||
include!("./dirs.rs");
|
||||
|
||||
lazy_static! {
|
||||
static ref TEST_LOADER: Loader = Loader::new(SCRATCH_DIR.clone());
|
||||
static ref TEST_LOADER: Loader = Loader::with_parser_lib_path(SCRATCH_DIR.clone());
|
||||
}
|
||||
|
||||
pub fn test_loader<'a>() -> &'a Loader {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue