Skip to content

Commit 530cf2a

Browse files
committed
implemented doc comment flattening. Fixes #15
1 parent e91403b commit 530cf2a

2 files changed

Lines changed: 283 additions & 5 deletions

File tree

src/docs.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ impl ColumnDoc {
3737
pub fn doc(&self) -> Option<&str> {
3838
self.doc.as_deref()
3939
}
40+
41+
/// Setter to update the table doc
42+
pub fn set_doc(&mut self, doc: impl Into<String>) {
43+
self.doc = Some(doc.into());
44+
}
4045
}
4146

4247
impl fmt::Display for ColumnDoc {
@@ -100,12 +105,22 @@ impl TableDoc {
100105
self.doc.as_deref()
101106
}
102107

108+
/// Setter to update the table doc
109+
pub fn set_doc(&mut self, doc: impl Into<String>) {
110+
self.doc = Some(doc.into());
111+
}
112+
103113
/// Getter for the `columns` field
104114
#[must_use]
105115
#[allow(clippy::missing_const_for_fn)]
106116
pub fn columns(&self) -> &[ColumnDoc] {
107117
&self.columns
108118
}
119+
120+
/// Getter that returns a mutable reference to the [`ColumnDoc`] vec
121+
pub fn columns_mut(&mut self) -> &mut [ColumnDoc] {
122+
&mut self.columns
123+
}
109124
}
110125

111126
impl fmt::Display for TableDoc {
@@ -550,7 +565,6 @@ mod tests {
550565

551566
fn count_writes<T: fmt::Display>(v: &T) -> usize {
552567
let mut w = FailOnNthWrite { fail_at: usize::MAX, writes: 0, sink: String::new() };
553-
// We don't care if this errors (it shouldn't with fail_at=MAX), we only want the write count.
554568
let _ = fmt::write(&mut w, format_args!("{v}"));
555569
w.writes
556570
}
@@ -584,4 +598,54 @@ mod tests {
584598
);
585599
}
586600
}
601+
602+
#[test]
603+
fn column_doc_set_doc_updates_doc() {
604+
let mut col = ColumnDoc::new("id".to_string(), None);
605+
assert_eq!(col.name(), "id");
606+
assert_eq!(col.doc(), None);
607+
col.set_doc("primary key");
608+
assert_eq!(col.doc(), Some("primary key"));
609+
let new_doc = String::from("primary key for users table");
610+
col.set_doc(new_doc);
611+
assert_eq!(col.doc(), Some("primary key for users table"));
612+
}
613+
614+
#[test]
615+
fn table_doc_set_doc_updates_doc() {
616+
let mut table = TableDoc::new(None, "users".to_string(), None, Vec::new());
617+
assert_eq!(table.name(), "users");
618+
assert_eq!(table.schema(), None);
619+
assert_eq!(table.doc(), None);
620+
table.set_doc("users table docs");
621+
assert_eq!(table.doc(), Some("users table docs"));
622+
table.set_doc(String::from("updated users table docs"));
623+
assert_eq!(table.doc(), Some("updated users table docs"));
624+
}
625+
626+
#[test]
627+
fn columns_mut_allows_mutating_column_docs() {
628+
let mut table = TableDoc::new(
629+
None,
630+
"users".to_string(),
631+
None,
632+
vec![
633+
ColumnDoc::new("id".to_string(), None),
634+
ColumnDoc::new("username".to_string(), None),
635+
],
636+
);
637+
638+
{
639+
let cols_mut = table.columns_mut();
640+
assert_eq!(cols_mut.len(), 2);
641+
cols_mut[0].set_doc("primary key");
642+
cols_mut[1].set_doc("login name");
643+
}
644+
645+
let cols = table.columns();
646+
assert_eq!(cols[0].name(), "id");
647+
assert_eq!(cols[0].doc(), Some("primary key"));
648+
assert_eq!(cols[1].name(), "username");
649+
assert_eq!(cols[1].doc(), Some("login name"));
650+
}
587651
}

