KeiSeiKit-1.0/_primitives/_rust/kei-gdrive-import/src/scan.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

78 lines
2.5 KiB
Rust

//! Walk-tree scanner.
//!
//! Two backends:
//! * local FS (`std::fs::read_dir`, no `walkdir` dep)
//! * remote rclone (shell out to `rclone lsjson <remote> --dirs-only`)
//!
//! Depth: one level under root. The wizard recurses by re-invoking
//! `scan-tree` on subfolders the user marks AMBIGUOUS — keeps the
//! primitive flat and predictable.
use std::path::{Path, PathBuf};
use std::process::Command;
use anyhow::{anyhow, Context, Result};
use serde::Deserialize;
use crate::classify::{classify, Classification};
pub fn scan_tree(root: &Path) -> Result<Vec<Classification>> {
let mut out: Vec<Classification> = Vec::new();
let entries = std::fs::read_dir(root)
.with_context(|| format!("read_dir {}", root.display()))?;
for entry in entries {
let entry = entry?;
let p = entry.path();
if !p.is_dir() {
continue;
}
out.push(classify(&p));
}
out.sort_by(|a, b| a.path.cmp(&b.path));
Ok(out)
}
#[derive(Debug, Deserialize)]
struct RcloneEntry {
#[serde(rename = "Name")]
name: String,
#[serde(rename = "IsDir")]
is_dir: bool,
}
pub fn scan_remote(remote_root: &str) -> Result<Vec<Classification>> {
let output = Command::new("rclone")
.args(["lsjson", remote_root, "--dirs-only"])
.output()
.with_context(|| format!("invoke rclone lsjson {remote_root}"))?;
if !output.status.success() {
return Err(anyhow!(
"rclone lsjson failed ({}): {}",
output.status,
String::from_utf8_lossy(&output.stderr)
));
}
let entries: Vec<RcloneEntry> =
serde_json::from_slice(&output.stdout).context("parse rclone lsjson output")?;
let mut out: Vec<Classification> = Vec::new();
for e in entries {
if !e.is_dir {
continue;
}
// For remote folders we can't classify without download — emit
// a stub Classification keyed on the remote path. The wizard
// is responsible for `rclone copy`-ing the candidate to a
// staging dir and then re-running `classify` locally.
let pseudo = PathBuf::from(format!("{}/{}", remote_root.trim_end_matches('/'), e.name));
out.push(Classification {
path: pseudo.display().to_string(),
verdict: crate::classify::Verdict::Ambiguous,
score: 0,
primary_lang: "unknown".to_string(),
markers: Vec::new(),
});
}
out.sort_by(|a, b| a.path.cmp(&b.path));
Ok(out)
}