use crate::cli::sparql_autocomplete::SparqlAutocompleteProvider;
use rustyline_derive::{Helper, Highlighter, Validator};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Helper, Highlighter, Validator)]
pub(crate) struct SparqlHelper {
pub(crate) autocomplete_provider: SparqlAutocompleteProvider,
}
impl SparqlHelper {
pub(crate) fn new() -> Self {
Self {
autocomplete_provider: SparqlAutocompleteProvider::new(),
}
}
}
impl rustyline::completion::Completer for SparqlHelper {
type Candidate = String;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
use crate::cli::completion::{CompletionContext, CompletionProvider};
let context = CompletionContext::from_line(line, pos);
let completions = self.autocomplete_provider.get_completions(&context);
let line_before = &line[..pos];
let start = line_before
.rfind(char::is_whitespace)
.map(|i| i + 1)
.unwrap_or(0);
let candidates: Vec<String> = completions
.into_iter()
.map(|item| item.replacement)
.collect();
Ok((start, candidates))
}
}
impl rustyline::hint::Hinter for SparqlHelper {
type Hint = String;
fn hint(&self, line: &str, pos: usize, _ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
if pos < line.len() {
return None;
}
let line_upper = line.to_uppercase();
if line_upper.starts_with("SELECT") && !line_upper.contains("WHERE") {
Some(" WHERE { ?s ?p ?o }".to_string())
} else if line_upper.starts_with("PREFIX")
&& line.matches(':').count() == 1
&& !line.contains('<')
{
Some(" <http://example.org/>".to_string())
} else if line_upper.ends_with("WHERE") {
Some(" { ?s ?p ?o }".to_string())
} else {
None
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct QuerySession {
pub(crate) name: String,
pub(crate) dataset: String,
pub(crate) queries: Vec<String>,
pub(crate) created_at: String,
pub(crate) modified_at: String,
}
impl QuerySession {
pub(crate) fn new(name: String, dataset: String) -> Self {
let now = chrono::Local::now().to_rfc3339();
Self {
name,
dataset,
queries: Vec::new(),
created_at: now.clone(),
modified_at: now,
}
}
pub(crate) fn add_query(&mut self, query: String) {
self.queries.push(query);
self.modified_at = chrono::Local::now().to_rfc3339();
}
pub(crate) fn save_to_file(&self, path: &PathBuf) -> Result<(), String> {
let json = serde_json::to_string_pretty(self)
.map_err(|e| format!("Failed to serialize session: {}", e))?;
fs::write(path, json).map_err(|e| format!("Failed to write session file: {}", e))?;
Ok(())
}
pub(crate) fn load_from_file(path: &PathBuf) -> Result<Self, String> {
let json =
fs::read_to_string(path).map_err(|e| format!("Failed to read session file: {}", e))?;
let session: QuerySession = serde_json::from_str(&json)
.map_err(|e| format!("Failed to parse session file: {}", e))?;
Ok(session)
}
pub(crate) fn clear(&mut self) {
self.queries.clear();
self.modified_at = chrono::Local::now().to_rfc3339();
}
}