src/sql_doc.rs

Lines changed: 218 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ pub struct SqlDocBuilder {
2828
deny: Vec<String>,
2929
/// Used to indicate maintaining the `[(PathBuf, SqlFileDoc)]`
3030
retain_files: bool,
31+
/// Struct for tracking the settings for flattening multiline comments
32+
multiline_flat: MultiFlatten,
33+
}
34+
35+
/// Enum for multiline comment flattening.
36+
#[derive(Debug, Eq, PartialEq)]
37+
pub enum MultiFlatten {
38+
/// Default option, retains multiline structure with `\n`
39+
NoFlat,
40+
/// Sets multiline comments to be flattened and combined without adding formatting
41+
FlattenWithNone,
42+
/// Will flatten comments and amend the content of [`String`] to the end of the former leading lines
43+
Flatten(String),
3144
}
3245

3346
/// Enum for specifying a file doc source as a `directory` or a specific `file`
@@ -49,6 +62,7 @@ impl SqlDoc {
4962
source: SqlFileDocSource::Dir(root.as_ref().to_path_buf()),
5063
deny: Vec::new(),
5164
retain_files: false,
65+
multiline_flat: MultiFlatten::NoFlat,
5266
}
5367
}
5468
/// Method for generating builder from a [`Path`] of a single file
@@ -57,6 +71,7 @@ impl SqlDoc {
5771
source: SqlFileDocSource::File(path.as_ref().to_path_buf()),
5872
deny: Vec::new(),
5973
retain_files: false,
74+
multiline_flat: MultiFlatten::NoFlat,
6075
}
6176
}
6277

@@ -112,6 +127,11 @@ impl SqlDoc {
112127
pub fn tables(&self) -> &[TableDoc] {
113128
&self.tables
114129
}
130+
/// Getter that returns a mutable reference to the [`TableDoc`]
131+
#[must_use]
132+
pub fn tables_mut(&mut self) -> &mut [TableDoc] {
133+
&mut self.tables
134+
}
115135
/// Method to move tables out of Structure if needed
116136
#[must_use]
117137
pub fn into_tables(self) -> Vec<TableDoc> {
@@ -142,6 +162,26 @@ impl SqlDocBuilder {
142162
self
143163
}
144164

165+
/// Method for flattening the multiline comments without additional formatting
166+
#[must_use]
167+
pub fn flatten_multiline(mut self) -> Self {
168+
self.multiline_flat = MultiFlatten::FlattenWithNone;
169+
self
170+
}
171+
172+
/// Method for flattening the multiline comments with [`String`] containing additional leading line formatting to add, such as punctuation.
173+
#[must_use]
174+
pub fn flatten_multiline_with(mut self, suffix: &str) -> Self {
175+
self.multiline_flat = MultiFlatten::Flatten(suffix.to_string());
176+
self
177+
}
178+
/// Method to set multiline comments to preserver multiple lines
179+
#[must_use]
180+
pub fn preserve_multiline(mut self) -> Self {
181+
self.multiline_flat = MultiFlatten::NoFlat;
182+
self
183+
}
184+
145185
/// Builds the [`SqlDoc`]
146186
///
147187
/// # Errors
@@ -156,21 +196,60 @@ impl SqlDocBuilder {
156196
}
157197
};
158198
let mut tables = Vec::new();
159-
if self.retain_files {
199+
let mut sql_doc = if self.retain_files {
160200
let files = docs;
161201
for (_, sql_doc) in &files {
162202
tables.extend(sql_doc.tables().iter().cloned());
163203
}
164-
Ok(SqlDoc { tables, files: Some(files) })
204+
SqlDoc { tables, files: Some(files) }
165205
} else {
166206
for (_, sql_doc) in &docs {
167207
tables.extend(sql_doc.tables().iter().cloned());
168208
}
169-
Ok(SqlDoc { tables, files: None })
209+
SqlDoc { tables, files: None }
210+
};
211+
match self.multiline_flat {
212+
MultiFlatten::FlattenWithNone => {
213+
sql_doc = flatten_docs(sql_doc, None);
214+
}
215+
MultiFlatten::Flatten(flat_format) => {
216+
sql_doc = flatten_docs(sql_doc, Some(&flat_format));
217+
}
218+
MultiFlatten::NoFlat => {}
170219
}
220+
Ok(sql_doc)
171221
}
172222
}
173223

