///|
/// import(对齐上游 cmd/import.rs + import/run):
/// 空库直灌 / --merge 合并进非空库;坏行按 `<来源>:<行号>: <原因>` 上报不中止;
///|
/// 坏行定位前缀(`<来源>:<行号>`;atuin 无文件来源时为 `line N`)。
/// 只对坏行调用——成功行不构造定位串
fn make_location(source : String?, line_num : Int) -> String {
match source {
Some(path) => "\{path}:\{line_num}"
None => "line \{line_num}"
}
}
///|
/// 结束后 dedup + age(maxage) 再落盘。atuin 走子进程,其余插件读数据文件。
async fn run_import(
iopts : @cli.ImportOptions,
getenv : (String) -> String?,
errs : Array[String],
) -> Int {
let data_dir = match cfg(@config.data_dir(getenv), errs) {
Some(data_dir) => data_dir
None => return 1
}
let store = match open_store(data_dir, errs) {
Some(s) => s
None => return 1
}
if !iopts.merge && !store.database().is_empty() {
err_line(
errs, "current database is not empty, specify --merge to continue anyway",
)
return 1
}
let globs = match cfg(@config.exclude_dirs(getenv, getenv("HOME")), errs) {
Some(globs) => globs
None => return 1
}
// 单趟流式处理(对齐上游 import.rs::run 的惰性迭代结构):逐行
// 「Err 行/解析失败 → <定位>: <原因> 上报不中止;Ok → exclude 命中静默跳过、
// 直接入库」。atuin 折叠语义需要完整记录序列(六插件唯一带状态者),保持物化
let parse : (String) -> Result[@core.Dir, String] = match iopts.plugin {
"autojump" => @core.parse_autojump_line
"atuin" => @core.parse_atuin_line
// z / zsh-z / fasd / z.lua 与 z 同格式,共用解析(对齐上游复用 z::Iter)
_ => @core.parse_z_line
}
// 单行处理内核:空行静默跳过(上游迭代器 continue);
// location 串只对坏行构造(成功行下游不读它;百万行级每行拼串是可测开销)
let process_line = fn(
source : String?,
line_num : Int,
line : Result[String, String],
) -> Unit {
match line {
Err(reason) =>
err_line(errs, "\{make_location(source, line_num)}: \{reason}")
Ok(l) =>
if l != "" {
match parse(l) {
Ok(dir) =>
// _JB_EXCLUDE_DIRS 命中 → 静默跳过(对齐上游 exclude_dirs 过滤)
if !glob_hit(globs, dir.path) {
store
.database()
.add_unchecked(dir.path, dir.rank, dir.last_accessed)
}
Err(reason) =>
err_line(errs, "\{make_location(source, line_num)}: \{reason}")
}
}
}
}
if iopts.plugin == "atuin" {
let (records, _) = match @platform.atuin_history_records() {
Ok((records, stderr_text)) => {
forward_atuin_stderr(errs, stderr_text)
let source : String? = None
(records, source)
}
Err(msg) => {
err_line(errs, msg)
return 1
}
}
let parsed : Array[@core.LocatedDir] = []
for record in records {
match record.result {
Err(reason) =>
parsed.push({
location: make_location(None, record.line_num),
result: Err(reason),
})
Ok(line) =>
if line != "" {
match parse(line) {
Ok(dir) => parsed.push({ location: "", result: Ok(dir), })
Err(reason) =>
parsed.push({
location: make_location(None, record.line_num),
result: Err(reason),
})
}
}
}
}
for entry in @core.collapse_consecutive(parsed) {
match entry.result {
Err(reason) => err_line(errs, "\{entry.location}: \{reason}")
Ok(dir) =>
if !glob_hit(globs, dir.path) {
store
.database()
.add_unchecked(dir.path, dir.rank, dir.last_accessed)
}
}
}
} else {
// 文件源:逐行回调,行串即抛不物化记录数组;z.lua 主路径缺失回落
// fish 变体、双双缺失报主路径(对齐上游 z_lua.rs)
let stream_file = async fn(
path_res : Result[String, String],
) -> Result[Unit, String] {
match path_res {
Ok(path) =>
@platform.for_each_import_line(path, fn(line_num, line) {
process_line(Some(path), line_num, line)
})
Err(msg) => Err(msg)
}
}
let done = match iopts.plugin {
"z" => stream_file(@config.z_data_path(getenv))
"fasd" => stream_file(@config.fasd_data_path(getenv))
"zsh-z" => stream_file(@config.zsh_z_data_path(getenv))
"autojump" => stream_file(@config.autojump_data_path(getenv))
"z.lua" =>
match stream_file(@config.zlua_data_path(getenv)) {
Ok(_) => Ok(())
Err(_) =>
match @config.zlua_fish_path(getenv) {
Ok(fish_path) =>
if @afs.exists(fish_path) {
stream_file(Ok(fish_path))
} else {
stream_file(@config.zlua_data_path(getenv))
}
Err(msg) => Err(msg)
}
}
other => Err("invalid value '\{other}' for ''")
}
match done {
Ok(_) => ()
Err(msg) => {
err_line(errs, msg)
return 1
}
}
}
if store.database().is_dirty() {
store.database().dedup()
let maxage = match cfg(@config.maxage(getenv), errs) {
Some(maxage) => maxage
None => return 1
}
store.database().age(maxage)
}
finish(store, errs, 0)
}
///|
/// atuin stderr 透传(对齐上游继承 stderr 的字节原样):err_line 会补尾换行,
/// 原文自带的尾换行须先剥掉,否则多出一个空行(v18 真机回放实测的真分歧)
fn forward_atuin_stderr(errs : Array[String], text : String) -> Unit {
if text == "" {
return
}
let t = if text.has_suffix("\n") {
text[0:text.length() - 1].to_owned()
} else {
text
}
err_line(errs, t)
}