Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4a820dd
feat: run sqb audit natively
kvlonge Oct 13, 2026
faa0725
refactor(lint): neutralize interpolation, apply suppressions and scan…
kvlonge Oct 13, 2026
2b0f581
refactor(lint): format SQL bodies natively
kvlonge Oct 13, 2026
9ab4ab8
refactor(lint): lint and map SQL bodies natively
kvlonge Oct 13, 2026
0cc8f62
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
084a803
test: report interpolation restore failures without branching in the …
kvlonge Oct 13, 2026
6ea6cef
feat: run sqb query natively
kvlonge Oct 13, 2026
436cc59
feat: run sqb dag natively
kvlonge Oct 13, 2026
ed0f46d
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
9bc9ac4
refactor(core): scenario command selection, namespaces, result rows a…
kvlonge Oct 13, 2026
93efe45
feat: run sqb scenario test and capture natively
kvlonge Oct 13, 2026
4cd1a97
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
e45e4ae
feat: run sqb contract natively
kvlonge Oct 13, 2026
9ef310c
feat: run sqb init natively
kvlonge Oct 13, 2026
8d7c7f0
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
a093da4
refactor: share terminal cell widths and text wrapping in sqlbuild-core
kvlonge Oct 13, 2026
db21dfd
refactor(format): render format reports and resolve formatter selecto…
kvlonge Oct 13, 2026
a0f9610
feat: run sqb format natively
kvlonge Oct 13, 2026
f206078
refactor(lint): run lint passes and collect lint inputs natively
kvlonge Oct 13, 2026
b5faf0e
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
8ff0450
feat: run sqb cost natively
kvlonge Oct 13, 2026
f19bb9d
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
c7be26e
refactor(core): build command safety gates, limits, run context, timi…
kvlonge Oct 13, 2026
ea08e91
feat: enforce build plan safety gates natively
kvlonge Oct 13, 2026
a3b4422
feat: run sqb build natively
kvlonge Oct 13, 2026
e2d25df
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
babae82
test: raise the dense 10k all-rules compile memory budget
kvlonge Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 9 additions & 3 deletions crates/sqlbuild-cli/src/argv/_helpers/formatting.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! `argparse.HelpFormatter`'s help, usage and version text in the Python 3.13 layout.

use crate::argv::_helpers::text_wrap::{normalize_whitespace, wrap};
use sqlbuild_core::text::main::text_wrap::text_wrap;

