11#![ cfg_attr( not( any( test, doctest) ) , doc = include_str!( "../README.md" ) ) ]
22
3- use std:: { env, fs, path:: PathBuf } ;
3+ use std:: {
4+ env, fs,
5+ path:: { Path , PathBuf } ,
6+ } ;
47
5- use anyhow:: { Context , Result } ;
68use etcetera:: BaseStrategy as _;
79use log:: warn;
810use serde:: { Deserialize , Serialize } ;
911use serde_json:: Value ;
12+ use thiserror:: Error ;
13+
14+ pub type ConfigResult < T > = Result < T , ConfigError > ;
15+
16+ #[ derive( Debug , Error ) ]
17+ pub enum ConfigError {
18+ #[ error( "Bad JSON config {0} -- {1}" ) ]
19+ ConfigRead ( String , serde_json:: Error ) ,
20+ #[ error( transparent) ]
21+ HomeDir ( #[ from] etcetera:: HomeDirError ) ,
22+ #[ error( transparent) ]
23+ IO ( IoError ) ,
24+ #[ error( transparent) ]
25+ Serialization ( #[ from] serde_json:: Error ) ,
26+ }
27+
28+ #[ derive( Debug , Error ) ]
29+ pub struct IoError {
30+ pub error : std:: io:: Error ,
31+ pub path : Option < String > ,
32+ }
33+
34+ impl IoError {
35+ fn new ( error : std:: io:: Error , path : Option < & Path > ) -> Self {
36+ Self {
37+ error,
38+ path : path. map ( |p| p. to_string_lossy ( ) . to_string ( ) ) ,
39+ }
40+ }
41+ }
42+
43+ impl std:: fmt:: Display for IoError {
44+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
45+ write ! ( f, "{}" , self . error) ?;
46+ if let Some ( ref path) = self . path {
47+ write ! ( f, " ({path})" ) ?;
48+ }
49+ Ok ( ( ) )
50+ }
51+ }
1052
1153/// Holds the contents of tree-sitter's configuration file.
1254///
@@ -23,7 +65,7 @@ pub struct Config {
2365}
2466
2567impl Config {
26- pub fn find_config_file ( ) -> Result < Option < PathBuf > > {
68+ pub fn find_config_file ( ) -> ConfigResult < Option < PathBuf > > {
2769 if let Ok ( path) = env:: var ( "TREE_SITTER_DIR" ) {
2870 let mut path = PathBuf :: from ( path) ;
2971 path. push ( "config.json" ) ;
@@ -46,8 +88,12 @@ impl Config {
4688 . join ( "tree-sitter" )
4789 . join ( "config.json" ) ;
4890 if legacy_apple_path. is_file ( ) {
49- fs:: create_dir_all ( xdg_path. parent ( ) . unwrap ( ) ) ?;
50- fs:: rename ( & legacy_apple_path, & xdg_path) ?;
91+ let xdg_dir = xdg_path. parent ( ) . unwrap ( ) ;
92+ fs:: create_dir_all ( xdg_dir)
93+ . map_err ( |e| ConfigError :: IO ( IoError :: new ( e, Some ( xdg_dir) ) ) ) ?;
94+ fs:: rename ( & legacy_apple_path, & xdg_path) . map_err ( |e| {
95+ ConfigError :: IO ( IoError :: new ( e, Some ( legacy_apple_path. as_path ( ) ) ) )
96+ } ) ?;
5197 warn ! (
5298 "Your config.json file has been automatically migrated from \" {}\" to \" {}\" " ,
5399 legacy_apple_path. display( ) ,
@@ -67,7 +113,7 @@ impl Config {
67113 Ok ( None )
68114 }
69115
70- fn xdg_config_file ( ) -> Result < PathBuf > {
116+ fn xdg_config_file ( ) -> ConfigResult < PathBuf > {
71117 let xdg_path = etcetera:: choose_base_strategy ( ) ?
72118 . config_dir ( )
73119 . join ( "tree-sitter" )
@@ -84,7 +130,7 @@ impl Config {
84130 /// [`etcetera::choose_base_strategy`](https://docs.rs/etcetera/*/etcetera/#basestrategy)
85131 /// - `$HOME/.tree-sitter/config.json` as a fallback from where tree-sitter _used_ to store
86132 /// its configuration
87- pub fn load ( path : Option < PathBuf > ) -> Result < Self > {
133+ pub fn load ( path : Option < PathBuf > ) -> ConfigResult < Self > {
88134 let location = if let Some ( path) = path {
89135 path
90136 } else if let Some ( path) = Self :: find_config_file ( ) ? {
@@ -94,9 +140,9 @@ impl Config {
94140 } ;
95141
96142 let content = fs:: read_to_string ( & location)
97- . with_context ( || format ! ( "Failed to read {}" , location. to_string_lossy ( ) ) ) ?;
143+ . map_err ( |e| ConfigError :: IO ( IoError :: new ( e , Some ( location. as_path ( ) ) ) ) ) ?;
98144 let config = serde_json:: from_str ( & content)
99- . with_context ( || format ! ( "Bad JSON config {}" , location. to_string_lossy( ) ) ) ?;
145+ . map_err ( |e| ConfigError :: ConfigRead ( location. to_string_lossy ( ) . to_string ( ) , e ) ) ?;
100146 Ok ( Self { location, config } )
101147 }
102148
@@ -106,7 +152,7 @@ impl Config {
106152 /// disk.
107153 ///
108154 /// (Note that this is typically only done by the `tree-sitter init-config` command.)
109- pub fn initial ( ) -> Result < Self > {
155+ pub fn initial ( ) -> ConfigResult < Self > {
110156 let location = if let Ok ( path) = env:: var ( "TREE_SITTER_DIR" ) {
111157 let mut path = PathBuf :: from ( path) ;
112158 path. push ( "config.json" ) ;
@@ -119,17 +165,20 @@ impl Config {
119165 }
120166
121167 /// Saves this configuration to the file that it was originally loaded from.
122- pub fn save ( & self ) -> Result < ( ) > {
168+ pub fn save ( & self ) -> ConfigResult < ( ) > {
123169 let json = serde_json:: to_string_pretty ( & self . config ) ?;
124- fs:: create_dir_all ( self . location . parent ( ) . unwrap ( ) ) ?;
125- fs:: write ( & self . location , json) ?;
170+ let config_dir = self . location . parent ( ) . unwrap ( ) ;
171+ fs:: create_dir_all ( config_dir)
172+ . map_err ( |e| ConfigError :: IO ( IoError :: new ( e, Some ( config_dir) ) ) ) ?;
173+ fs:: write ( & self . location , json)
174+ . map_err ( |e| ConfigError :: IO ( IoError :: new ( e, Some ( self . location . as_path ( ) ) ) ) ) ?;
126175 Ok ( ( ) )
127176 }
128177
129178 /// Parses a component-specific configuration from the configuration file. The type `C` must
130179 /// be [deserializable](https://docs.rs/serde/*/serde/trait.Deserialize.html) from a JSON
131180 /// object, and must only include the fields relevant to that component.
132- pub fn get < C > ( & self ) -> Result < C >
181+ pub fn get < C > ( & self ) -> ConfigResult < C >
133182 where
134183 C : for < ' de > Deserialize < ' de > ,
135184 {
@@ -140,7 +189,7 @@ impl Config {
140189 /// Adds a component-specific configuration to the configuration file. The type `C` must be
141190 /// [serializable](https://docs.rs/serde/*/serde/trait.Serialize.html) into a JSON object, and
142191 /// must only include the fields relevant to that component.
143- pub fn add < C > ( & mut self , config : C ) -> Result < ( ) >
192+ pub fn add < C > ( & mut self , config : C ) -> ConfigResult < ( ) >
144193 where
145194 C : Serialize ,
146195 {
0 commit comments