Skip to main content

entrenar/cli/commands/research/
export.rs

1//! Research export subcommand
2
3use crate::cli::logging::log;
4use crate::cli::LogLevel;
5use crate::config::{ExportArgs, ExportFormat};
6use crate::research::{AnonymizationConfig, LiterateDocument, NotebookExporter, ResearchArtifact};
7use std::path::Path;
8
9pub fn run_research_export(args: ExportArgs, level: LogLevel) -> Result<(), String> {
10    log(
11        level,
12        LogLevel::Normal,
13        &format!("Exporting: {} -> {}", args.input.display(), args.output.display()),
14    );
15
16    match args.format {
17        ExportFormat::Notebook => export_notebook(&args.input, &args.output, level),
18        ExportFormat::Html => export_html(&args.input, &args.output, level),
19        ExportFormat::AnonymizedJson => export_anonymized_json(&args, level),
20        ExportFormat::RoCrate => {
21            Err("Use 'entrenar research bundle' for RO-Crate export".to_string())
22        }
23    }
24}
25
26/// Export a literate document as a Jupyter notebook
27fn export_notebook(input: &Path, output: &Path, level: LogLevel) -> Result<(), String> {
28    let content =
29        std::fs::read_to_string(input).map_err(|e| format!("Failed to read input: {e}"))?;
30
31    let doc = LiterateDocument::parse_markdown(&content);
32    let notebook = NotebookExporter::from_literate(&doc);
33
34    let ipynb = notebook.to_ipynb();
35    std::fs::write(output, &ipynb).map_err(|e| format!("Failed to write notebook: {e}"))?;
36
37    log(level, LogLevel::Normal, &format!("Notebook exported: {} cells", notebook.cell_count()));
38    Ok(())
39}
40
41/// Export a literate document as HTML
42fn export_html(input: &Path, output: &Path, level: LogLevel) -> Result<(), String> {
43    let content =
44        std::fs::read_to_string(input).map_err(|e| format!("Failed to read input: {e}"))?;
45
46    let doc = LiterateDocument::parse_markdown(&content);
47    let html = doc.to_html();
48
49    std::fs::write(output, &html).map_err(|e| format!("Failed to write HTML: {e}"))?;
50
51    log(level, LogLevel::Normal, "HTML exported successfully");
52    Ok(())
53}
54
55/// Export a research artifact as anonymized JSON
56fn export_anonymized_json(args: &ExportArgs, level: LogLevel) -> Result<(), String> {
57    if !args.anonymize {
58        return Err("--anonymize flag required for anonymized export".to_string());
59    }
60
61    let salt = args.anon_salt.as_ref().ok_or("--anon-salt required for anonymization")?;
62
63    let yaml = std::fs::read_to_string(&args.input)
64        .map_err(|e| format!("Failed to read artifact: {e}"))?;
65
66    let artifact: ResearchArtifact =
67        serde_yaml::from_str(&yaml).map_err(|e| format!("Failed to parse artifact: {e}"))?;
68
69    let config = AnonymizationConfig::new(salt);
70    let anon = config.anonymize(&artifact);
71
72    let json = serde_json::to_string_pretty(&anon).map_err(|e| format!("JSON error: {e}"))?;
73
74    std::fs::write(&args.output, &json).map_err(|e| format!("Failed to write JSON: {e}"))?;
75
76    log(level, LogLevel::Normal, &format!("Anonymized artifact: {}", anon.anonymous_id));
77    Ok(())
78}