use crate::argv::_helpers::text_wrap::normalize_whitespace;
use crate::argv::constants::{
INDENT_INCREMENT, MAX_HELP_POSITION, MAXIMUM_BLANK_RUN, MINIMUM_TEXT_WIDTH, USAGE_PREFIX,
VERSION_PLACEHOLDER,
Expand Down Expand Up @@ -130,7 +132,11 @@ fn help_text(help: &HelpText) -> Option<&str> {
fn format_text(formatter: &Formatter, text: &str, indent: i64) -> String {
let width: i64 = (formatter.width - indent).max(MINIMUM_TEXT_WIDTH);
let padding: String = " ".repeat(indent as usize);
let lines: Vec<String> = wrap(&normalize_whitespace(text), width as usize, &padding);
let lines: Vec<String> = text_wrap(
&normalize_whitespace(text),
width as usize,
(&padding, &padding),
);
format!("{}\n\n", lines.join("\n"))
}

Expand Down Expand Up @@ -179,7 +185,7 @@ fn format_action(formatter: &Formatter, entry: &HelpEntry, indent: i64) -> Strin
}
};
let lines: Vec<String> = help
.map(|help| wrap(&normalize_whitespace(help), help_width as usize, ""))
.map(|help| text_wrap(&normalize_whitespace(help), help_width as usize, ("", "")))
.unwrap_or_default();
if let Some((first, rest)) = lines.split_first() {
text.push_str(&format!("{}{first}\n", " ".repeat(indent_first as usize)));
Expand Down
169 changes: 1 addition & 168 deletions crates/sqlbuild-cli/src/argv/_helpers/text_wrap.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
//! Python's `textwrap.wrap` with its default options, as `argparse` uses it.

use sqlbuild_core::text::main::is_decimal_digit::is_decimal_digit;
use sqlbuild_core::text::main::python_isalnum::python_isalnum;

use crate::argv::constants::MINIMUM_EM_DASH;
//! Whitespace normalization `argparse` applies before wrapping help text.

/// `_whitespace_matcher.sub(' ', text).strip()`: ASCII whitespace runs become one space.
pub(crate) fn normalize_whitespace(text: &str) -> String {
Expand All @@ -22,165 +17,3 @@ pub(crate) fn normalize_whitespace(text: &str) -> String {
}
normalized
}

/// `textwrap.wrap(text, width, initial_indent=indent, subsequent_indent=indent)`.
pub(crate) fn wrap(text: &str, width: usize, indent: &str) -> Vec<String> {
let characters: Vec<char> = text.chars().collect();
let mut chunks: Vec<Vec<char>> = split_chunks(&characters);
chunks.reverse();
let indent_length: usize = indent.chars().count();
let mut lines: Vec<String> = Vec::new();
while !chunks.is_empty() {
let mut line: Vec<Vec<char>> = Vec::new();
let mut length: usize = 0;
let available: i64 = width as i64 - indent_length as i64;
if !lines.is_empty() && chunks.last().is_some_and(|chunk| is_blank(chunk)) {
chunks.pop();
}
while let Some(chunk) = chunks.last() {
if (length + chunk.len()) as i64 <= available {
length += chunk.len();
line.push(chunks.pop().unwrap_or_default());
} else {
break;
}
}
if let Some(chunk) = chunks.pop_if(|chunk| chunk.len() as i64 > available) {
let (head, rest) = split_long_word(chunk, line.is_empty(), length, available);
line.extend(head);
chunks.extend(rest);
}
if line.last().is_some_and(|chunk| is_blank(chunk)) {
line.pop();
}
if !line.is_empty() {
let joined: String = line.iter().flatten().collect();
lines.push(format!("{indent}{joined}"));
}
}
lines
}

/// `_handle_long_word`: the part of a too-long chunk the line takes, and what stays queued.
fn split_long_word(
mut chunk: Vec<char>,
line_is_empty: bool,
length: usize,
width: i64,
) -> (Option<Vec<char>>, Option<Vec<char>>) {
let space_left: i64 = if width < 1 { 1 } else { width - length as i64 };
if space_left <= 0 {
return if line_is_empty {
(Some(chunk), None)
} else {
(None, Some(chunk))
};
}
let space_left: usize = space_left as usize;
let mut end: usize = space_left.min(chunk.len());
if chunk.len() > space_left
&& let Some(hyphen) = chunk[..space_left]
.iter()
.rposition(|character| *character == '-')
&& hyphen > 0
&& chunk[..hyphen].iter().any(|character| *character != '-')
{
end = hyphen + 1;
}
let rest: Vec<char> = chunk.split_off(end);
(Some(chunk), Some(rest))
}

fn is_blank(chunk: &[char]) -> bool {
chunk.iter().all(|character| character.is_whitespace())
}

fn is_space(character: char) -> bool {
matches!(character, '\t' | '\n' | '\x0b' | '\x0c' | '\r' | ' ')
}

fn is_word(character: char) -> bool {
character == '_' || python_isalnum(character)
}

fn is_word_punct(character: char) -> bool {
is_word(character) || matches!(character, '!' | '"' | '\'' | '&' | '.' | ',' | '?')
}

fn is_letter(character: char) -> bool {
is_word(character) && !is_decimal_digit(character)
}

/// `TextWrapper.wordsep_re.split`: whitespace runs, em-dashes and possibly hyphenated words.
fn split_chunks(text: &[char]) -> Vec<Vec<char>> {
let mut chunks: Vec<Vec<char>> = Vec::new();
let mut start: usize = 0;
while start < text.len() {
let end: usize = chunk_end(text, start);
chunks.push(text[start..end].to_vec());
start = end;
}
chunks
}

fn at(text: &[char], index: usize) -> Option<char> {
text.get(index).copied()
}

fn letter_at(text: &[char], index: Option<usize>) -> bool {
index
.and_then(|index| at(text, index))
.is_some_and(is_letter)
}

/// The end of a `-{2,}(?=\w)` match at `start`, if there is one.
fn em_dash_end(text: &[char], start: usize) -> Option<usize> {
let count: usize = text[start..]
.iter()
.take_while(|character| **character == '-')
.count();
(count >= MINIMUM_EM_DASH && at(text, start + count).is_some_and(is_word))
.then_some(start + count)
}

fn chunk_end(text: &[char], start: usize) -> usize {
if is_space(text[start]) {
return start
+ text[start..]
.iter()
.take_while(|character| is_space(**character))
.count();
}
if start > 0
&& is_word_punct(text[start - 1])
&& let Some(end) = em_dash_end(text, start)
{
return end;
}
let mut position: usize = start + 1;
loop {
if at(text, position) == Some('-') {
let two_letters: bool = letter_at(text, position.checked_sub(2))
&& letter_at(text, position.checked_sub(1));
let letter_hyphen_letter: bool = letter_at(text, position.checked_sub(3))
&& at(text, position.wrapping_sub(2)) == Some('-')
&& letter_at(text, position.checked_sub(1));
let ahead: bool = letter_at(text, Some(position + 1))
&& (letter_at(text, Some(position + 2))
|| (at(text, position + 2) == Some('-')
&& letter_at(text, Some(position + 3))));
if (two_letters || letter_hyphen_letter) && ahead {
return position + 1;
}
}
match at(text, position) {
None => return position,
Some(character) if is_space(character) => return position,
Some(_) => {}
}
if is_word_punct(text[position - 1]) && em_dash_end(text, position).is_some() {
return position;
}
position += 1;
}
}
2 changes: 0 additions & 2 deletions crates/sqlbuild-cli/src/argv/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ pub(crate) const MAX_HELP_POSITION: i64 = 24;
pub(crate) const MINIMUM_TEXT_WIDTH: i64 = 11;
/// `_long_break_matcher` folds longer runs of newlines into this many.
pub(crate) const MAXIMUM_BLANK_RUN: usize = 2;
/// `textwrap`'s em-dash is at least this many hyphens.
pub(crate) const MINIMUM_EM_DASH: usize = 2;

/// `CliNamespace` attributes the post-parse rules read and write.
pub(crate) const COMMAND_DEST: &str = "command";
Expand Down
8 changes: 5 additions & 3 deletions crates/sqlbuild-cli/src/argv/tests/test_text_wrap.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::argv::_helpers::text_wrap::{normalize_whitespace, wrap};
use sqlbuild_core::text::main::text_wrap::text_wrap;

use crate::argv::_helpers::text_wrap::normalize_whitespace;
use crate::argv::tests::test_types::WrapTestCase;

#[test]
Expand Down Expand Up @@ -29,10 +31,10 @@ fn given_help_text_when_wrapping_then_lines_match_python_textwrap() {

for test_case in test_cases {
assert_eq!(
wrap(
text_wrap(
&normalize_whitespace(test_case.text),
test_case.width,
test_case.indent
(test_case.indent, test_case.indent)
),
test_case.expected_lines,
"{}",
Expand Down
4 changes: 3 additions & 1 deletion crates/sqlbuild-cli/src/diff_report/_helpers/layout.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! The subset of Rich's console layout the diff summary uses: text, groups, tables and columns.

use crate::diff_report::_helpers::text_cells::{cell_len, wrap_line};
use sqlbuild_core::text::main::cell_len::cell_len;

use crate::diff_report::_helpers::text_cells::wrap_line;

/// Text attributes and one standard foreground color, as Rich renders them.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
Expand Down
39 changes: 2 additions & 37 deletions crates/sqlbuild-cli/src/diff_report/_helpers/text_cells.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,7 @@
//! Terminal cell widths and Rich's word wrapping of one line of text.

/// The terminal cells one character occupies: zero for combining marks, two for wide characters.
pub(crate) fn char_cells(character: char) -> usize {
let code: u32 = u32::from(character);
let zero_width: bool = matches!(
code,
0x0300..=0x036F | 0x200B..=0x200F | 0x20D0..=0x20FF | 0xFE00..=0xFE0F | 0xFE20..=0xFE2F
);
let wide: bool = matches!(
code,
0x1100..=0x115F
| 0x2E80..=0x303E
| 0x3041..=0x33FF
| 0x3400..=0x4DBF
| 0x4E00..=0x9FFF
| 0xA000..=0xA4CF
| 0xAC00..=0xD7A3
| 0xF900..=0xFAFF
| 0xFE30..=0xFE4F
| 0xFF00..=0xFF60
| 0xFFE0..=0xFFE6
| 0x1F300..=0x1F64F
| 0x1F900..=0x1F9FF
| 0x20000..=0x3FFFD
);
if zero_width {
0
} else if wide {
2
} else {
1
}
}

/// The terminal cells a string occupies.
pub(crate) fn cell_len(text: &str) -> usize {
text.chars().map(char_cells).sum()
}
use sqlbuild_core::text::main::cell_len::cell_len;
use sqlbuild_core::text::main::char_cells::char_cells;

/// The longest prefix of `text` that fits `width` cells, padded with spaces to exactly `width`.
pub(crate) fn set_cell_size(text: &str, width: usize) -> String {
Expand Down
Loading
Loading