Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions cot-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,25 +108,6 @@ pub fn dbtest(_args: TokenStream, input: TokenStream) -> TokenStream {
.into()
}

/// An attribute macro that defines a custom migration operation.
///
/// This macro simplifies writing custom migration operations by allowing you to
/// write them as regular `async` functions. It handles the necessary pinning
/// and boxing of the return type to make it compatible with the migration
/// engine.
///
/// # Examples
///
/// ```
/// use cot::db::Result;
/// use cot::db::migrations::{MigrationContext, migration_op};
///
/// #[migration_op]
/// async fn my_migration(ctx: MigrationContext<'_>) -> Result<()> {
/// // Your migration logic here
/// Ok(())
/// }
/// ```
#[proc_macro_attribute]
pub fn migration_op(_args: TokenStream, input: TokenStream) -> TokenStream {
let fn_input = parse_macro_input!(input as ItemFn);
Expand Down
2 changes: 2 additions & 0 deletions cot-test/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod utils;

use std::fs;
use std::path::PathBuf;
use std::sync::{Mutex, MutexGuard, OnceLock};
Expand Down
55 changes: 55 additions & 0 deletions cot-test/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! Test utilities

use std::fmt::Write;

/// Formats a code snippet for an error message using the style of rustc
/// diagnostics
///
/// # Example
///
/// ```text
/// --> migrations.md:201:1
/// |
/// 201 | Rollback dry run
/// 202 |
/// 203 | Target:
/// 204 | app: customers
/// 205 | migration: m_0001_initial
/// |
/// ```
#[must_use]
pub fn format_code_snippet(
literal: &str,
file_name: &str,
start_line: usize,
start_col: usize,
max_lines: usize,
) -> String {
const FMT_MSG: &str = "failed to write to string buffer";
let lines: Vec<&str> = literal.lines().take(max_lines).collect();

// Width of the largest line number, for gutter alignment.
let last_line_num = start_line + lines.len().saturating_sub(1);
let gutter_width = last_line_num.to_string().len();

let mut out = String::new();
writeln!(
out,
"{:width$}--> {}:{}:{}\n",
"",
file_name,
start_line,
start_col,
width = gutter_width + 1
)
.expect(FMT_MSG);
writeln!(out, "{:gutter_width$} |", "").expect(FMT_MSG);

for (i, line) in lines.iter().enumerate() {
let line_num = start_line + i;
writeln!(out, "{line_num:gutter_width$} | {line}").expect(FMT_MSG);
}
writeln!(out, "{:gutter_width$} |", "").expect(FMT_MSG);

out
}
37 changes: 33 additions & 4 deletions cot-test/tests/doc_code_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,28 @@ use std::sync::OnceLock;
use comrak::arena_tree::NodeEdge;
use comrak::nodes::NodeValue;
use comrak::{Arena, parse_document};
use cot_test::{TestConfig, TestLanguage, get_test_project};
use cot_test::utils::format_code_snippet;
use cot_test::{TestConfig, TestLanguage, TestLanguageFromStringError, get_test_project};
use libtest_mimic::{Arguments, Failed, Trial};

type TestRunner = fn(&str) -> Result<(), Failed>;

static TEST_RUNNERS: OnceLock<HashMap<(TestLanguage, TestConfig), TestRunner>> = OnceLock::new();

#[derive(Debug, thiserror::Error)]
#[error(
"{source}\n{}",
format_code_snippet(literal, file_name, *start_line, *start_col, 5)
)]
pub struct CodeBlockError {
#[source]
source: TestLanguageFromStringError,
file_name: String,
literal: String,
start_line: usize,
start_col: usize,
}

fn main() {
let args = Arguments::from_args();

Expand Down Expand Up @@ -45,13 +60,19 @@ fn main() {
}

let contents = fs::read_to_string(&path).expect("failed to read md file");
test_md(&mut trials, file_name, &contents);
if let Err(err) = test_md(&mut trials, file_name, &contents) {
panic!("{err}");
}
}

libtest_mimic::run(&args, trials).exit();
}

fn test_md(trials: &mut Vec<Trial>, file_name: &str, file_contents: &str) {
fn test_md(
trials: &mut Vec<Trial>,
file_name: &str,
file_contents: &str,
) -> Result<(), CodeBlockError> {
let arena = Arena::new();

let mut options = comrak::Options::default();
Expand All @@ -75,7 +96,14 @@ fn test_md(trials: &mut Vec<Trial>, file_name: &str, file_contents: &str) {
TestConfig::Default,
)
};
let lang = lang.expect("unknown language");

let lang = lang.map_err(|source| CodeBlockError {
source,
file_name: file_name.to_string(),
literal: code_block.literal.clone(),
start_line: node_data.sourcepos.start.line,
start_col: node_data.sourcepos.start.column,
})?;

if let Some(runner) = TEST_RUNNERS.get().unwrap().get(&(lang, test_config)) {
let literal = if lang == TestLanguage::Rust {
Expand Down Expand Up @@ -106,6 +134,7 @@ fn test_md(trials: &mut Vec<Trial>, file_name: &str, file_contents: &str) {
}
}
}
Ok(())
}

fn clean_code(code: &str) -> String {
Expand Down
19 changes: 19 additions & 0 deletions cot/src/db/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ use std::future::Future;
use std::io::Write;
use std::{fmt, io};

/// An attribute macro that defines a custom migration operation.
///
/// This macro simplifies writing custom migration operations by allowing you to
/// write them as regular `async` functions. It handles the necessary pinning
/// and boxing of the return type to make it compatible with the migration
/// engine.
///
/// # Examples
///
/// ```
/// use cot::db::Result;
/// use cot::db::migrations::{MigrationContext, migration_op};
///
/// #[migration_op]
/// async fn my_migration(ctx: MigrationContext<'_>) -> Result<()> {
/// // Your migration logic here
/// Ok(())
/// }
/// ```
pub use cot_macros::migration_op;
use sea_query::{ColumnDef, StringLen};
use thiserror::Error;
Expand Down
Loading
Loading