224+
fn flatten_docs(mut sql_doc: SqlDoc, flat_format: Option<&str>) -> SqlDoc {
225+
let format = flat_format.map_or("", |c| c);
226+
for table in sql_doc.tables_mut() {
227+
if let Some(doc) = table.doc() {
228+
let new_doc = flatten_lines(doc.lines(), format);
229+
table.set_doc(new_doc);
230+
}
231+
for column in table.columns_mut() {
232+
if let Some(doc) = column.doc() {
233+
let new_doc = flatten_lines(doc.lines(), format);
234+
column.set_doc(new_doc);
235+
}
236+
}
237+
}
238+
239+
sql_doc
240+
}
241+
242+
fn flatten_lines<'a>(lines: impl Iterator<Item = &'a str>, sep: &str) -> String {
243+
let mut out = String::new();
244+
for (i, line) in lines.enumerate() {
245+
if i > 0 {
246+
out.push_str(sep);
247+
}
248+
out.push_str(line);
249+
}
250+
out
251+
}
252+
174253
fn generate_docs_from_dir<P: AsRef<Path>, S: AsRef<str>>(
175254
source: P,
176255
deny: &[S],
@@ -206,7 +285,7 @@ mod tests {
206285
SqlDoc,
207286
docs::{ColumnDoc, SqlFileDoc, TableDoc},
208287
error::DocError,
209-
sql_doc::SqlDocBuilder,
288+
sql_doc::{MultiFlatten, SqlDocBuilder},
210289
};
211290

212291
#[test]
@@ -446,6 +525,7 @@ mod tests {
446525
source: crate::sql_doc::SqlFileDocSource::File(PathBuf::from("path")),
447526
deny: vec!["path1".to_string(), "path2".to_string()],
448527
retain_files: true,
528+
multiline_flat: MultiFlatten::NoFlat,
449529
};
450530
assert_eq!(actual_builder, expected_builder);
451531
}
@@ -476,4 +556,138 @@ mod tests {
476556
let _ = fs::remove_dir_all(&base);
477557
Ok(())
478558
}
559+
560+
#[test]
561+
fn test_builder_multiflatten_variants() {
562+
let b1 = SqlDoc::from_path("dummy.sql");
563+
let b2 = SqlDoc::from_path("dummy.sql").flatten_multiline();
564+
let b3 = SqlDoc::from_path("dummy.sql").flatten_multiline_with(" . ");
565+
let b4 = SqlDoc::from_path("dummy.sql").flatten_multiline_with("--").preserve_multiline();
566+
assert!(matches!(b1, SqlDocBuilder { multiline_flat: MultiFlatten::NoFlat, .. }));
567+
assert!(matches!(b2, SqlDocBuilder { multiline_flat: MultiFlatten::FlattenWithNone, .. }));
568+
assert!(
569+
matches!(b3, SqlDocBuilder { multiline_flat: MultiFlatten::Flatten(s) , .. } if s == " . ")
570+
);
571+
assert!(matches!(b4, SqlDocBuilder { multiline_flat: MultiFlatten::NoFlat, .. }));
572+
}
573+
574+
#[test]
575+
fn test_flatten_lines_behavior() {
576+
use crate::sql_doc::flatten_lines;
577+
let input = vec!["a", "b", "c"];
578+
let no_sep = flatten_lines(input.clone().into_iter(), "");
579+
assert_eq!(no_sep, "abc");
580+
let dash_sep = flatten_lines(input.clone().into_iter(), " - ");
581+
assert_eq!(dash_sep, "a - b - c");
582+
let single = flatten_lines(["solo"].into_iter(), "XXX");
583+
assert_eq!(single, "solo");
584+
}
585+
586+
#[test]
587+
fn test_flatten_docs_no_flatter() {
588+
let table = TableDoc::new(
589+
None,
590+
"t".into(),
591+
Some("line1\nline2".into()),
592+
vec![
593+
ColumnDoc::new("c1".into(), Some("a\nb".into())),
594+
ColumnDoc::new("c2".into(), None),
595+
],
596+
);
597+
let flat_table = TableDoc::new(
598+
None,
599+
"t".into(),
600+
Some("line1line2".into()),
601+
vec![ColumnDoc::new("c1".into(), Some("ab".into())), ColumnDoc::new("c2".into(), None)],
602+
);
603+
604+
let output = {
605+
use crate::sql_doc::flatten_docs;
606+
let original = SqlDoc::new(vec![table], None);
607+
flatten_docs(original, None)
608+
};
609+
assert_eq!(output.tables(), vec![flat_table], "NoFlat should not alter docs");
610+
}
611+
612+
#[test]
613+
fn test_flatten_docs_flatten_no_separator() {
614+
let table = TableDoc::new(
615+
None,
616+
"t".into(),
617+
Some("A\nB\nC".into()),
618+
vec![ColumnDoc::new("c".into(), Some("x\ny".into()))],
619+
);
620+
let doc = SqlDoc::new(vec![table], None);
621+
622+
let out = {
623+
use crate::sql_doc::flatten_docs;
624+
flatten_docs(doc, Some(""))
625+
};
626+
627+
let t = &out.tables()[0];
628+
629+
assert_eq!(t.doc(), Some("ABC"));
630+
assert_eq!(t.columns()[0].doc(), Some("xy"));
631+
}
632+
633+
#[test]
634+
fn test_flatten_docs_flatten_with_separator() {
635+
let table = TableDoc::new(
636+
None,
637+
"t".into(),
638+
Some("hello\nworld".into()),
639+
vec![ColumnDoc::new("c".into(), Some("x\ny\nz".into()))],
640+
);
641+
let doc = SqlDoc::new(vec![table], None);
642+
643+
let out = {
644+
use crate::sql_doc::flatten_docs;
645+
flatten_docs(doc, Some(" | "))
646+
};
647+
648+
let t = &out.tables()[0];
649+
650+
assert_eq!(t.doc(), Some("hello | world"));
651+
assert_eq!(t.columns()[0].doc(), Some("x | y | z"));
652+
}
653+
654+
#[test]
655+
fn test_tables_mut_allows_modification() {
656+
let mut sql_doc =
657+
SqlDoc::new(vec![TableDoc::new(None, "t".into(), Some("old".into()), vec![])], None);
658+
for t in sql_doc.tables_mut() {
659+
t.set_doc("new");
660+
}
661+
assert_eq!(sql_doc.tables()[0].doc(), Some("new"));
662+
}
663+
664+
#[test]
665+
fn test_builder_build_with_flattening() -> Result<(), Box<dyn std::error::Error>> {
666+
let base = env::temp_dir().join("builder_flatten_test");
667+
let _ = fs::remove_dir_all(&base);
668+
fs::create_dir_all(&base)?;
669+
let file = base.join("t.sql");
670+
671+
let sql = r"
672+
/* Table Doc line1
673+
line2 */
674+
CREATE TABLE things (
675+
/* col1
676+
doc */
677+
id INTEGER
678+
);
679+
";
680+
681+
fs::write(&file, sql)?;
682+
683+
let built = SqlDoc::from_path(&file).flatten_multiline_with(" • ").build()?;
684+
685+
let t = &built.tables()[0];
686+
687+
assert_eq!(t.doc(), Some("Table Doc line1 • line2"));
688+
assert_eq!(t.columns()[0].doc(), Some("col1 • doc"));
689+
690+
let _ = fs::remove_dir_all(&base);
691+
Ok(())
692+
}
479693
}

0 commit comments

Comments
 (0)