use crate::refresh;
use serde_json::json;
use std::ffi::OsString;
use std::io::Write;
use std::process::ExitCode;
pub(super) fn run(args: &[OsString], stdout: &mut impl Write, stderr: &mut impl Write) -> ExitCode {
let (analyzer, args) = match super::hidden::analyzer(args) {
Ok(split) => split,
Err(error) => {
let _ = writeln!(stderr, "ripscope: --dump-units: {error}");
return ExitCode::FAILURE;
}
};
let names: Vec<OsString> = match args.as_slice() {
[flag, list] if flag == "-i" => match std::fs::read(list) {
Ok(bytes) => bytes
.split(|byte| *byte == b'\n')
.map(|line| line.strip_suffix(b"\r").unwrap_or(line))
.filter(|line| !line.is_empty())
.map(|line| refresh::path_of(line).into_os_string())
.collect(),
Err(error) => {
let _ = writeln!(
stderr,
"ripscope: cannot read {}: {error}",
list.to_string_lossy()
);
return ExitCode::FAILURE;
}
},
files => files.to_vec(),
};
for name in names {
let display = name.to_string_lossy().into_owned();
let bytes = match std::fs::read(&name) {
Ok(bytes) => bytes,
Err(error) => {
let _ = writeln!(stderr, "ripscope: cannot read {display}: {error}");
continue;
}
};
let records: Vec<_> = analyzer
.records(name.as_encoded_bytes(), &bytes)
.iter()
.map(|record| {
encode_line(
record.line,
&record.text,
record.symbols.iter().map(|symbol| {
(
symbol.at,
symbol.at + symbol.len,
symbol.mark.byte(),
symbol.assigned,
)
}),
record.define_end_after,
)
})
.collect();
let line = json!({ "file": display, "records": records });
if writeln!(stdout, "{line}").is_err() {
return ExitCode::FAILURE;
}
}
ExitCode::SUCCESS
}
fn latin1(bytes: &[u8]) -> String {
bytes.iter().map(|byte| char::from(*byte)).collect()
}
fn encode_line(
number: u32,
text: &[u8],
names: impl Iterator<Item = (usize, usize, u8, bool)>,
define_end: bool,
) -> serde_json::Value {
let mut fragments = Vec::new();
let mut symbols = Vec::new();
let mut from = 0;
for (at, end, mark, assigned) in names {
fragments.push(latin1(&text[from..at]));
symbols.push(json!([
(mark as char).to_string(),
latin1(&text[at..end]),
assigned
]));
from = end;
}
fragments.push(latin1(&text[from..]));
json!({"line": number, "fragments": fragments, "symbols": symbols, "define_end": define_end})
}
pub(super) fn stored(
args: &[OsString],
stdout: &mut impl Write,
stderr: &mut impl Write,
) -> ExitCode {
let [flag, index] = args else {
let _ = writeln!(stderr, "ripscope: --dump-store needs -f INDEX");
return ExitCode::FAILURE;
};
if flag != "-f" {
let _ = writeln!(stderr, "ripscope: --dump-store needs -f INDEX");
return ExitCode::FAILURE;
}
let result = (|| -> Result<(), crate::store::StoreError> {
let store =
crate::store::Store::open(std::path::Path::new(index), crate::store::Access::Read)?;
let snapshot = store.snapshot()?;
for file in snapshot.files() {
let unit = snapshot.unit(file.unit)?;
let records: Vec<_> = unit
.lines()
.map(|line| {
encode_line(
line.number,
line.text,
line.names().map(|name| {
let at = name.at as usize;
(at, at + name.text.len(), name.mark.byte(), name.assigned)
}),
line.define_end_after,
)
})
.collect();
let row = json!({"file":latin1(&file.path),"records":records});
writeln!(stdout, "{row}").map_err(crate::store::StoreError::Io)?;
}
Ok(())
})();
match result {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
let _ = writeln!(stderr, "ripscope: {error}");
ExitCode::FAILURE
}
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn listed_file_names_that_are_not_utf8_are_read() {
use std::os::unix::ffi::OsStrExt;
let directory = tempfile::tempdir().expect("directory");
let mut name = directory.path().as_os_str().as_bytes().to_vec();
name.extend_from_slice(b"/caf\xe9.c");
std::fs::write(std::ffi::OsStr::from_bytes(&name), b"int Alpha;\n").expect("file");
let list = directory.path().join("list");
std::fs::write(&list, [&name[..], b"\n"].concat()).expect("list");
let args = [OsString::from("-i"), list.into_os_string()];
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
assert_eq!(run(&args, &mut stdout, &mut stderr), ExitCode::SUCCESS);
assert!(stderr.is_empty(), "{}", String::from_utf8_lossy(&stderr));
let line: serde_json::Value = serde_json::from_slice(&stdout).expect("one JSON line");
assert_eq!(line["records"].as_array().map(Vec::len), Some(1), "{line}");
}
}