Skip to main content

ankit_engine/
lib.rs

1//! High-level workflow operations for Anki via AnkiConnect.
2//!
3//! This crate provides ergonomic, high-level operations built on top of the
4//! [`ankit`] client library. While `ankit` provides 1:1 API bindings, `ankit-engine`
5//! combines multiple API calls into cohesive workflows.
6//!
7//! # Quick Start
8//!
9//! ```no_run
10//! use ankit_engine::Engine;
11//!
12//! # async fn example() -> ankit_engine::Result<()> {
13//! let engine = Engine::new();
14//!
15//! // High-level workflows
16//! let stats = engine.analyze().study_summary("Japanese", 30).await?;
17//! println!("Cards reviewed: {}", stats.total_reviews);
18//!
19//! // Direct client access when needed
20//! let version = engine.client().misc().version().await?;
21//! # Ok(())
22//! # }
23//! ```
24//!
25//! # Feature Flags
26//!
27//! All workflow modules are enabled by default. Disable with:
28//!
29//! ```toml
30//! [dependencies]
31//! ankit-engine = { version = "0.1", default-features = false, features = ["analyze"] }
32//! ```
33//!
34//! Available features:
35//! - `import` - Bulk import with duplicate handling
36//! - `export` - Deck and review history export
37//! - `organize` - Deck cloning, merging, reorganization
38//! - `analyze` - Study statistics and problem card detection
39//! - `migrate` - Note type migration with field mapping
40//! - `media` - Media audit and cleanup
41//! - `progress` - Card state management and performance tagging
42//! - `enrich` - Find and update notes with empty fields
43//! - `deduplicate` - Duplicate detection and removal
44//! - `backup` - Deck backup and restore to .apkg files
45//! - `search` - Content search helpers (always enabled)
46
47mod error;
48pub mod search;
49
50#[cfg(feature = "analyze")]
51pub mod analyze;
52
53#[cfg(feature = "export")]
54pub mod export;
55
56#[cfg(feature = "import")]
57pub mod import;
58
59#[cfg(feature = "media")]
60pub mod media;
61
62#[cfg(feature = "migrate")]
63pub mod migrate;
64
65#[cfg(feature = "organize")]
66pub mod organize;
67
68#[cfg(feature = "progress")]
69pub mod progress;
70
71#[cfg(feature = "enrich")]
72pub mod enrich;
73
74#[cfg(feature = "deduplicate")]
75pub mod deduplicate;
76
77#[cfg(feature = "backup")]
78pub mod backup;
79
80pub use error::{Error, Result};
81
82// Re-export ankit types for convenience
83pub use ankit::{
84    AnkiClient, CanAddResult, CardAnswer, CardInfo, CardModTime, CardTemplate, ClientBuilder,
85    CreateModelParams, DeckConfig, DeckStats, DuplicateScope, Ease, FieldFont, FindReplaceParams,
86    LapseConfig, MediaAttachment, ModelField, ModelStyling, NewCardConfig, Note, NoteBuilder,
87    NoteField, NoteInfo, NoteModTime, NoteOptions, ReviewConfig, StoreMediaParams,
88};
89
90#[cfg(feature = "analyze")]
91use analyze::AnalyzeEngine;
92
93#[cfg(feature = "export")]
94use export::ExportEngine;
95
96#[cfg(feature = "import")]
97use import::ImportEngine;
98
99#[cfg(feature = "media")]
100use media::MediaEngine;
101
102#[cfg(feature = "migrate")]
103use migrate::MigrateEngine;
104
105#[cfg(feature = "organize")]
106use organize::OrganizeEngine;
107
108#[cfg(feature = "progress")]
109use progress::ProgressEngine;
110
111#[cfg(feature = "enrich")]
112use enrich::EnrichEngine;
113
114#[cfg(feature = "deduplicate")]
115use deduplicate::DeduplicateEngine;
116
117#[cfg(feature = "backup")]
118use backup::BackupEngine;
119
120use search::SearchEngine;
121
122/// High-level workflow engine for Anki operations.
123///
124/// The engine wraps an [`AnkiClient`] and provides access to workflow modules
125/// that combine multiple API calls into cohesive operations.
126///
127/// # Example
128///
129/// ```no_run
130/// use ankit_engine::Engine;
131///
132/// # async fn example() -> ankit_engine::Result<()> {
133/// // Create with default client settings
134/// let engine = Engine::new();
135///
136/// // Or with a custom client
137/// let client = ankit_engine::AnkiClient::builder()
138///     .url("http://localhost:8765")
139///     .build();
140/// let engine = Engine::from_client(client);
141///
142/// // Access workflow modules
143/// let stats = engine.analyze().study_summary("Default", 7).await?;
144/// # Ok(())
145/// # }
146/// ```
147#[derive(Debug, Clone)]
148pub struct Engine {
149    client: AnkiClient,
150}
151
152impl Engine {
153    /// Create a new engine with default client settings.
154    ///
155    /// Connects to AnkiConnect at `http://127.0.0.1:8765`.
156    pub fn new() -> Self {
157        Self {
158            client: AnkiClient::new(),
159        }
160    }
161
162    /// Create an engine from an existing client.
163    pub fn from_client(client: AnkiClient) -> Self {
164        Self { client }
165    }
166
167    /// Get a reference to the underlying client.
168    ///
169    /// Use this for direct API access when workflows don't cover your use case.
170    pub fn client(&self) -> &AnkiClient {
171        &self.client
172    }
173
174    /// Access import workflows.
175    ///
176    /// Provides bulk import with duplicate detection and conflict resolution.
177    #[cfg(feature = "import")]
178    pub fn import(&self) -> ImportEngine<'_> {
179        ImportEngine::new(&self.client)
180    }
181
182    /// Access export workflows.
183    ///
184    /// Provides deck export and review history extraction.
185    #[cfg(feature = "export")]
186    pub fn export(&self) -> ExportEngine<'_> {
187        ExportEngine::new(&self.client)
188    }
189
190    /// Access organization workflows.
191    ///
192    /// Provides deck cloning, merging, and tag-based reorganization.
193    #[cfg(feature = "organize")]
194    pub fn organize(&self) -> OrganizeEngine<'_> {
195        OrganizeEngine::new(&self.client)
196    }
197
198    /// Access analysis workflows.
199    ///
200    /// Provides study statistics and problem card (leech) detection.
201    #[cfg(feature = "analyze")]
202    pub fn analyze(&self) -> AnalyzeEngine<'_> {
203        AnalyzeEngine::new(&self.client)
204    }
205
206    /// Access migration workflows.
207    ///
208    /// Provides note type migration with field mapping.
209    #[cfg(feature = "migrate")]
210    pub fn migrate(&self) -> MigrateEngine<'_> {
211        MigrateEngine::new(&self.client)
212    }
213
214    /// Access media workflows.
215    ///
216    /// Provides media audit and cleanup operations.
217    #[cfg(feature = "media")]
218    pub fn media(&self) -> MediaEngine<'_> {
219        MediaEngine::new(&self.client)
220    }
221
222    /// Access progress management workflows.
223    ///
224    /// Provides card state management, performance tagging, and bulk operations.
225    #[cfg(feature = "progress")]
226    pub fn progress(&self) -> ProgressEngine<'_> {
227        ProgressEngine::new(&self.client)
228    }
229
230    /// Access enrichment workflows.
231    ///
232    /// Provides tools for finding notes with empty fields and updating them.
233    #[cfg(feature = "enrich")]
234    pub fn enrich(&self) -> EnrichEngine<'_> {
235        EnrichEngine::new(&self.client)
236    }
237
238    /// Access deduplication workflows.
239    ///
240    /// Provides duplicate detection and removal based on key fields.
241    #[cfg(feature = "deduplicate")]
242    pub fn deduplicate(&self) -> DeduplicateEngine<'_> {
243        DeduplicateEngine::new(&self.client)
244    }
245
246    /// Access backup and restore workflows.
247    ///
248    /// Provides deck backup to .apkg files and restore operations.
249    #[cfg(feature = "backup")]
250    pub fn backup(&self) -> BackupEngine<'_> {
251        BackupEngine::new(&self.client)
252    }
253
254    /// Access content search helpers.
255    ///
256    /// Provides simplified search methods that return full note info
257    /// instead of just IDs, abstracting away Anki query syntax.
258    ///
259    /// # Example
260    ///
261    /// ```no_run
262    /// # use ankit_engine::Engine;
263    /// # async fn example() -> ankit_engine::Result<()> {
264    /// let engine = Engine::new();
265    ///
266    /// // Search for text in any field
267    /// let notes = engine.search().text("conjugation", Some("Japanese")).await?;
268    ///
269    /// // Search in a specific field
270    /// let notes = engine.search().field("Front", "hello", None).await?;
271    /// # Ok(())
272    /// # }
273    /// ```
274    pub fn search(&self) -> SearchEngine<'_> {
275        SearchEngine::new(&self.client)
276    }
277}
278
279impl Default for Engine {
280    fn default() -> Self {
281        Self::new()
282    }
283}