From a6c1863f57b9be37747576b6669dac39f0de1c2e Mon Sep 17 00:00:00 2001 From: mow Date: Sun, 8 Mar 2026 17:41:05 +0100 Subject: [PATCH] refactor(core): reorganize module structure --- core/examples/cli.rs | 10 ++-- core/src/document/command.rs | 50 ++-------------- core/src/document/format.rs | 38 ------------- core/src/document/loader.rs | 57 +++++++++++++++---- core/src/document/mod.rs | 21 +++---- core/src/document/model/mod.rs | 12 ++++ core/src/document/{ => model}/portable.rs | 4 +- core/src/document/{ => model}/raster.rs | 2 +- core/src/document/{ => model}/vector.rs | 4 +- core/src/document/session/command.rs | 45 +++++++++++++++ .../document/{session.rs => session/data.rs} | 8 ++- core/src/document/session/mod.rs | 11 ++++ core/src/document/{ => session}/store.rs | 24 ++++++-- core/src/document/types/image.rs | 17 ++++++ .../src/document/{core.rs => types/layout.rs} | 29 +++------- core/src/document/types/mod.rs | 12 ++++ core/src/document/types/viewbox.rs | 16 ++++++ core/src/{error/kinds.rs => error.rs} | 14 ++--- core/src/error/mod.rs | 8 --- core/src/lib.rs | 8 ++- core/src/render/mod.rs | 1 - core/src/render/page.rs | 4 -- 22 files changed, 231 insertions(+), 164 deletions(-) delete mode 100644 core/src/document/format.rs create mode 100644 core/src/document/model/mod.rs rename core/src/document/{ => model}/portable.rs (76%) rename core/src/document/{ => model}/raster.rs (87%) rename core/src/document/{ => model}/vector.rs (76%) create mode 100644 core/src/document/session/command.rs rename core/src/document/{session.rs => session/data.rs} (92%) create mode 100644 core/src/document/session/mod.rs rename core/src/document/{ => session}/store.rs (70%) create mode 100644 core/src/document/types/image.rs rename core/src/document/{core.rs => types/layout.rs} (63%) create mode 100644 core/src/document/types/mod.rs create mode 100644 core/src/document/types/viewbox.rs rename core/src/{error/kinds.rs => error.rs} (74%) delete mode 100644 core/src/error/mod.rs delete mode 100644 core/src/render/page.rs diff --git a/core/examples/cli.rs b/core/examples/cli.rs index 4d45b5c..cf4304e 100644 --- a/core/examples/cli.rs +++ b/core/examples/cli.rs @@ -4,9 +4,9 @@ // CLI test harness for the noctua_core document foundation. use clap::{ArgGroup, Parser}; -use noctua_core::document::command::SessionCommand; use noctua_core::document::manager::{DocumentManager, DocumentState}; -use noctua_core::document::session::CollectionKind; +use noctua_core::document::session::command::SessionCommand; +use noctua_core::document::session::data::CollectionKind; use std::path::PathBuf; // ── ANSI helpers ── @@ -139,7 +139,7 @@ fn main() { exec( &mut mgr, - &format!("New session '{session_name}'"), + &format!("New browser session '{session_name}'"), SessionCommand::New { name: session_name.clone(), kind: CollectionKind::DirectoryBrowser, @@ -154,7 +154,7 @@ fn main() { ); exec( &mut mgr, - &format!("Scan directory {}", dir.display()), + &format!("Add directory {}", dir.display()), SessionCommand::AddDirectory { dir }, ); exec( @@ -186,7 +186,7 @@ fn main() { ); exec( &mut mgr, - &format!("Scan directory {}", path.display()), + &format!("Add directory {}", path.display()), SessionCommand::AddDirectory { dir: path }, ); } diff --git a/core/src/document/command.rs b/core/src/document/command.rs index 01ce88e..7fe0ee1 100644 --- a/core/src/document/command.rs +++ b/core/src/document/command.rs @@ -1,51 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later // src/document/command.rs // -// Command enums for session management and document operations. +// Commands that operate on the active document itself. -use crate::document::session::CollectionKind; -use std::path::PathBuf; - -/// Commands operating on sessions and their document collections. -/// All collection-mutating commands operate on the active session -/// unless they explicitly name a session. -#[derive(Debug, Clone)] -pub enum SessionCommand { - // -- Lifecycle -- - /// Create a new empty session of the given kind. - New { name: String, kind: CollectionKind }, - /// Open sessions from an existing .ron file. - Open { path: PathBuf }, - /// Switch the active session by name. - Select { name: String }, - - // -- Collection management -- - /// Scan a directory and replace the active session's items. - AddDirectory { dir: PathBuf }, - /// Add a single document to the active session. - AddDocument { path: PathBuf }, - /// Copy a document from one session to another. - CopyDocument { - source: String, - target: String, - source_index: usize, - target_index: usize, - }, - /// Remove a document by index from the active session. - RemoveDocument { index: usize }, - - // -- Navigation -- - /// Navigate to the next document in the active session. - NextDocument, - /// Navigate to the previous document in the active session. - PreviousDocument, - /// Select a document by index in the active session. - SelectDocument { index: usize }, -} - -/// Commands operating on the active document itself. -/// Reserved for future use (rotate, flip, zoom, export, …). +/// Commands operating on the active document (zoom, export, annotate, …). +/// +/// The `DocumentManager` dispatches these after resolving which document is +/// active – the UI never needs to know the underlying format. #[derive(Debug, Clone)] pub enum DocumentCommand { - // Future: Rotate, Flip, Zoom, Export, ... + // Reserved for future use: Rotate, Flip, Zoom, Export, Annotate, … } diff --git a/core/src/document/format.rs b/core/src/document/format.rs deleted file mode 100644 index 1303f6f..0000000 --- a/core/src/document/format.rs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// src/document/format.rs -// -// Single source of truth for document format detection. - -use std::path::Path; - -/// The three document formats Noctua understands. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DocumentFormat { - /// Raster images – anything libcosmic's image crate can decode. - Raster, - /// Vector graphics (SVG). - Vector, - /// Portable documents (PDF). - Portable, -} - -/// Detects the document format from the file extension. -/// -/// Returns `None` for unknown extensions so directory scans can skip -/// unsupported files early. At load time the underlying library -/// (ImageReader, usvg, pdfium) is the final authority. -pub fn detect_format(path: &Path) -> Option { - match path - .extension() - .and_then(|e| e.to_str()) - .map(|e| e.to_lowercase()) - .as_deref() - { - Some("pdf") => Some(DocumentFormat::Portable), - Some("svg") => Some(DocumentFormat::Vector), - Some("png" | "jpg" | "jpeg" | "webp" | "gif" | "bmp" | "tiff" | "tif") => { - Some(DocumentFormat::Raster) - } - _ => None, - } -} diff --git a/core/src/document/loader.rs b/core/src/document/loader.rs index 90f0a48..c94dc31 100644 --- a/core/src/document/loader.rs +++ b/core/src/document/loader.rs @@ -1,21 +1,56 @@ // SPDX-License-Identifier: GPL-3.0-or-later // src/document/loader.rs // -// Document loading: format dispatch and directory scanning. +// Document loading: format detection, format dispatch, and directory scanning. -use crate::document::format::{detect_format, DocumentFormat}; -use crate::document::portable::PortableModel; -use crate::document::raster::RasterModel; -use crate::document::session::{SessionData, SessionItem}; -use crate::document::vector::VectorModel; -use crate::document::{core::PageLayout, DocumentContent}; +use crate::document::model::{PortableModel, RasterModel, VectorModel}; +use crate::document::session::data::{SessionData, SessionItem}; +use crate::document::types::PageLayout; +use crate::document::DocumentContent; use crate::error::{Error, Result}; use std::path::Path; +/// The three document formats Noctua understands. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum DocumentFormat { + /// Raster images – anything the image crate can decode. + Raster, + /// Vector graphics (SVG). + Vector, + /// Portable documents (PDF). + Portable, +} + +/// Detects the document format from the file extension. +/// +/// Returns `None` for unknown extensions so directory scans can skip +/// unsupported files early. At load time the underlying library +/// (`ImageReader`, `usvg`, `pdfium`) is the final authority. +fn detect_format(path: &Path) -> Option { + match path + .extension() + .and_then(|e| e.to_str()) + .map(str::to_lowercase) + .as_deref() + { + Some("pdf") => Some(DocumentFormat::Portable), + Some("svg") => Some(DocumentFormat::Vector), + Some("png" | "jpg" | "jpeg" | "webp" | "gif" | "bmp" | "tiff" | "tif") => { + Some(DocumentFormat::Raster) + } + _ => None, + } +} + /// Loads a document from a path and returns its content. -pub fn load_document(path: &Path) -> Result { +/// +/// # Errors +/// +/// Returns [`Error::NotFound`] if the path does not exist. +/// Returns [`Error::Unsupported`] if the file extension is not recognised. +pub(crate) fn load_document(path: &Path) -> Result { if !path.exists() { - return Err(Error::NotFound(format!("{:?}", path))); + return Err(Error::NotFound(format!("{}", path.display()))); } match detect_format(path) { @@ -51,10 +86,10 @@ pub fn load_document(path: &Path) -> Result { } /// Scans a directory for supported files and populates the session. -pub fn scan_directory_into(session: &mut SessionData, dir: &Path) { +pub(crate) fn scan_directory_into(session: &mut SessionData, dir: &Path) { if let Ok(entries) = std::fs::read_dir(dir) { let mut paths: Vec<_> = entries - .filter_map(|e| e.ok()) + .filter_map(std::result::Result::ok) .map(|e| e.path()) .filter(|p| p.is_file() && detect_format(p).is_some()) .collect(); diff --git a/core/src/document/mod.rs b/core/src/document/mod.rs index 5954763..c719136 100644 --- a/core/src/document/mod.rs +++ b/core/src/document/mod.rs @@ -1,25 +1,22 @@ // SPDX-License-Identifier: GPL-3.0-or-later // src/document/mod.rs // -// Document abstraction and format definitions. +// Document abstraction – the public surface of the document subsystem. pub mod command; -pub mod core; -pub mod format; pub mod loader; pub mod manager; -pub mod portable; -pub mod raster; +pub mod model; pub mod session; -pub mod store; -pub mod vector; +pub mod types; -use self::portable::PortableModel; -use self::raster::RasterModel; -use self::vector::VectorModel; +use self::model::{PortableModel, RasterModel, VectorModel}; -/// Represents the physical content of a loaded document. -#[derive(Debug, Clone)] +/// The decoded content of a loaded document. +/// +/// This enum is internal to `noctua_core`. The UI only ever sees a `RawImage` +/// produced by the render subsystem – it never matches on this type. +#[derive(Debug)] pub enum DocumentContent { Raster(RasterModel), Vector(VectorModel), diff --git a/core/src/document/model/mod.rs b/core/src/document/model/mod.rs new file mode 100644 index 0000000..7ede092 --- /dev/null +++ b/core/src/document/model/mod.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// src/document/model/mod.rs +// +// The three concrete document models Noctua understands. + +pub mod portable; +pub mod raster; +pub mod vector; + +pub use portable::PortableModel; +pub use raster::RasterModel; +pub use vector::VectorModel; diff --git a/core/src/document/portable.rs b/core/src/document/model/portable.rs similarity index 76% rename from core/src/document/portable.rs rename to core/src/document/model/portable.rs index e7ef528..cdcdc43 100644 --- a/core/src/document/portable.rs +++ b/core/src/document/model/portable.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// src/document/portable.rs +// src/document/model/portable.rs // // Portable document format model (PDF). -use crate::document::core::PageLayout; +use crate::document::types::PageLayout; use std::path::PathBuf; #[derive(Clone, Debug)] diff --git a/core/src/document/raster.rs b/core/src/document/model/raster.rs similarity index 87% rename from core/src/document/raster.rs rename to core/src/document/model/raster.rs index 5000acd..83228bc 100644 --- a/core/src/document/raster.rs +++ b/core/src/document/model/raster.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// src/document/raster.rs +// src/document/model/raster.rs // // Raster image document model (JPEG, PNG, etc.). diff --git a/core/src/document/vector.rs b/core/src/document/model/vector.rs similarity index 76% rename from core/src/document/vector.rs rename to core/src/document/model/vector.rs index 5c29ad3..15e9bf9 100644 --- a/core/src/document/vector.rs +++ b/core/src/document/model/vector.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// src/document/vector.rs +// src/document/model/vector.rs // // Vector image document model (SVG). -use crate::document::core::ViewBox; +use crate::document::types::ViewBox; use std::path::PathBuf; #[derive(Clone, Debug)] diff --git a/core/src/document/session/command.rs b/core/src/document/session/command.rs new file mode 100644 index 0000000..0b141e4 --- /dev/null +++ b/core/src/document/session/command.rs @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// src/document/session/command.rs +// +// Commands that operate on sessions and their document collections. + +use crate::document::session::data::CollectionKind; +use std::path::PathBuf; + +/// Commands operating on sessions and their document collections. +/// +/// All collection-mutating commands operate on the active session +/// unless they explicitly name a session. +#[derive(Debug, Clone)] +pub enum SessionCommand { + // Lifecycle + /// Create a new empty session of the given kind. + New { name: String, kind: CollectionKind }, + /// Open sessions from an existing .ron file. + Open { path: PathBuf }, + /// Switch the active session by name. + Select { name: String }, + + // Collection management + /// Scan a directory and replace the active session's items. + AddDirectory { dir: PathBuf }, + /// Add a single document to the active session. + AddDocument { path: PathBuf }, + /// Copy a document from one session to another. + CopyDocument { + source: String, + target: String, + source_index: usize, + target_index: usize, + }, + /// Remove a document by index from the active session. + RemoveDocument { index: usize }, + + // Navigation + /// Navigate to the next document in the active session. + NextDocument, + /// Navigate to the previous document in the active session. + PreviousDocument, + /// Select a document by index in the active session. + SelectDocument { index: usize }, +} diff --git a/core/src/document/session.rs b/core/src/document/session/data.rs similarity index 92% rename from core/src/document/session.rs rename to core/src/document/session/data.rs index 8dd49e9..157982e 100644 --- a/core/src/document/session.rs +++ b/core/src/document/session/data.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// src/document/session.rs +// src/document/session/data.rs // // A session is the unit of work in Noctua. It groups a set of document items // under a name and tracks which item is currently active. @@ -42,6 +42,7 @@ pub struct SessionItem { } impl SessionItem { + #[must_use] pub fn new(path: PathBuf, page_index: usize) -> Self { Self { path, page_index } } @@ -52,8 +53,8 @@ impl SessionItem { pub struct SessionData { pub name: String, pub kind: CollectionKind, - /// For DirectoryBrowser: the source directory to scan. - /// For DocumentCollection: None (items are managed individually). + /// For `DirectoryBrowser`: the source directory to scan. + /// For `DocumentCollection`: None (items are managed individually). /// The .ron session file path is tracked separately. #[serde(default)] pub path: Option, @@ -63,6 +64,7 @@ pub struct SessionData { } impl SessionData { + #[must_use] pub fn new( name: &str, kind: CollectionKind, diff --git a/core/src/document/session/mod.rs b/core/src/document/session/mod.rs new file mode 100644 index 0000000..1e82946 --- /dev/null +++ b/core/src/document/session/mod.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// src/document/session/mod.rs +// +// Session subsystem: data model, commands, and persistence. + +pub mod command; +pub mod data; +pub mod store; + +pub use command::SessionCommand; +pub use data::{CollectionKind, SessionData, SessionItem}; diff --git a/core/src/document/store.rs b/core/src/document/session/store.rs similarity index 70% rename from core/src/document/store.rs rename to core/src/document/session/store.rs index 70049c6..480728f 100644 --- a/core/src/document/store.rs +++ b/core/src/document/session/store.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// src/document/store.rs +// src/document/session/store.rs // // RON-based persistence for session data: load and save session lists. // @@ -27,23 +27,37 @@ // ), // ] -use crate::document::session::SessionData; +use crate::document::session::data::SessionData; use crate::error::{Error, Result}; use std::path::Path; /// Loads all sessions from a RON file. +/// +/// # Errors +/// +/// Returns [`Error::Io`] if the file cannot be read. +/// Returns [`Error::Session`] if the file cannot be parsed. pub fn load_sessions(path: &Path) -> Result> { let content = std::fs::read_to_string(path).map_err(Error::Io)?; - ron::from_str(&content) - .map_err(|e| Error::Session(format!("Failed to parse session file {:?}: {}", path, e))) + ron::from_str(&content).map_err(|e| { + Error::Session(format!( + "Failed to parse session file {}: {e}", + path.display() + )) + }) } /// Saves all sessions to a RON file. +/// +/// # Errors +/// +/// Returns [`Error::Session`] if the sessions cannot be serialized. +/// Returns [`Error::Io`] if the file cannot be written. #[allow(dead_code)] pub fn save_sessions(path: &Path, sessions: &[SessionData]) -> Result<()> { let content = ron::ser::to_string_pretty(sessions, ron::ser::PrettyConfig::default()) - .map_err(|e| Error::Session(format!("Failed to serialize sessions: {}", e)))?; + .map_err(|e| Error::Session(format!("Failed to serialize sessions: {e}")))?; std::fs::write(path, content).map_err(Error::Io) } diff --git a/core/src/document/types/image.rs b/core/src/document/types/image.rs new file mode 100644 index 0000000..3791631 --- /dev/null +++ b/core/src/document/types/image.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// src/document/types/image.rs +// +// Raw pixel buffer passed from the render subsystem to the UI. + +use std::sync::Arc; + +/// A decoded, format-independent pixel buffer ready for display. +/// +/// The UI receives this type from the `DocumentManager` and never needs to +/// know which document format (PDF, SVG, raster) produced it. +#[derive(Clone, Debug)] +pub struct RawImage { + pub data: Arc>, + pub width: u32, + pub height: u32, +} diff --git a/core/src/document/core.rs b/core/src/document/types/layout.rs similarity index 63% rename from core/src/document/core.rs rename to core/src/document/types/layout.rs index 9782d18..e86628d 100644 --- a/core/src/document/core.rs +++ b/core/src/document/types/layout.rs @@ -1,17 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// src/document/core.rs +// src/document/types/layout.rs // -// Core domain models for document representation. - -use std::sync::Arc; - -#[derive(Clone, Debug)] -pub struct RawImage { - pub data: Arc>, - pub width: u32, - pub height: u32, -} +// PageLayout – describes the vertical arrangement of pages in a document. +/// The computed layout of all pages in a multi-page document. +/// +/// Stores absolute y-offsets for each page so the render subsystem can +/// determine which pages are visible at a given scroll position without +/// iterating the full list on every frame. #[derive(Clone, Debug)] pub struct PageLayout { pub page_starts: Vec, @@ -21,15 +17,8 @@ pub struct PageLayout { pub max_width: f64, } -#[derive(Clone, Debug)] -pub struct ViewBox { - pub x: f64, - pub y: f64, - pub width: f64, - pub height: f64, -} - impl PageLayout { + #[must_use] pub fn new(page_sizes: Vec<(f64, f64)>, gap: f64) -> Self { let mut page_starts = Vec::with_capacity(page_sizes.len()); let mut y = 0.0_f64; @@ -43,7 +32,7 @@ impl PageLayout { } } - let total_height = if !page_sizes.is_empty() { y - gap } else { 0.0 }; + let total_height = if page_sizes.is_empty() { 0.0 } else { y - gap }; Self { page_starts, diff --git a/core/src/document/types/mod.rs b/core/src/document/types/mod.rs new file mode 100644 index 0000000..eae29b1 --- /dev/null +++ b/core/src/document/types/mod.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// src/document/types/mod.rs +// +// Shared domain primitives used across the document and render subsystems. + +pub mod image; +pub mod layout; +pub mod viewbox; + +pub use image::RawImage; +pub use layout::PageLayout; +pub use viewbox::ViewBox; diff --git a/core/src/document/types/viewbox.rs b/core/src/document/types/viewbox.rs new file mode 100644 index 0000000..45715c6 --- /dev/null +++ b/core/src/document/types/viewbox.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// src/document/types/viewbox.rs +// +// ViewBox – describes the visible coordinate space of a vector document. + +/// The coordinate space declared by an SVG `viewBox` attribute. +/// +/// Used by the render subsystem to map document coordinates to screen pixels +/// without exposing SVG internals to the UI. +#[derive(Clone, Debug)] +pub struct ViewBox { + pub x: f64, + pub y: f64, + pub width: f64, + pub height: f64, +} diff --git a/core/src/error/kinds.rs b/core/src/error.rs similarity index 74% rename from core/src/error/kinds.rs rename to core/src/error.rs index 8f11344..c17566b 100644 --- a/core/src/error/kinds.rs +++ b/core/src/error.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// src/error/kinds.rs +// src/error.rs // -// Central error types for Noctua. +// Central error and result types for noctua_core. use std::fmt; @@ -24,11 +24,11 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::NotFound(msg) => write!(f, "Not found: {}", msg), - Error::Unsupported(msg) => write!(f, "Unsupported: {}", msg), - Error::Render(msg) => write!(f, "Render error: {}", msg), - Error::Session(msg) => write!(f, "Session error: {}", msg), - Error::Io(err) => write!(f, "I/O error: {}", err), + Error::NotFound(msg) => write!(f, "Not found: {msg}"), + Error::Unsupported(msg) => write!(f, "Unsupported: {msg}"), + Error::Render(msg) => write!(f, "Render error: {msg}"), + Error::Session(msg) => write!(f, "Session error: {msg}"), + Error::Io(err) => write!(f, "I/O error: {err}"), } } } diff --git a/core/src/error/mod.rs b/core/src/error/mod.rs deleted file mode 100644 index c488562..0000000 --- a/core/src/error/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// src/error/mod.rs -// -// Central error module – re-exports the shared Error and Result types. - -mod kinds; - -pub use kinds::{Error, Result}; diff --git a/core/src/lib.rs b/core/src/lib.rs index 9a4f59b..b337937 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -3,6 +3,12 @@ // // Public API of the noctua_core library. -pub mod document; pub mod error; + +pub mod document; pub mod render; + +pub use document::command::DocumentCommand; +pub use document::manager::DocumentManager; +pub use document::session::SessionCommand; +pub use document::types::RawImage; diff --git a/core/src/render/mod.rs b/core/src/render/mod.rs index 9424162..c13e7ba 100644 --- a/core/src/render/mod.rs +++ b/core/src/render/mod.rs @@ -4,5 +4,4 @@ // Rendering subsystem: converts document content into displayable output. pub mod converter; -pub mod page; pub mod thumbnail; diff --git a/core/src/render/page.rs b/core/src/render/page.rs deleted file mode 100644 index 5242cbe..0000000 --- a/core/src/render/page.rs +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// src/render/page.rs -// -// Page layout and rendering primitives (scaffold – not yet implemented).