char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
1c8ffed
main
1use std:: path::{ Path , PathBuf }; 2use std:: rc:: Rc ; 3use std:: sync:: Arc ; 4 5use avril_core:: ModuleLoader ; 6use avril_host:: environment:: Environment ; 7use avril_runtime:: authority:: SourceAuthority ; 8use avril_runtime:: caps::{ Capabilities , Category , Initial }; 9use avril_runtime:: modules:: ModuleGraph ; 10use avril_runtime:: registry:: Options ; 11 12use crate :: cli::{ Command , ParseError , command, default_cache, usage}; 13 14pub fn working_dir () ->Result < PathBuf , ParseError > { 15Ok ( std:: env:: current_dir () ?. canonicalize () ?) 16} 17 18fn value ( 19args : & mut impl Iterator < Item =String >, 20missing : ParseError , 21) ->Result < String , ParseError > { 22match args. next () { 23Some ( value) if !value. is_empty () && !value. starts_with ( '-' ) =>Ok ( value), 24 _ =>Err ( missing), 25} 26} 27 28pub fn flag_value ( arg : & str ) ->Result <( & str , & str ), ParseError > { 29let ( flag, value) = arg 30. split_once ( '=' ) 31. ok_or_else ( ||ParseError :: ExpectedEquals { arg : arg. to_owned () }) ?; 32if value. is_empty () { 33return Err ( ParseError :: EmptyValue { flag : flag. to_owned () }); 34} 35Ok (( flag, value)) 36} 37 38pub fn reject ( arg : & str , bare : & [ & str ]) ->ParseError { 39let flag = arg. split_once ( '=' ). map_or ( arg, |( flag, _) | flag); 40if bare. contains ( & flag) { 41ParseError :: UnexpectedValue { 42flag : flag. trim_start_matches ( '-' ). to_owned (), 43} 44} else { 45ParseError :: ExpectedEquals { arg : arg. to_owned () } 46} 47} 48 49pub struct Common { 50pub ( crate ) initial : Initial , 51pub ( crate ) offline : bool , 52pub ( crate ) frozen : bool , 53pub ( crate ) automatic_types : bool , 54pub ( crate ) jsr_registry : url:: Url , 55pub ( crate ) npm_registry : url:: Url , 56pub ( crate ) cache : PathBuf , 57pub ( crate ) lock : Option < PathBuf >, 58pub ( crate ) vendor : Option < PathBuf >, 59pub ( crate ) includes : Vec <( String , Option < String >)>, 60pub ( crate ) include_from : Option < String >, 61pub ( crate ) env_files : Vec < PathBuf >, 62pub ( crate ) cwd : PathBuf , 63} 64 65impl Common { 66pub fn new ( argv : & [ String ], cwd : & Path ) ->Self { 67let zero = argv 68. iter () 69. take_while ( |arg| arg. as_str () !="--" ) 70. any ( |arg| arg =="-0" ); 71Self { 72initial : Initial :: new ( zero), 73offline : false , 74frozen : false , 75automatic_types : true , 76jsr_registry : url:: Url :: parse ( "https://jsr.io/" ). unwrap (), 77npm_registry : url:: Url :: parse ( "https://registry.npmjs.org/" ). unwrap (), 78cache : default_cache ( cwd), 79lock : None , 80vendor : None , 81includes : Vec :: new (), 82include_from : None , 83env_files : Vec :: new (), 84cwd : cwd. to_owned (), 85} 86} 87 88pub fn offer ( 89& mut self , 90arg : & str , 91args : & mut impl Iterator < Item =String >, 92) ->Result < bool , ParseError > { 93match arg{ 94"-0" =>return Ok ( true ), 95"--offline" =>self . offline =true , 96"--frozen" =>self . frozen =true , 97"--no-types-packages" =>self . automatic_types =false , 98"--env-file" =>{ 99let value =value ( args, ParseError :: EnvFilePath ) ?; 100self . env_files . push ( self . cwd . join ( value)); 101} 102 _if arg. starts_with ( "--allow-" ) || arg. starts_with ( "--deny-" ) =>{ 103let ( flag, value) =flag_value ( arg) ?; 104let ( name, deny) = flag 105. strip_prefix ( "--allow-" ) 106. map ( |name|( name, false )) 107. or_else ( || flag. strip_prefix ( "--deny-" ). map ( |name|( name, true ))) 108. unwrap (); 109self . initial 110. grant ( name. parse ::< Category >() ?, deny, value, & self . cwd ) ?; 111} 112 _ =>{ 113let Some (( flag, value)) = arg. split_once ( '=' ) else { 114return Ok ( false ); 115}; 116match flag{ 117"--offline" |"--frozen" |"--no-types-packages" =>{ 118return Err ( ParseError :: UnexpectedValue { 119flag : flag. trim_start_matches ( '-' ). to_owned (), 120}); 121} 122"--jsr-registry" |"--npm-registry" |"--cache-dir" |"--lock" |"--vendor" 123 |"--include" |"--include-from" |"--env-file" 124if value. is_empty () => 125{ 126return Err ( ParseError :: EmptyValue { flag : flag. to_owned () }); 127} 128"--jsr-registry" =>self . jsr_registry = url:: Url :: parse ( value) ?, 129"--npm-registry" =>self . npm_registry = url:: Url :: parse ( value) ?, 130"--cache-dir" =>self . cache =self . cwd . join ( value), 131"--lock" =>self . lock =Some ( self . cwd . join ( value)), 132"--vendor" =>self . vendor =Some ( self . cwd . join ( value)), 133"--include" =>self 134. includes 135. push (( value. to_owned (), self . include_from . clone ())), 136"--include-from" =>{ 137self . include_from =( value !="." ). then ( || value. to_owned ()) 138} 139"--env-file" =>self . env_files . push ( self . cwd . join ( value)), 140 _ =>return Ok ( false ), 141} 142} 143} 144Ok ( true ) 145} 146 147pub fn finish ( self , path : PathBuf , module : String , lock : PathBuf ) ->Result < Entry , ParseError > { 148let explicit_lock =self . lock . is_some (); 149Ok ( Entry { 150caps : Capabilities :: new ( self . initial , self . cwd . clone ()), 151packages : Options { 152jsr_registry : self . jsr_registry , 153npm_registry : self . npm_registry , 154cache : self . cache , 155lock : self . lock . unwrap_or ( lock), 156project_root : self . cwd . clone (), 157offline : self . offline , 158frozen : self . frozen , 159update : false , 160ephemeral_lock : false , 161vendor : self . vendor , 162}, 163 explicit_lock, 164automatic_types : self . automatic_types , 165 path, 166 module, 167includes : self . includes , 168environment : crate :: env_file:: load ( & self . env_files ). map_err ( ParseError :: Env ) ?, 169}) 170} 171} 172 173/// A finished parse: everything a command needs to open the entry's graph. 174# [ derive ( Clone )] 175pub struct Entry { 176pub caps : Arc < Capabilities >, 177pub packages : Options , 178pub explicit_lock : bool , 179pub automatic_types : bool , 180pub path : PathBuf , 181pub module : String , 182pub includes : Vec <( String , Option < String >)>, 183pub environment : Environment , 184} 185 186pub enum Arg { 187Flag ( String ), 188Positional ( String ), 189} 190 191/// One command's argv: `--`, the table check, and the shared flags are 192/// handled here, so `next` yields only what the command itself must handle. 193pub struct Parser { 194pub command : &' static Command , 195pub common : Common , 196pub cwd : PathBuf , 197args : std:: vec:: IntoIter < String >, 198separator : bool , 199} 200 201impl Parser { 202pub fn new ( name : & str , argv : Vec < String >, cwd : & Path ) ->Result < Self , ParseError > { 203Ok ( Self { 204command : command ( name). ok_or_else ( ||ParseError :: Usage ( usage ())) ?, 205common : Common :: new ( & argv, cwd), 206cwd : cwd. to_owned (), 207args : argv. into_iter (), 208separator : false , 209}) 210} 211 212pub fn next ( & mut self ) ->Result < Option < Arg >, ParseError > { 213while let Some ( arg) =self . args . next () { 214if self . separator || !arg. starts_with ( '-' ) { 215return Ok ( Some ( Arg :: Positional ( arg))); 216} 217if arg =="--" { 218self . separator =true ; 219continue ; 220} 221self . command . check ( & arg). map_err ( ParseError :: Check ) ?; 222if !self . common . offer ( & arg, & mut self . args ) ?{ 223return Ok ( Some ( Arg :: Flag ( arg))); 224} 225} 226Ok ( None ) 227} 228 229pub fn value ( & mut self , missing : ParseError ) ->Result < String , ParseError > { 230value ( & mut self . args , missing) 231} 232 233pub fn usage ( & self ) ->ParseError { 234ParseError :: Usage ( self . command . usage ()) 235} 236 237/// `--flag=PATH` or `--flag PATH`, given at most once. 238pub fn path_once ( 239& mut self , 240arg : & str , 241flag : & str , 242slot : & mut Option < String >, 243) ->Result < bool , ParseError > { 244let ( name, inline) = arg 245. split_once ( '=' ) 246. map_or (( arg, None ), |( name, value) |( name, Some ( value))); 247if name != flag{ 248return Ok ( false ); 249} 250let flag = flag. trim_start_matches ( '-' ). to_owned (); 251let value = inline 252. map ( str:: to_owned) 253. or_else ( ||self . args . next ()) 254. filter ( |value| !value. is_empty () && !value. starts_with ( '-' )) 255. ok_or_else ( ||ParseError :: MissingPath { flag : flag. clone () }) ?; 256if slot. replace ( value). is_some () { 257return Err ( ParseError :: Duplicate { flag}); 258} 259Ok ( true ) 260} 261 262pub fn resolve ( & mut self , raw : & str ) ->Result < Resolved , ParseError > { 263if let Some ( url) = avril_runtime:: modules:: data_url ( raw) { 264return Ok ( Resolved { 265path : self . cwd . join ( ".avril-data.js" ), 266module : url. to_string (), 267lock : self . cwd . join ( "avril.lock" ), 268}); 269} 270if let Some ( url) = avril_runtime:: modules:: http_url ( raw) { 271self . common . initial . remote_entry ( & url) ?; 272return Ok ( Resolved { 273path : self . cwd . join ( ".avril-remote.js" ), 274module : url. to_string (), 275lock : self . cwd . join ( "avril.lock" ), 276}); 277} 278let path =self . cwd . join ( raw). canonicalize () ?; 279self . common . initial . entry ( & path); 280Ok ( Resolved { 281module : path. to_str (). ok_or ( ParseError :: ModulePath ) ?. to_owned (), 282lock : path. parent (). unwrap (). join ( "avril.lock" ), 283 path, 284}) 285} 286 287pub fn entry ( mut self , raw : & str ) ->Result < Entry , ParseError > { 288let resolved =self . resolve ( raw) ?; 289self . common 290. finish ( resolved. path , resolved. module , resolved. lock ) 291} 292 293/// An entry for commands without a module (repl, lsp). 294pub fn synthetic ( self , file : & str ) ->Result < Entry , ParseError > { 295let path =self . cwd . join ( file); 296let module = path. to_str (). ok_or ( ParseError :: ModulePath ) ?. to_owned (); 297let lock =self . cwd . join ( "avril.lock" ); 298let mut entry =self . common . finish ( path, module, lock) ?; 299 entry. packages . ephemeral_lock =true ; 300Ok ( entry) 301} 302} 303 304impl Entry { 305/// Resolves the project root for this entry's anchor: the lockfile 306/// defaults to the entry's directory unless `--lock` was explicit. 307pub fn project ( 308& self , 309repl : bool , 310) ->Result <( SourceAuthority , PathBuf , Options ), Box < dyn std:: error:: Error >> { 311let authority =SourceAuthority :: execute ( self . caps . clone ()); 312let anchor =self 313. path 314. to_str () 315. ok_or ( "Module paths must be valid UTF-8" ) ?; 316let root =if repl{ 317ModuleGraph :: project_root_for_repl ( & authority, anchor) ? 318} else { 319ModuleGraph :: project_root ( & authority, anchor) ? 320}; 321let mut packages =self . packages . clone (); 322if !self . explicit_lock { 323 packages. lock = root. join ( "avril.lock" ); 324} 325// Explicit --lock never changes the npm project root. 326 packages. project_root = root. clone (); 327Ok (( authority, root, packages)) 328} 329} 330 331pub struct Resolved { 332pub path : PathBuf , 333pub module : String , 334pub lock : PathBuf , 335} 336 337pub fn prepare_graph ( 338handle : tokio:: runtime:: Handle , 339authority : SourceAuthority , 340packages : Options , 341name : & str , 342includes : & [( String , Option < String >)], 343prepare_entry : bool , 344progress : Option < avril_runtime:: registry:: ProgressSink >, 345) ->Result < Rc < ModuleGraph >, Box < dyn std:: error:: Error >> { 346let store = avril_runtime:: registry:: PackageStore :: new_with_authority ( 347 handle, 348 packages, 349 authority. clone (), 350) ?; 351let store =Arc :: new ( match progress{ 352Some ( progress) => store. with_progress ( progress), 353None => store, 354}); 355let graph =Rc :: new ( ModuleGraph :: open_shared ( authority, store)); 356 futures:: executor:: block_on ( graph. initialize_package ( name)) ?; 357if prepare_entry{ 358 futures:: executor:: block_on ( graph. prepare ( name. to_owned (), String :: new ())) ?; 359} 360for ( include, from) in includes{ 361let referrer =match from{ 362Some ( from) =>{ 363 futures:: executor:: block_on ( graph. prepare ( from. clone (), name. to_owned ())) ? 364} 365None => name. to_owned (), 366}; 367if referrer. starts_with ( "avril:" ) { 368return Err ( "Include referrer must be a source module" . into ()); 369} 370 futures:: executor:: block_on ( graph. prepare ( include. clone (), referrer)) ?; 371} 372Ok ( graph) 373}