use amql_engine::{
find_project_root, load_manifest, meta, run_all_extractors, suggest_repairs, unified_query,
validate, AnnotationStore, CodeCache, ExtractorRegistry, Manifest, ProjectRoot,
ResolverRegistry, Scope,
};
use rustyline::error::ReadlineError;
use rustyline::DefaultEditor;
use std::path::PathBuf;
use std::process::ExitCode;
const PROMPT: &str = "aql> ";
const HISTORY_FILE: &str = ".aql_history";
pub fn run_repl() -> ExitCode {
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let project_root = match find_project_root(&cwd) {
Some(root) => root,
None => {
eprintln!(
"Error: no {} found in any parent directory",
meta::schema_file()
);
return ExitCode::FAILURE;
}
};
let manifest = match load_manifest(&project_root) {
Ok(m) => m,
Err(e) => {
eprintln!("Error loading manifest: {e}");
return ExitCode::FAILURE;
}
};
let resolvers = ResolverRegistry::with_defaults();
let mut cache = CodeCache::new(&project_root);
let mut store = AnnotationStore::new(&project_root);
store.load_all_from_locator();
load_extractors(&manifest, &project_root, &mut store);
let mut editor = match DefaultEditor::new() {
Ok(e) => e,
Err(e) => {
eprintln!("Failed to initialize editor: {e}");
return ExitCode::FAILURE;
}
};
let history_path = dirs_path().join(HISTORY_FILE);
let _ = editor.load_history(&history_path);
println!("AQL REPL — type a selector to query, or :help for commands");
loop {
match editor.readline(PROMPT) {
Ok(line) => {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let _ = editor.add_history_entry(trimmed);
if trimmed.starts_with(':') {
match handle_command(trimmed, &store, &manifest, &cache) {
CommandResult::Continue => {}
CommandResult::Quit => break,
}
} else {
match unified_query(
trimmed,
&Scope::from(""),
&mut cache,
&mut store,
&resolvers,
None,
) {
Ok(results) => {
println!("{}", serde_json::to_string_pretty(&results).unwrap());
}
Err(e) => {
eprintln!("Error: {e}");
}
}
}
}
Err(ReadlineError::Interrupted | ReadlineError::Eof) => break,
Err(e) => {
eprintln!("Error: {e}");
break;
}
}
}
let _ = editor.save_history(&history_path);
ExitCode::SUCCESS
}
enum CommandResult {
Continue,
Quit,
}
fn handle_command(
input: &str,
store: &AnnotationStore,
manifest: &Manifest,
cache: &CodeCache,
) -> CommandResult {
match input {
":quit" | ":q" => CommandResult::Quit,
":schema" => {
println!("{}", serde_json::to_string_pretty(manifest).unwrap());
CommandResult::Continue
}
":validate" => {
let results = validate(store, manifest);
println!("{}", serde_json::to_string_pretty(&results).unwrap());
CommandResult::Continue
}
":repair" => {
let suggestions = suggest_repairs(store, Some(cache));
println!("{}", serde_json::to_string_pretty(&suggestions).unwrap());
CommandResult::Continue
}
":help" | ":h" => {
println!("Commands:");
println!(" :schema — print the manifest schema");
println!(" :validate — validate annotations against schema");
println!(" :repair — suggest fixes for broken bindings");
println!(" :quit — exit the REPL");
println!();
println!("Type any selector to run a unified query.");
CommandResult::Continue
}
_ => {
eprintln!("Unknown command: {input}. Type :help for available commands.");
CommandResult::Continue
}
}
}
fn load_extractors(
manifest: &Manifest,
project_root: &std::path::Path,
store: &mut AnnotationStore,
) {
let root = ProjectRoot::from(project_root);
let registry = ExtractorRegistry::with_defaults();
let results = run_all_extractors(manifest, &root, ®istry);
for result in results {
if !result.annotations.is_empty() {
store.load_extractor_output(result.annotations);
}
}
}
fn dirs_path() -> PathBuf {
std::env::var_os("HOME")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("."))
}