|
| 1 | +use std::collections::HashMap; |
| 2 | +use std::path::{Path, PathBuf}; |
| 3 | +use clap::Parser; |
| 4 | +use itertools::Itertools; |
| 5 | + |
| 6 | + |
| 7 | +fn main() -> anyhow::Result<()> { |
| 8 | + let cli = Cli::parse(); |
| 9 | + cli.run()?; |
| 10 | + Ok(()) |
| 11 | +} |
| 12 | + |
| 13 | +// Example usage: |
| 14 | +// (cd compiler-rs; find ../specification -name '*Request.ts' | cargo run --bin add_url_paths ../output/schema/schema.json | sh) |
| 15 | + |
| 16 | +/// Adds url paths to request definitions. Stdin must be a list of files, one per line. |
| 17 | +/// Outputs a shell script that uses ast-grep. |
| 18 | +#[derive(Debug, Parser)] |
| 19 | +#[command(author, version, about, long_about)] |
| 20 | +pub struct Cli { |
| 21 | + /// input schema file, eg: ../output/schema/schema-no-generics.json |
| 22 | + schema: PathBuf, |
| 23 | +} |
| 24 | + |
| 25 | +impl Cli { |
| 26 | + pub fn run(&self) -> anyhow::Result<()> { |
| 27 | + |
| 28 | + // Canonicalize all file names, so that we can do some suffix mapping from the schema locations. |
| 29 | + let files: Vec<PathBuf> = std::io::read_to_string(std::io::stdin())? |
| 30 | + .lines() |
| 31 | + .flat_map(|line| std::fs::canonicalize(line) |
| 32 | + .map_err(|e| { |
| 33 | + eprintln!("File {} not found", line); |
| 34 | + Result::<PathBuf, _>::Err(e) |
| 35 | + })) // Remove errors |
| 36 | + .collect(); |
| 37 | + |
| 38 | + let json = std::fs::read_to_string(&self.schema)?; |
| 39 | + let schema = clients_schema::IndexedModel::from_reader(json.as_bytes())?; |
| 40 | + |
| 41 | + let mut location_to_request = HashMap::<&Path, &clients_schema::Endpoint>::new(); |
| 42 | + for ep in &schema.endpoints { |
| 43 | + let Some(req_name) = ep.request.as_ref() else { |
| 44 | + //eprintln!("Skipping endpoint {} with no request", ep.name); |
| 45 | + continue; |
| 46 | + }; |
| 47 | + |
| 48 | + let type_def = schema.types.get(req_name).unwrap(); |
| 49 | + let location = type_def.base().spec_location.as_ref().unwrap(); |
| 50 | + let location = Path::new(location.split_once('#').unwrap().0); |
| 51 | + |
| 52 | + location_to_request.insert(location, ep); |
| 53 | + }; |
| 54 | + |
| 55 | + for file in files { |
| 56 | + if let Some((_, endpoint)) = location_to_request.iter().find(|(location, _)| file.ends_with(location)) { |
| 57 | + generate_astgrep_command(&file, endpoint); |
| 58 | + } else { |
| 59 | + eprintln!("No request found for {:?}", file); |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + Ok(()) |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +fn generate_astgrep_command(file: &Path, endpoint: &clients_schema::Endpoint) { |
| 68 | + |
| 69 | + let text = std::fs::read_to_string(file).unwrap(); |
| 70 | + if text.contains("urls:") { |
| 71 | + eprintln!("Found an existing 'url' property. Skipping {file:?}"); |
| 72 | + return; |
| 73 | + } |
| 74 | + |
| 75 | + // We cannot express conditional parts in the source form of patterns. |
| 76 | + |
| 77 | + // Requests with generic parameters |
| 78 | + let request_expr = if text.contains("Request<") { |
| 79 | + "Request<$$$PARAM>" |
| 80 | + } else { |
| 81 | + "Request" |
| 82 | + }; |
| 83 | + |
| 84 | + // A handful of requests don't have an extends clause |
| 85 | + let extends_expr = if text.contains(" extends ") { |
| 86 | + "extends $REQBASE" |
| 87 | + } else { |
| 88 | + "" |
| 89 | + }; |
| 90 | + |
| 91 | + let urls: String = endpoint.urls.iter().map(|url| { |
| 92 | + let path = &url.path; |
| 93 | + let methods = url.methods.iter().map(|method| format!("\"{}\"", method)).join(", "); |
| 94 | + let deprecation = match &url.deprecation { |
| 95 | + Some(deprecation) => format!("/** @deprecated {} {} */\n ", deprecation.version, deprecation.description), |
| 96 | + None => "".to_string(), |
| 97 | + }; |
| 98 | + |
| 99 | + format!(r#" {{ |
| 100 | + {deprecation}path: "{path}", |
| 101 | + methods: [{methods}] |
| 102 | + }}"#) |
| 103 | + }).join(",\n"); |
| 104 | + |
| 105 | + let pattern = format!(r#"interface {request_expr} {extends_expr} {{ |
| 106 | + $$$PROPS |
| 107 | +}}"#); |
| 108 | + |
| 109 | + let fix = format!(r#"interface {request_expr} {extends_expr} {{ |
| 110 | + urls: [ |
| 111 | +{urls} |
| 112 | + ], |
| 113 | + $$$PROPS |
| 114 | +}}"#); |
| 115 | + |
| 116 | + let file = file.to_str().unwrap(); |
| 117 | + println!("#----- {file}"); |
| 118 | + println!(r#"ast-grep --update-all --lang ts --pattern '{pattern}' --rewrite '{fix}' "{file}""#); |
| 119 | + |
| 120 | + println!(); |
| 121 | +} |
0 commit comments