use crate::error::{Result, SurqlError};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ColorScheme {
pub primary: &'static str,
pub secondary: &'static str,
pub background: &'static str,
pub text: &'static str,
pub accent: &'static str,
pub success: &'static str,
pub warning: &'static str,
pub error: &'static str,
pub muted: &'static str,
}
impl ColorScheme {
#[must_use]
pub const fn default_const() -> Self {
Self {
primary: "#6366f1",
secondary: "#ec4899",
background: "#f8fafc",
text: "#0f172a",
accent: "#8b5cf6",
success: "#10b981",
warning: "#f59e0b",
error: "#ef4444",
muted: "#94a3b8",
}
}
}
impl Default for ColorScheme {
fn default() -> Self {
Self::default_const()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GraphVizTheme {
pub node_color: &'static str,
pub edge_color: &'static str,
pub bg_color: &'static str,
pub font_name: &'static str,
pub node_shape: &'static str,
pub node_style: &'static str,
pub edge_style: &'static str,
pub use_gradients: bool,
pub use_clusters: bool,
}
impl GraphVizTheme {
#[must_use]
pub const fn default_const() -> Self {
Self {
node_color: "#6366f1",
edge_color: "#64748b",
bg_color: "transparent",
font_name: "Arial",
node_shape: "record",
node_style: "filled,rounded",
edge_style: "solid",
use_gradients: true,
use_clusters: false,
}
}
#[must_use]
pub const fn backward_compatible(self) -> Self {
Self {
use_gradients: false,
use_clusters: false,
..self
}
}
}
impl Default for GraphVizTheme {
fn default() -> Self {
Self::default_const()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MermaidTheme {
pub theme_name: &'static str,
pub primary_color: &'static str,
pub secondary_color: &'static str,
pub use_custom_css: bool,
}
impl MermaidTheme {
#[must_use]
pub const fn default_const() -> Self {
Self {
theme_name: "default",
primary_color: "#6366f1",
secondary_color: "#ec4899",
use_custom_css: true,
}
}
}
impl Default for MermaidTheme {
fn default() -> Self {
Self::default_const()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ASCIITheme {
pub box_style: &'static str,
pub use_unicode: bool,
pub use_colors: bool,
pub use_icons: bool,
pub color_scheme: &'static str,
}
impl ASCIITheme {
#[must_use]
pub const fn default_const() -> Self {
Self {
box_style: "rounded",
use_unicode: true,
use_colors: true,
use_icons: true,
color_scheme: "default",
}
}
}
impl Default for ASCIITheme {
fn default() -> Self {
Self::default_const()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Theme {
pub name: &'static str,
pub description: &'static str,
pub color_scheme: ColorScheme,
pub graphviz: GraphVizTheme,
pub mermaid: MermaidTheme,
pub ascii: ASCIITheme,
}
#[must_use]
pub const fn modern_color_scheme() -> ColorScheme {
ColorScheme {
primary: "#6366f1",
secondary: "#ec4899",
background: "#f8fafc",
text: "#0f172a",
accent: "#8b5cf6",
success: "#10b981",
warning: "#f59e0b",
error: "#ef4444",
muted: "#94a3b8",
}
}
#[must_use]
pub const fn modern_graphviz() -> GraphVizTheme {
GraphVizTheme {
node_color: "#6366f1",
edge_color: "#64748b",
bg_color: "transparent",
font_name: "Arial",
node_shape: "record",
node_style: "filled,rounded",
edge_style: "solid",
use_gradients: true,
use_clusters: false,
}
}
#[must_use]
pub const fn modern_mermaid() -> MermaidTheme {
MermaidTheme {
theme_name: "default",
primary_color: "#6366f1",
secondary_color: "#ec4899",
use_custom_css: true,
}
}
#[must_use]
pub const fn modern_ascii() -> ASCIITheme {
ASCIITheme {
box_style: "rounded",
use_unicode: true,
use_colors: true,
use_icons: true,
color_scheme: "default",
}
}
#[must_use]
pub fn modern_theme() -> Theme {
Theme {
name: "modern",
description: "Clean, professional design with indigo and pink accents",
color_scheme: modern_color_scheme(),
graphviz: modern_graphviz(),
mermaid: modern_mermaid(),
ascii: modern_ascii(),
}
}
#[must_use]
pub const fn dark_color_scheme() -> ColorScheme {
ColorScheme {
primary: "#8b5cf6",
secondary: "#d946ef",
background: "#1e1b4b",
text: "#f1f5f9",
accent: "#a78bfa",
success: "#34d399",
warning: "#fbbf24",
error: "#f87171",
muted: "#64748b",
}
}
#[must_use]
pub const fn dark_graphviz() -> GraphVizTheme {
GraphVizTheme {
node_color: "#8b5cf6",
edge_color: "#64748b",
bg_color: "#1e1b4b",
font_name: "Arial",
node_shape: "record",
node_style: "filled,rounded",
edge_style: "solid",
use_gradients: true,
use_clusters: false,
}
}
#[must_use]
pub const fn dark_mermaid() -> MermaidTheme {
MermaidTheme {
theme_name: "dark",
primary_color: "#8b5cf6",
secondary_color: "#d946ef",
use_custom_css: true,
}
}
#[must_use]
pub const fn dark_ascii() -> ASCIITheme {
ASCIITheme {
box_style: "rounded",
use_unicode: true,
use_colors: true,
use_icons: true,
color_scheme: "dark",
}
}
#[must_use]
pub fn dark_theme() -> Theme {
Theme {
name: "dark",
description: "Dark background theme with violet and fuchsia for dark mode environments",
color_scheme: dark_color_scheme(),
graphviz: dark_graphviz(),
mermaid: dark_mermaid(),
ascii: dark_ascii(),
}
}
#[must_use]
pub const fn forest_color_scheme() -> ColorScheme {
ColorScheme {
primary: "#10b981",
secondary: "#14b8a6",
background: "#f0fdf4",
text: "#14532d",
accent: "#059669",
success: "#22c55e",
warning: "#f59e0b",
error: "#ef4444",
muted: "#86efac",
}
}
#[must_use]
pub const fn forest_graphviz() -> GraphVizTheme {
GraphVizTheme {
node_color: "#10b981",
edge_color: "#059669",
bg_color: "transparent",
font_name: "Arial",
node_shape: "record",
node_style: "filled,rounded",
edge_style: "solid",
use_gradients: true,
use_clusters: false,
}
}
#[must_use]
pub const fn forest_mermaid() -> MermaidTheme {
MermaidTheme {
theme_name: "forest",
primary_color: "#10b981",
secondary_color: "#14b8a6",
use_custom_css: true,
}
}
#[must_use]
pub const fn forest_ascii() -> ASCIITheme {
ASCIITheme {
box_style: "rounded",
use_unicode: true,
use_colors: true,
use_icons: true,
color_scheme: "forest",
}
}
#[must_use]
pub fn forest_theme() -> Theme {
Theme {
name: "forest",
description: "Nature-inspired theme with emerald and teal on light green background",
color_scheme: forest_color_scheme(),
graphviz: forest_graphviz(),
mermaid: forest_mermaid(),
ascii: forest_ascii(),
}
}
#[must_use]
pub const fn minimal_color_scheme() -> ColorScheme {
ColorScheme {
primary: "#6b7280",
secondary: "#64748b",
background: "#ffffff",
text: "#1f2937",
accent: "#9ca3af",
success: "#10b981",
warning: "#f59e0b",
error: "#ef4444",
muted: "#d1d5db",
}
}
#[must_use]
pub const fn minimal_graphviz() -> GraphVizTheme {
GraphVizTheme {
node_color: "#6b7280",
edge_color: "#9ca3af",
bg_color: "transparent",
font_name: "Arial",
node_shape: "record",
node_style: "filled",
edge_style: "solid",
use_gradients: false,
use_clusters: false,
}
}
#[must_use]
pub const fn minimal_mermaid() -> MermaidTheme {
MermaidTheme {
theme_name: "neutral",
primary_color: "#6b7280",
secondary_color: "#64748b",
use_custom_css: true,
}
}
#[must_use]
pub const fn minimal_ascii() -> ASCIITheme {
ASCIITheme {
box_style: "single",
use_unicode: true,
use_colors: false,
use_icons: false,
color_scheme: "minimal",
}
}
#[must_use]
pub fn minimal_theme() -> Theme {
Theme {
name: "minimal",
description: "Minimalist grayscale theme with subtle styling",
color_scheme: minimal_color_scheme(),
graphviz: minimal_graphviz(),
mermaid: minimal_mermaid(),
ascii: minimal_ascii(),
}
}
pub fn get_theme(name: &str) -> Result<Theme> {
match name {
"modern" => Ok(modern_theme()),
"dark" => Ok(dark_theme()),
"forest" => Ok(forest_theme()),
"minimal" => Ok(minimal_theme()),
other => Err(SurqlError::Validation {
reason: format!(
"Unknown theme: {other:?}. Available themes: dark, forest, minimal, modern"
),
}),
}
}
#[must_use]
pub fn list_themes() -> Vec<&'static str> {
vec!["dark", "forest", "minimal", "modern"]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn color_scheme_defaults_match_python() {
let cs = ColorScheme::default();
assert_eq!(cs.primary, "#6366f1");
assert_eq!(cs.secondary, "#ec4899");
assert_eq!(cs.background, "#f8fafc");
assert_eq!(cs.text, "#0f172a");
assert_eq!(cs.accent, "#8b5cf6");
assert_eq!(cs.success, "#10b981");
assert_eq!(cs.warning, "#f59e0b");
assert_eq!(cs.error, "#ef4444");
assert_eq!(cs.muted, "#94a3b8");
}
#[test]
fn graphviz_theme_defaults_match_python() {
let t = GraphVizTheme::default();
assert_eq!(t.node_color, "#6366f1");
assert_eq!(t.edge_color, "#64748b");
assert_eq!(t.bg_color, "transparent");
assert_eq!(t.font_name, "Arial");
assert_eq!(t.node_shape, "record");
assert_eq!(t.node_style, "filled,rounded");
assert_eq!(t.edge_style, "solid");
assert!(t.use_gradients);
assert!(!t.use_clusters);
}
#[test]
fn mermaid_theme_defaults_match_python() {
let t = MermaidTheme::default();
assert_eq!(t.theme_name, "default");
assert_eq!(t.primary_color, "#6366f1");
assert_eq!(t.secondary_color, "#ec4899");
assert!(t.use_custom_css);
}
#[test]
fn ascii_theme_defaults_match_python() {
let t = ASCIITheme::default();
assert_eq!(t.box_style, "rounded");
assert!(t.use_unicode);
assert!(t.use_colors);
assert!(t.use_icons);
assert_eq!(t.color_scheme, "default");
}
#[test]
fn modern_theme_matches_python() {
let t = modern_theme();
assert_eq!(t.name, "modern");
assert_eq!(
t.description,
"Clean, professional design with indigo and pink accents"
);
assert_eq!(t.color_scheme.primary, "#6366f1");
assert_eq!(t.graphviz.node_color, "#6366f1");
assert_eq!(t.mermaid.theme_name, "default");
assert_eq!(t.ascii.box_style, "rounded");
}
#[test]
fn dark_theme_matches_python() {
let t = dark_theme();
assert_eq!(t.name, "dark");
assert_eq!(t.color_scheme.primary, "#8b5cf6");
assert_eq!(t.color_scheme.secondary, "#d946ef");
assert_eq!(t.color_scheme.background, "#1e1b4b");
assert_eq!(t.graphviz.bg_color, "#1e1b4b");
assert_eq!(t.mermaid.theme_name, "dark");
assert_eq!(t.ascii.color_scheme, "dark");
}
#[test]
fn forest_theme_matches_python() {
let t = forest_theme();
assert_eq!(t.name, "forest");
assert_eq!(t.color_scheme.primary, "#10b981");
assert_eq!(t.color_scheme.secondary, "#14b8a6");
assert_eq!(t.graphviz.edge_color, "#059669");
assert_eq!(t.mermaid.theme_name, "forest");
}
#[test]
fn minimal_theme_matches_python() {
let t = minimal_theme();
assert_eq!(t.name, "minimal");
assert_eq!(t.color_scheme.primary, "#6b7280");
assert_eq!(t.graphviz.node_style, "filled");
assert!(!t.graphviz.use_gradients);
assert_eq!(t.mermaid.theme_name, "neutral");
assert_eq!(t.ascii.box_style, "single");
assert!(!t.ascii.use_colors);
assert!(!t.ascii.use_icons);
}
#[test]
fn get_theme_known_names() {
assert_eq!(get_theme("modern").unwrap().name, "modern");
assert_eq!(get_theme("dark").unwrap().name, "dark");
assert_eq!(get_theme("forest").unwrap().name, "forest");
assert_eq!(get_theme("minimal").unwrap().name, "minimal");
}
#[test]
fn get_theme_unknown_errors() {
let err = get_theme("neon").unwrap_err();
assert!(matches!(err, SurqlError::Validation { .. }));
assert!(err.to_string().contains("Unknown theme"));
}
#[test]
fn list_themes_is_sorted() {
let names = list_themes();
assert_eq!(names, vec!["dark", "forest", "minimal", "modern"]);
}
#[test]
fn graphviz_backward_compatible_disables_flags() {
let t = modern_graphviz().backward_compatible();
assert!(!t.use_gradients);
assert!(!t.use_clusters);
assert_eq!(t.node_color, "#6366f1");
}
}