acdc_parser/model/
tables.rs

1//! Table types for `AsciiDoc` documents.
2
3use serde::{Deserialize, Serialize};
4
5use super::Block;
6use super::location::Location;
7
8/// Horizontal alignment for table cells
9#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum HorizontalAlignment {
12    #[default]
13    Left,
14    Center,
15    Right,
16}
17
18/// Vertical alignment for table cells
19#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21pub enum VerticalAlignment {
22    #[default]
23    Top,
24    Middle,
25    Bottom,
26}
27
28/// Column width specification
29#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
30#[serde(rename_all = "lowercase")]
31#[non_exhaustive]
32pub enum ColumnWidth {
33    /// Proportional width (e.g., 1, 2, 3 - relative to other columns)
34    Proportional(u32),
35    /// Percentage width (e.g., 15%, 30%, 55%)
36    Percentage(u32),
37    /// Auto-width - content determines width (~)
38    Auto,
39}
40
41impl Default for ColumnWidth {
42    fn default() -> Self {
43        ColumnWidth::Proportional(1)
44    }
45}
46
47/// Column content style
48#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
49#[serde(rename_all = "lowercase")]
50#[non_exhaustive]
51pub enum ColumnStyle {
52    /// `AsciiDoc` block content (a) - supports lists, blocks, macros
53    #[serde(rename = "asciidoc")]
54    AsciiDoc,
55    /// Default paragraph-level markup (d)
56    #[default]
57    Default,
58    /// Emphasis/italic (e)
59    Emphasis,
60    /// Header styling (h)
61    Header,
62    /// Literal block text (l)
63    Literal,
64    /// Monospace font (m)
65    Monospace,
66    /// Strong/bold (s)
67    Strong,
68}
69
70/// Column format specification for table formatting
71#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
72#[non_exhaustive]
73pub struct ColumnFormat {
74    #[serde(default, skip_serializing_if = "is_default_halign")]
75    pub halign: HorizontalAlignment,
76    #[serde(default, skip_serializing_if = "is_default_valign")]
77    pub valign: VerticalAlignment,
78    #[serde(default, skip_serializing_if = "is_default_width")]
79    pub width: ColumnWidth,
80    #[serde(default, skip_serializing_if = "is_default_style")]
81    pub style: ColumnStyle,
82}
83
84impl ColumnFormat {
85    /// Create a new column format with default values.
86    #[must_use]
87    pub fn new() -> Self {
88        Self::default()
89    }
90
91    /// Set the horizontal alignment.
92    #[must_use]
93    pub fn with_halign(mut self, halign: HorizontalAlignment) -> Self {
94        self.halign = halign;
95        self
96    }
97
98    /// Set the vertical alignment.
99    #[must_use]
100    pub fn with_valign(mut self, valign: VerticalAlignment) -> Self {
101        self.valign = valign;
102        self
103    }
104
105    /// Set the column width.
106    #[must_use]
107    pub fn with_width(mut self, width: ColumnWidth) -> Self {
108        self.width = width;
109        self
110    }
111
112    /// Set the column style.
113    #[must_use]
114    pub fn with_style(mut self, style: ColumnStyle) -> Self {
115        self.style = style;
116        self
117    }
118}
119
120#[allow(clippy::trivially_copy_pass_by_ref)]
121fn is_default_halign(h: &HorizontalAlignment) -> bool {
122    *h == HorizontalAlignment::default()
123}
124
125#[allow(clippy::trivially_copy_pass_by_ref)]
126fn is_default_valign(v: &VerticalAlignment) -> bool {
127    *v == VerticalAlignment::default()
128}
129
130#[allow(clippy::trivially_copy_pass_by_ref)]
131fn is_default_width(w: &ColumnWidth) -> bool {
132    *w == ColumnWidth::default()
133}
134
135#[allow(clippy::trivially_copy_pass_by_ref)]
136fn is_default_style(s: &ColumnStyle) -> bool {
137    *s == ColumnStyle::default()
138}
139
140pub(crate) fn are_all_columns_default(specs: &[ColumnFormat]) -> bool {
141    specs.iter().all(|s| *s == ColumnFormat::default())
142}
143
144/// A `Table` represents a table in a document.
145#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
146#[non_exhaustive]
147pub struct Table {
148    pub header: Option<TableRow>,
149    pub footer: Option<TableRow>,
150    pub rows: Vec<TableRow>,
151    /// Column format specification for each column (alignment, width, style)
152    /// Skipped if all columns have default format
153    #[serde(default, skip_serializing_if = "are_all_columns_default")]
154    pub columns: Vec<ColumnFormat>,
155    pub location: Location,
156}
157
158impl Table {
159    /// Create a new table with the given rows and location.
160    #[must_use]
161    pub fn new(rows: Vec<TableRow>, location: Location) -> Self {
162        Self {
163            header: None,
164            footer: None,
165            rows,
166            columns: Vec::new(),
167            location,
168        }
169    }
170
171    /// Set the header row.
172    #[must_use]
173    pub fn with_header(mut self, header: Option<TableRow>) -> Self {
174        self.header = header;
175        self
176    }
177
178    /// Set the footer row.
179    #[must_use]
180    pub fn with_footer(mut self, footer: Option<TableRow>) -> Self {
181        self.footer = footer;
182        self
183    }
184
185    /// Set the column format specifications.
186    #[must_use]
187    pub fn with_columns(mut self, columns: Vec<ColumnFormat>) -> Self {
188        self.columns = columns;
189        self
190    }
191}
192
193/// A `TableRow` represents a row in a table.
194#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
195#[non_exhaustive]
196pub struct TableRow {
197    pub columns: Vec<TableColumn>,
198}
199
200impl TableRow {
201    /// Create a new table row with the given columns.
202    #[must_use]
203    pub fn new(columns: Vec<TableColumn>) -> Self {
204        Self { columns }
205    }
206}
207
208/// A `TableColumn` represents a column/cell in a table row.
209#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
210#[non_exhaustive]
211pub struct TableColumn {
212    pub content: Vec<Block>,
213}
214
215impl TableColumn {
216    /// Create a new table column with the given content.
217    #[must_use]
218    pub fn new(content: Vec<Block>) -> Self {
219        Self { content }
220    }
221}