char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
75a6060
main
1use serde_json:: Value as JsonValue ; 2 3use crate :: syntax::{ self , Value , ValueKind }; 4use crate ::{ Error , Options , Span , format}; 5 6# [ derive ( Debug , Clone , Copy , PartialEq , Eq )] 7pub enum PathSegment < ' a > { 8Key ( &' a str ), 9Index ( usize ), 10} 11 12impl < ' a > From < &' a str > for PathSegment < ' a > { 13fn from ( value : &' a str ) ->Self { 14Self :: Key ( value) 15} 16} 17 18impl From < usize > for PathSegment < ' _ > { 19fn from ( value : usize ) ->Self { 20Self :: Index ( value) 21} 22} 23 24# [ derive ( Debug , Clone )] 25pub struct Document { 26source : String , 27root : Value , 28options : Options , 29} 30 31impl Document { 32pub fn parse ( source : impl Into < String >) ->Result < Self , Error > { 33Self :: parse_with ( source, & Options :: default ()) 34} 35 36pub fn parse_with ( source : impl Into < String >, options : & Options ) ->Result < Self , Error > { 37let source = source. into (); 38let root = syntax:: parse_with ( & source, options) ?; 39Ok ( Self { source, root, options : * options}) 40} 41 42pub fn parse_object ( source : impl Into < String >) ->Result < Self , Error > { 43Self :: parse_object_with ( source, & Options :: default ()) 44} 45 46pub fn parse_object_with ( source : impl Into < String >, options : & Options ) ->Result < Self , Error > { 47let source = source. into (); 48let root = syntax:: parse_object_with ( & source, options) ?; 49Ok ( Self { source, root, options : * options}) 50} 51 52pub fn options ( & self ) ->Options { 53self . options 54} 55 56pub fn source ( & self ) ->& str { 57& self . source 58} 59 60pub fn into_source ( self ) ->String { 61self . source 62} 63 64pub fn root ( & self ) ->& Value { 65& self . root 66} 67 68pub fn to_value ( & self ) ->Result < JsonValue , Error > { 69self . root . to_value () 70} 71 72/// Missing object keys are inserted; an array index equal to its length appends. 73/// Parent containers must exist. An empty path replaces the root. 74pub fn set ( & mut self , path : & [ PathSegment < ' _ >], value : & JsonValue ) ->Result <(), Error > { 75let edit =if let Some (( last, parent)) = path. split_last () { 76let container =self . locate ( parent) ?; 77if let Some ( existing) =child ( container, * last) ?{ 78self . replacement ( existing, value) ? 79} else { 80let prefix =match ( & container. kind , last) { 81( ValueKind :: Object ( _), PathSegment :: Key ( key)) =>{ 82format! ( "{}: " , format:: key ( key)) 83} 84( ValueKind :: Array ( items), PathSegment :: Index ( index)) 85if * index == items. len () => 86{ 87String :: new () 88} 89 _ =>{ 90return Err ( syntax:: invalid ( 91 container. span . clone (), 92"Array index out of bounds" , 93)); 94} 95}; 96Some ( vec! [ self . insertion ( container, & prefix, value) ?]) 97} 98} else { 99self . replacement ( & self . root , value) ? 100}; 101if let Some ( edits) = edit{ 102self . apply ( edits) ?; 103} 104Ok (()) 105} 106 107/// Comments outside the value survive, including those between a key and its value. 108/// Removing the root is not supported; replace it with an empty object or null instead. 109pub fn remove ( & mut self , path : & [ PathSegment < ' _ >]) ->Result < bool , Error > { 110let Some (( last, parent)) = path. split_last () else { 111return Err ( syntax:: invalid ( 112self . root . span . clone (), 113"Cannot remove the root" , 114)); 115}; 116let container =self . locate ( parent) ?; 117let Some ( value) =child ( container, * last) ?else { 118return Ok ( false ); 119}; 120let mut edits =vec! [( value. span . clone (), String :: new ())]; 121let mut member_start = value. span . start ; 122let mut colon_at =None ; 123let mut key_end =None ; 124let next =match ( & container. kind , last) { 125( ValueKind :: Object ( members), PathSegment :: Key ( key)) =>{ 126let index = members 127. iter () 128. position ( |member| member. key ==* key) 129. unwrap (); 130let member =& members[ index]; 131 member_start = member. key_span . start ; 132 key_end =Some ( member. key_span . end ); 133 edits. push (( member. key_span . clone (), String :: new ())); 134let colon = 135punctuation ( & self . source , member. key_span . end ..value. span . start , b':' ). unwrap (); 136 colon_at =Some ( colon); 137 edits. push (( colon..colon +1 , String :: new ())); 138 members. get ( index +1 ). map ( |member| member. key_span . start ) 139} 140( ValueKind :: Array ( items), PathSegment :: Index ( index)) =>{ 141 items. get ( index +1 ). map ( |value| value. span . start ) 142} 143 _ =>unreachable! (), 144}; 145let comma =punctuation ( 146& self . source , 147 value. span . end ..next. unwrap_or_else ( ||self . container_end ( container)), 148b',' , 149); 150if let Some ( comma) = comma{ 151 edits. push (( comma..comma +1 , String :: new ())); 152} 153// A member alone on its line takes its indentation and line break 154// with it, rather than leaving a whitespace-only line behind. 155// Comments or other content on the line block both extensions. 156let start_of_line =line_start ( & self . source , member_start); 157if start_of_line < member_start &&is_blank ( & self . source , start_of_line..member_start) { 158 edits. push (( start_of_line..member_start, String :: new ())); 159} 160let member_end = comma. map_or ( value. span . end , |comma| comma +1 ); 161let bytes =self . source . as_bytes (); 162let mut end = member_end; 163while end < bytes. len () &&matches! ( bytes[ end], b' ' |b'\t' ) { 164 end +=1 ; 165} 166if self . source [ end..]. starts_with ( "\r\n" ) { 167 end +=2 ; 168} else if self . source [ end..]. starts_with ([ '\r' , '\n' ]) { 169 end +=1 ; 170} else { 171 end = member_end; 172} 173if end > member_end{ 174 edits. push (( member_end..end, String :: new ())); 175} 176// Blank gaps around the colon and comma (e.g. the space in `a: 1`) 177// are not part of any span above and would otherwise survive on the 178// collapsed line. Gaps containing comments are not blank and stay. 179if let ( Some ( colon), Some ( key_end)) =( colon_at, key_end) { 180let mut gaps =vec! [ key_end..colon, colon +1 ..value. span . start ]; 181if let Some ( comma) = comma{ 182 gaps. push ( value. span . end ..comma); 183} 184for gapin gaps{ 185if gap. start < gap. end &&is_blank ( & self . source , gap. clone ()) { 186 edits. push (( gap, String :: new ())); 187} 188} 189} 190self . apply ( edits) ?; 191Ok ( true ) 192} 193 194fn locate ( & self , path : & [ PathSegment < ' _ >]) ->Result < & Value , Error > { 195let mut value =& self . root ; 196for segmentin path{ 197 value =child ( value, * segment) ? 198. ok_or_else ( || syntax:: invalid ( value. span . clone (), "Path does not exist" )) ?; 199} 200Ok ( value) 201} 202 203fn replacement ( 204& self , 205existing : & Value , 206value : & JsonValue , 207) ->Result < Option < Vec <( Span , String )>>, Error > { 208if existing. to_value (). ok (). as_ref () ==Some ( value) { 209return Ok ( None ); 210} 211let implicit =matches! ( existing. kind , ValueKind :: Object ( _)) 212 && !self . source [ existing. span . start ..]. starts_with ( '{' ); 213let indent =line_indent ( & self . source , existing. span . start ); 214let mut text =if let JsonValue :: String ( text) = value 215 &&self . source [ existing. span . start ..]. starts_with ( '\'' ) 216{ 217let json = serde_json:: to_string ( text). unwrap (); 218format! ( "'{}'" , json[ 1 ..json. len () -1 ]. replace ( '\'' , "\\'" )) 219} else { 220formatted ( 221 value, 222 implicit, 223 indent, 224newline ( & self . source ), 225& self . options , 226) ? 227}; 228if existing. span . is_empty () 229 && existing. span . start >0 230 && !self . source [ ..existing. span . start ]. ends_with ([ '\r' , '\n' ]) 231{ 232 text =format! ( "{}{indent}{text}" , newline ( & self . source )); 233} 234let mut edits =vec! [( existing. span . clone (), text)]; 235if implicit 236 &&let Some ( comma) = 237punctuation ( & self . source , existing. span . end ..self . source . len (), b',' ) 238{ 239 edits. push (( comma..comma +1 , String :: new ())); 240} 241Ok ( Some ( edits)) 242} 243 244fn container_end ( & self , container : & Value ) ->usize { 245if matches! ( container. kind , ValueKind :: Object ( _)) 246 && !self . source [ container. span . start ..]. starts_with ( '{' ) 247{ 248self . source . len () 249} else { 250 container. span . end 251} 252} 253 254fn insertion ( 255& self , 256container : & Value , 257prefix : & str , 258value : & JsonValue , 259) ->Result <( Span , String ), Error > { 260let braced =self . source [ container. span . start ..]. starts_with ([ '{' , '[' ]); 261let end =self . container_end ( container) - usize:: from ( braced); 262let base =line_indent ( & self . source , container. span . start ); 263let first =match & container. kind { 264ValueKind :: Object ( members) => members. first (). map ( |member| member. key_span . start ), 265ValueKind :: Array ( items) => items. first (). map ( |value| value. span . start ), 266 _ =>unreachable! (), 267}; 268let indent = first 269. filter ( |start|{ 270self . source [ line_start ( & self . source , * start) ..* start] 271. chars () 272. all ( |ch| ch ==' ' || ch =='\t' ) 273}) 274. map ( |start|line_indent ( & self . source , start). to_owned ()) 275. unwrap_or_else ( ||{ 276if braced{ 277format! ( "{base} " ) 278} else { 279 base. to_owned () 280} 281}); 282let line =line_start ( & self . source , end); 283let at_line_start =self . source [ line..end] 284. chars () 285. all ( |ch| ch ==' ' || ch =='\t' ); 286let at =if braced && at_line_start{ line} else { end}; 287let newline =newline ( & self . source ); 288let mut text =String :: new (); 289if at >0 && !self . source [ ..at]. ends_with ([ '\r' , '\n' ]) { 290 text. push_str ( newline); 291} 292 text. push_str ( & indent); 293 text. push_str ( prefix); 294 text. push_str ( & formatted ( value, false , & indent, newline, & self . options ) ?); 295if braced{ 296 text. push_str ( newline); 297if !at_line_start{ 298 text. push_str ( base); 299} 300} else if self . source . ends_with ([ '\r' , '\n' ]) { 301 text. push_str ( newline); 302} 303Ok (( at..at, text)) 304} 305 306fn apply ( & mut self , mut edits : Vec <( Span , String )>) ->Result <(), Error > { 307let mut source =self . source . clone (); 308 edits. sort_by_key ( |( span, _) | std:: cmp:: Reverse ( span. start )); 309for ( span, text) in edits{ 310 source. replace_range ( span, & text); 311} 312let root = syntax:: parse_with ( & source, & self . options ). map_err ( |error|Error { 313kind : error. kind , 314span : self . root . span . clone (), 315message : format! ( "Edit would produce invalid fJSON: {}" , error. message ), 316previous : None , 317}) ?; 318self . source = source; 319self . root = root; 320Ok (()) 321} 322} 323 324fn child < ' a >( value : &' a Value , segment : PathSegment < ' _ >) ->Result < Option < &' a Value >, Error > { 325match ( & value. kind , segment) { 326( ValueKind :: Object ( members), PathSegment :: Key ( key)) =>Ok ( members 327. iter () 328. find ( |member| member. key == key) 329. map ( |member|& member. value )), 330( ValueKind :: Array ( items), PathSegment :: Index ( index)) =>Ok ( items. get ( index)), 331 _ =>Err ( syntax:: invalid ( 332 value. span . clone (), 333"Path segment does not match its container" , 334)), 335} 336} 337 338fn formatted ( 339value : & JsonValue , 340implicit : bool , 341indent : & str , 342newline : & str , 343options : & Options , 344) ->Result < String , Error > { 345Ok ( format:: ( value, implicit, options) ? 346. split ( '\n' ) 347. collect ::< Vec < _ >>() 348. join ( & format! ( "{newline}{indent}" ))) 349} 350 351fn is_blank ( source : & str , span : Span ) ->bool { 352 source[ span] 353. bytes () 354. all ( |byte|matches! ( byte, b' ' |b'\t' )) 355} 356 357fn line_start ( source : & str , at : usize ) ->usize { 358 source[ ..at] 359. rfind ([ '\r' , '\n' ]) 360. map_or ( 0 , |index| index +1 ) 361} 362 363fn line_indent ( source : & str , at : usize ) ->& str { 364let start =line_start ( source, at); 365let end = source[ start..at] 366. find ( |ch| ch !=' ' && ch !='\t' ) 367. unwrap_or ( at - start); 368& source[ start..start + end] 369} 370 371fn newline ( source : & str ) ->&' static str { 372if source. contains ( "\r\n" ) { 373"\r\n" 374} else if source. contains ( '\r' ) { 375"\r" 376} else { 377"\n" 378} 379} 380 381fn punctuation ( source : & str , span : Span , target : u8 ) ->Option < usize > { 382let bytes = source. as_bytes (); 383let mut at = span. start ; 384while at < span. end { 385if source[ at..]. starts_with ( "//" ) { 386 at += source[ at..span. end ] 387. find ([ '\r' , '\n' ]) 388. unwrap_or ( span. end - at); 389} else if source[ at..]. starts_with ( "/*" ) { 390 at += source[ at..span. end ]. find ( "*/" ). unwrap () +2 ; 391} else if bytes[ at] == target{ 392return Some ( at); 393} else { 394 at +=1 ; 395} 396} 397None 398}