jtool_notebook/
cell.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::time::Duration;
4
5use crate::timing::ExecutionTiming;
6
7/// A notebook cell
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(tag = "cell_type")]
10pub enum Cell {
11    /// Code cell
12    #[serde(rename = "code")]
13    Code(CodeCell),
14    /// Markdown cell
15    #[serde(rename = "markdown")]
16    Markdown(MarkdownCell),
17    /// Raw cell
18    #[serde(rename = "raw")]
19    Raw(RawCell),
20}
21
22impl Cell {
23    /// Get cell source as unified string
24    pub fn source(&self) -> &Source {
25        match self {
26            Cell::Code(c) => &c.source,
27            Cell::Markdown(m) => &m.source,
28            Cell::Raw(r) => &r.source,
29        }
30    }
31
32    /// Get mutable source reference
33    pub fn source_mut(&mut self) -> &mut Source {
34        match self {
35            Cell::Code(c) => &mut c.source,
36            Cell::Markdown(m) => &mut m.source,
37            Cell::Raw(r) => &mut r.source,
38        }
39    }
40
41    /// Get cell metadata
42    pub fn metadata(&self) -> &CellMetadata {
43        match self {
44            Cell::Code(c) => &c.metadata,
45            Cell::Markdown(m) => &m.metadata,
46            Cell::Raw(r) => &r.metadata,
47        }
48    }
49
50    /// Check if cell is code cell
51    pub fn is_code(&self) -> bool {
52        matches!(self, Cell::Code(_))
53    }
54
55    /// Get cell type as string
56    pub fn cell_type(&self) -> &str {
57        match self {
58            Cell::Code(_) => "code",
59            Cell::Markdown(_) => "markdown",
60            Cell::Raw(_) => "raw",
61        }
62    }
63}
64
65/// Code cell with execution outputs
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct CodeCell {
68    /// Cell source code
69    pub source: Source,
70    /// Cell metadata
71    #[serde(default)]
72    pub metadata: CellMetadata,
73    /// Execution count (null if not executed, may be negative in some notebook implementations)
74    pub execution_count: Option<i32>,
75    /// Cell outputs
76    #[serde(default)]
77    pub outputs: Vec<Output>,
78}
79
80impl CodeCell {
81    /// Get execution timing from cell metadata
82    ///
83    /// JupyterLab 1.2+ can record timing information in cell metadata under the "execution" key.
84    /// Returns None if timing data is not present or cannot be parsed.
85    pub fn timing(&self) -> Option<ExecutionTiming> {
86        self.metadata
87            .extra
88            .get("execution")
89            .and_then(|v| serde_json::from_value(v.clone()).ok())
90    }
91
92    /// Get execution duration (total time from busy to idle)
93    ///
94    /// This is a convenience method that extracts timing and calculates duration.
95    pub fn duration(&self) -> Option<Duration> {
96        self.timing()?.total_duration()
97    }
98
99    /// Check if this cell has timing metadata
100    pub fn has_timing(&self) -> bool {
101        self.timing().map(|t| t.has_timing()).unwrap_or(false)
102    }
103}
104
105/// Markdown cell
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct MarkdownCell {
108    /// Cell source (markdown text)
109    pub source: Source,
110    /// Cell metadata
111    #[serde(default)]
112    pub metadata: CellMetadata,
113}
114
115/// Raw cell
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct RawCell {
118    /// Cell source (raw text)
119    pub source: Source,
120    /// Cell metadata
121    #[serde(default)]
122    pub metadata: CellMetadata,
123}
124
125/// Cell source (string or lines)
126#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(untagged)]
128pub enum Source {
129    /// Single string
130    String(String),
131    /// Array of lines
132    Lines(Vec<String>),
133}
134
135impl Source {
136    /// Get as unified string
137    pub fn as_str(&self) -> String {
138        match self {
139            Source::String(s) => s.clone(),
140            Source::Lines(lines) => lines.join(""),
141        }
142    }
143
144    /// Get as lines (splitting if necessary)
145    pub fn as_lines(&self) -> Vec<String> {
146        match self {
147            Source::String(s) => s.lines().map(|l| l.to_string()).collect(),
148            Source::Lines(lines) => lines.clone(),
149        }
150    }
151}
152
153/// Cell metadata
154#[derive(Debug, Clone, Default, Serialize, Deserialize)]
155pub struct CellMetadata {
156    /// Cell is collapsed
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub collapsed: Option<bool>,
159    /// Custom tags
160    #[serde(default, skip_serializing_if = "Vec::is_empty")]
161    pub tags: Vec<String>,
162    /// Additional metadata
163    #[serde(flatten)]
164    pub extra: HashMap<String, serde_json::Value>,
165}
166
167/// Cell execution output
168#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(tag = "output_type")]
170pub enum Output {
171    /// Stream output (stdout/stderr)
172    #[serde(rename = "stream")]
173    Stream {
174        /// Stream name ("stdout" or "stderr")
175        name: String,
176        /// Output text
177        text: Source,
178    },
179
180    /// Display data (rich output)
181    #[serde(rename = "display_data")]
182    DisplayData {
183        /// MIME bundle (HTML, images, etc.)
184        data: HashMap<String, serde_json::Value>,
185        /// Output metadata
186        #[serde(default)]
187        metadata: serde_json::Value,
188    },
189
190    /// Execution result (cell return value)
191    #[serde(rename = "execute_result")]
192    ExecuteResult {
193        /// Execution count
194        execution_count: i32,
195        /// MIME bundle
196        data: HashMap<String, serde_json::Value>,
197        /// Output metadata
198        #[serde(default)]
199        metadata: serde_json::Value,
200    },
201
202    /// Error output
203    #[serde(rename = "error")]
204    Error {
205        /// Exception name
206        ename: String,
207        /// Exception value
208        evalue: String,
209        /// Traceback lines (with ANSI codes)
210        traceback: Vec<String>,
211    },
212}