diff --git a/README.md b/README.md index 89d7eb2..71fbcb8 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ A command-line interface for searching Mozilla codebases using searchfox.org, wr Also available as a Rust library (`searchfox-lib`) and Python package (`searchfox-py`). +Set `SEARCHFOX_BASE_URL` to point at a different server (e.g. a local mozsearch instance: `SEARCHFOX_BASE_URL=http://127.0.0.1:16995`); defaults to `https://searchfox.org`. + ## Features - Search across multiple Mozilla repositories (mozilla-central, beta, release, ESR branches, comm-central) diff --git a/searchfox-cli/src/main.rs b/searchfox-cli/src/main.rs index 04858dd..e9f273a 100644 --- a/searchfox-cli/src/main.rs +++ b/searchfox-cli/src/main.rs @@ -18,7 +18,7 @@ use std::collections::HashMap; #[command( name = "searchfox-cli", about = "Searchfox CLI for Mozilla code search", - long_about = "A command-line interface for searching Mozilla codebases using searchfox.org.\n\nExamples:\n searchfox-cli -q AudioStream\n searchfox-cli -q AudioStream -C -l 10\n searchfox-cli -q '^Audio.*' -r\n searchfox-cli -q AudioStream -p ^dom/media\n searchfox-cli -p PContent.ipdl # Search for files by path only\n searchfox-cli --get-file dom/media/AudioStream.h\n searchfox-cli --symbol AudioContext\n searchfox-cli --symbol 'AudioContext::CreateGain'\n searchfox-cli --id main\n searchfox-cli -q 'path:dom/media AudioStream'\n searchfox-cli -q 'symbol:AudioContext' --context 3\n searchfox-cli --define 'AudioContext::CreateGain'\n searchfox-cli --calls-from 'mozilla::dom::AudioContext::CreateGain' --depth 2\n searchfox-cli --calls-to 'mozilla::dom::AudioContext::CreateGain' --depth 3\n searchfox-cli --calls-between 'AudioContext,AudioNode' --depth 2\n searchfox-cli --field-layout 'mozilla::dom::AudioContext'" + long_about = "A command-line interface for searching Mozilla codebases using searchfox.org.\n\nExamples:\n searchfox-cli -q AudioStream\n searchfox-cli -q AudioStream -C -l 10\n searchfox-cli -q '^Audio.*' -r\n searchfox-cli -q AudioStream -p ^dom/media\n searchfox-cli -p PContent.ipdl # Search for files by path only\n searchfox-cli --get-file dom/media/AudioStream.h\n searchfox-cli --symbol AudioContext\n searchfox-cli --symbol 'AudioContext::CreateGain'\n searchfox-cli --id main\n searchfox-cli -q 'path:dom/media AudioStream'\n searchfox-cli -q 'symbol:AudioContext' --context 3\n searchfox-cli --define 'AudioContext::CreateGain'\n searchfox-cli --calls-from 'mozilla::dom::AudioContext::CreateGain' --depth 2\n searchfox-cli --calls-to 'mozilla::dom::AudioContext::CreateGain' --depth 3\n searchfox-cli --calls-between 'AudioContext,AudioNode' --depth 2\n searchfox-cli --field-layout 'mozilla::dom::AudioContext'\n\nEnvironment:\n SEARCHFOX_BASE_URL Override the server base URL (default: https://searchfox.org)" )] struct Args { #[arg(short, long, help = "Search query string")] @@ -365,7 +365,7 @@ async fn main() -> Result<()> { if args.log_requests { eprintln!("=== REQUEST LOGGING ENABLED ==="); if let Err(e) = client.ping().await { - eprintln!("[PING] Warning: Could not ping searchfox.org: {e}"); + eprintln!("[PING] Warning: Could not ping {}: {e}", client.base_url()); } eprintln!("================================"); } @@ -445,7 +445,14 @@ async fn main() -> Result<()> { if let Some((start, end)) = extract_line_range_from_output(&context) { println!( "{}", - generate_link(&client.repo, file_path, start, end, hash.as_deref()) + generate_link( + client.base_url(), + &client.repo, + file_path, + start, + end, + hash.as_deref() + ) ); } } @@ -493,7 +500,14 @@ async fn main() -> Result<()> { }; println!( "{}", - generate_link(&client.repo, path, start, end, hash.as_deref()) + generate_link( + client.base_url(), + &client.repo, + path, + start, + end, + hash.as_deref() + ) ); } else { let content = client.get_file(path).await?; @@ -641,8 +655,13 @@ async fn main() -> Result<()> { println!("### {category}\n"); for r in group { println!( - "- {}:{} — https://searchfox.org/{}/source/{}#{}", - r.path, r.line_number, args.repo, r.path, r.line_number + "- {}:{} — {}/{}/source/{}#{}", + r.path, + r.line_number, + client.base_url(), + args.repo, + r.path, + r.line_number ); } println!(); @@ -665,6 +684,7 @@ async fn main() -> Result<()> { println!( "{}", generate_link( + client.base_url(), &client.repo, &result.path, result.line_number, @@ -746,6 +766,7 @@ async fn main() -> Result<()> { } fn generate_link( + base_url: &str, repo: &str, path: &str, start_line: usize, @@ -761,14 +782,8 @@ fn generate_link( }; let rev_repo = searchfox_url_repo(repo); match hash { - Some(h) => format!( - "https://searchfox.org/{}/rev/{}/{}{}", - rev_repo, h, path, fragment - ), - None => format!( - "https://searchfox.org/{}/source/{}{}", - rev_repo, path, fragment - ), + Some(h) => format!("{}/{}/rev/{}/{}{}", base_url, rev_repo, h, path, fragment), + None => format!("{}/{}/source/{}{}", base_url, rev_repo, path, fragment), } } diff --git a/searchfox-lib/src/blame.rs b/searchfox-lib/src/blame.rs index 583e081..c63b2bb 100644 --- a/searchfox-lib/src/blame.rs +++ b/searchfox-lib/src/blame.rs @@ -9,7 +9,8 @@ use std::collections::HashMap; impl SearchfoxClient { pub async fn get_head_hash(&self) -> anyhow::Result { let url = format!( - "https://searchfox.org/{}/commit-info/HEAD", + "{}/{}/commit-info/HEAD", + self.base_url, searchfox_url_repo(&self.repo) ); let response = self.get_raw(&url).await?; @@ -31,7 +32,8 @@ impl SearchfoxClient { .filter(|h| h.len() == 40 && h.chars().all(|c| c.is_ascii_hexdigit())) { let hgrev_url = format!( - "https://searchfox.org/{}/hgrev/{}", + "{}/{}/hgrev/{}", + self.base_url, searchfox_url_repo(&self.repo), hg_hash ); @@ -66,7 +68,7 @@ impl SearchfoxClient { lines: &[usize], ) -> Result> { // Fetch the HTML page for the file - let url = format!("https://searchfox.org/{}/source/{}", self.repo, path); + let url = format!("{}/{}/source/{}", self.base_url, self.repo, path); let html = self.get_html(&url).await?; // Parse blame data from HTML @@ -135,10 +137,7 @@ impl SearchfoxClient { for chunk in revs.chunks(BATCH_SIZE) { let revs_str = chunk.join(","); - let url = format!( - "https://searchfox.org/{}/commit-info/{}", - self.repo, revs_str - ); + let url = format!("{}/{}/commit-info/{}", self.base_url, self.repo, revs_str); let response = self.get_raw(&url).await?; let mut commit_infos: Vec = serde_json::from_str(&response)?; diff --git a/searchfox-lib/src/call_graph.rs b/searchfox-lib/src/call_graph.rs index 76d0f04..4472256 100644 --- a/searchfox-lib/src/call_graph.rs +++ b/searchfox-lib/src/call_graph.rs @@ -262,10 +262,7 @@ impl SearchfoxClient { anyhow::bail!("No call graph query specified"); }; - let mut url = Url::parse(&format!( - "https://searchfox.org/{}/query/default", - self.repo - ))?; + let mut url = Url::parse(&format!("{}/{}/query/default", self.base_url, self.repo))?; url.query_pairs_mut().append_pair("q", &query_string); let response = self.get(url).await?; diff --git a/searchfox-lib/src/can_gc.rs b/searchfox-lib/src/can_gc.rs index fb33692..eead7cf 100644 --- a/searchfox-lib/src/can_gc.rs +++ b/searchfox-lib/src/can_gc.rs @@ -95,7 +95,7 @@ impl SearchfoxClient { async fn resolve_full_names(&self, symbol: &str) -> Result> { let query = format!("id:{symbol}"); - let mut url = Url::parse(&format!("https://searchfox.org/{}/search", self.repo))?; + let mut url = Url::parse(&format!("{}/{}/search", self.base_url, self.repo))?; url.query_pairs_mut().append_pair("q", &query); let response = self.get(url).await?; diff --git a/searchfox-lib/src/client.rs b/searchfox-lib/src/client.rs index 5a6cd1b..ee50788 100644 --- a/searchfox-lib/src/client.rs +++ b/searchfox-lib/src/client.rs @@ -22,11 +22,14 @@ impl SearchfoxClient { crate::cache::prune(&conn); std::sync::Mutex::new(conn) }); + let base_url = std::env::var("SEARCHFOX_BASE_URL") + .map(|v| v.trim_end_matches('/').to_string()) + .unwrap_or_else(|_| "https://searchfox.org".to_string()); Ok(Self { client, repo, log_requests, - base_url: "https://searchfox.org".to_string(), + base_url, request_counter: std::sync::atomic::AtomicUsize::new(0), cache, cache_enabled: true, @@ -133,15 +136,16 @@ impl SearchfoxClient { } eprintln!( - "[PING] Testing network latency to searchfox.org (ICMP ping disabled, using HTTP HEAD)..." + "[PING] Testing network latency to {} (ICMP ping disabled, using HTTP HEAD)...", + self.base_url ); - let ping_url = "https://searchfox.org/"; + let ping_url = format!("{}/", self.base_url); let start = Instant::now(); let response = self .client - .head(ping_url) + .head(&ping_url) .timeout(Duration::from_secs(10)) .send() .await?; @@ -266,6 +270,10 @@ impl SearchfoxClient { &self.client } + pub fn base_url(&self) -> &str { + &self.base_url + } + pub fn set_cache_enabled(&mut self, enabled: bool) { self.cache_enabled = enabled; } diff --git a/searchfox-lib/src/field_layout.rs b/searchfox-lib/src/field_layout.rs index 866214a..405e04e 100644 --- a/searchfox-lib/src/field_layout.rs +++ b/searchfox-lib/src/field_layout.rs @@ -249,10 +249,7 @@ impl SearchfoxClient { pub async fn search_field_layout(&self, query: &FieldLayoutQuery) -> Result { let query_string = format!("field-layout:'{}'", query.class_name); - let mut url = Url::parse(&format!( - "https://searchfox.org/{}/query/default", - self.repo - ))?; + let mut url = Url::parse(&format!("{}/{}/query/default", self.base_url, self.repo))?; url.query_pairs_mut().append_pair("q", &query_string); let response = self.get(url).await?; diff --git a/searchfox-lib/src/search.rs b/searchfox-lib/src/search.rs index 991630d..990c197 100644 --- a/searchfox-lib/src/search.rs +++ b/searchfox-lib/src/search.rs @@ -188,7 +188,7 @@ impl SearchfoxClient { pub async fn search(&self, options: &SearchOptions) -> Result> { let query = options.build_query(); - let mut url = Url::parse(&format!("https://searchfox.org/{}/search", self.repo))?; + let mut url = Url::parse(&format!("{}/{}/search", self.base_url, self.repo))?; url.query_pairs_mut() .append_pair("q", &query) .append_pair("case", if options.case { "true" } else { "false" }) @@ -334,7 +334,7 @@ impl SearchfoxClient { symbol.to_string() }; let query = format!("id:{search_symbol}"); - let mut url = Url::parse(&format!("https://searchfox.org/{}/search", self.repo))?; + let mut url = Url::parse(&format!("{}/{}/search", self.base_url, self.repo))?; url.query_pairs_mut().append_pair("q", &query); if let Some(path) = path_filter { url.query_pairs_mut().append_pair("path", path); diff --git a/searchfox-lib/src/utils.rs b/searchfox-lib/src/utils.rs index 7ff7eec..f7e3eca 100644 --- a/searchfox-lib/src/utils.rs +++ b/searchfox-lib/src/utils.rs @@ -213,6 +213,11 @@ pub fn is_potential_definition(line: &Line, query: &str) -> bool { } pub fn searchfox_url_repo(repo: &str) -> &str { + // The firefox-* renames only exist on searchfox.org; a server behind + // SEARCHFOX_BASE_URL serves trees under their configured names verbatim. + if std::env::var("SEARCHFOX_BASE_URL").is_ok() { + return repo; + } match repo { "mozilla-central" => "firefox-main", "mozilla-beta" => "firefox-beta",