use std::collections::BTreeMap;
use std::io::Write;
use anyhow::Result;
use pelicode_core::{FileFeatures, Hash128, Level};
use pelicode_db::FunctionRow;
use serde::Serialize;
pub fn json<T: Serialize + ?Sized>(out: &mut dyn Write, value: &T) -> Result<()> {
serde_json::to_writer_pretty(&mut *out, value)?;
writeln!(out)?;
Ok(())
}
fn short(hash: &str) -> &str {
&hash[..hash.len().min(16)]
}
pub fn extract_text(
out: &mut dyn Write,
files: &[FileFeatures],
tokens: Option<Level>,
) -> Result<()> {
for file in files {
for f in &file.functions {
writeln!(
out,
"{}:{}-{}\t{}\t{}\t{}\ttokens={}\tN1={}{}",
file.path.display(),
f.start_line,
f.end_line,
file.language,
f.name,
f.kind,
f.token_count,
short(&f.exact_hashes[&Level::N1].to_hex()),
if f.is_indexed() { "" } else { "\t(too small)" },
)?;
if let (Some(level), Some(all)) = (tokens, &f.tokens) {
writeln!(out, " {level}: {}", all[&level].join(" "))?;
}
}
}
Ok(())
}
pub fn export_text(out: &mut dyn Write, rows: &[FunctionRow]) -> Result<()> {
for r in rows {
writeln!(
out,
"{}:{}@{}\t{}:{}-{}\t{}\t{}\ttokens={}\tN1={}",
r.ecosystem,
r.package,
r.version,
r.path,
r.start_line,
r.end_line,
r.language,
r.name,
r.token_count,
r.exact_hashes.get(&Level::N1).map_or("", |h| short(h)),
)?;
}
Ok(())
}
#[derive(Serialize)]
struct ExtractRecord<'a> {
path: String,
language: &'a str,
name: &'a str,
kind: &'a str,
start_line: usize,
end_line: usize,
start_column: usize,
end_column: usize,
start_byte: usize,
end_byte: usize,
parent: Option<usize>,
token_count: usize,
node_count: usize,
depth: usize,
indexed: bool,
n0: String,
n1: String,
n2: String,
n3: String,
#[serde(skip_serializing_if = "Option::is_none")]
tokens: Option<String>,
}
pub fn extract_csv(
out: &mut dyn Write,
files: &[FileFeatures],
tokens: Option<Level>,
) -> Result<()> {
let mut writer = csv::Writer::from_writer(out);
for file in files {
let path = file.path.to_string_lossy().into_owned();
for f in &file.functions {
let hex = |level| {
f.exact_hashes
.get(&level)
.map(Hash128::to_hex)
.unwrap_or_default()
};
writer.serialize(ExtractRecord {
path: path.clone(),
language: file.language.name(),
name: &f.name,
kind: &f.kind,
start_line: f.start_line,
end_line: f.end_line,
start_column: f.start_column,
end_column: f.end_column,
start_byte: f.start_byte,
end_byte: f.end_byte,
parent: f.parent,
token_count: f.token_count,
node_count: f.node_count,
depth: f.depth,
indexed: f.is_indexed(),
n0: hex(Level::N0),
n1: hex(Level::N1),
n2: hex(Level::N2),
n3: hex(Level::N3),
tokens: tokens.and_then(|level| Some(f.tokens.as_ref()?[&level].join(" "))),
})?;
}
}
writer.flush()?;
Ok(())
}
#[derive(Serialize)]
struct ExportRecord<'a> {
id: i64,
ecosystem: &'a str,
package: &'a str,
version: &'a str,
license: Option<&'a str>,
path: &'a str,
language: &'a str,
name: &'a str,
kind: &'a str,
start_line: usize,
end_line: usize,
start_column: usize,
end_column: usize,
start_byte: usize,
end_byte: usize,
parent_id: Option<i64>,
token_count: usize,
node_count: usize,
depth: usize,
indexed: bool,
n0: &'a str,
n1: &'a str,
n2: &'a str,
n3: &'a str,
}
pub fn export_csv(out: &mut dyn Write, rows: &[FunctionRow]) -> Result<()> {
let mut writer = csv::Writer::from_writer(out);
for r in rows {
let hex = |level| hash_of(&r.exact_hashes, level);
writer.serialize(ExportRecord {
id: r.id,
ecosystem: &r.ecosystem,
package: &r.package,
version: &r.version,
license: r.license.as_deref(),
path: &r.path,
language: &r.language,
name: &r.name,
kind: &r.kind,
start_line: r.start_line,
end_line: r.end_line,
start_column: r.start_column,
end_column: r.end_column,
start_byte: r.start_byte,
end_byte: r.end_byte,
parent_id: r.parent_id,
token_count: r.token_count,
node_count: r.node_count,
depth: r.depth,
indexed: r.indexed,
n0: hex(Level::N0),
n1: hex(Level::N1),
n2: hex(Level::N2),
n3: hex(Level::N3),
})?;
}
writer.flush()?;
Ok(())
}
fn hash_of(hashes: &BTreeMap<Level, String>, level: Level) -> &str {
hashes.get(&level).map_or("", String::as_str)
}