Skip to content

Commit a1d313e

Browse files
authored
refactor: run build, audit, scenario, query, dag, contract, init, format and cost natively (#1103)
* feat: run sqb audit natively * refactor(lint): neutralize interpolation, apply suppressions and scan fixture nulls natively Interpolation sentinels for lint and formatting (references, macros, templates, hook context and generic-audit parameters), sentinel restoration, interpolation tokens at an offset, reason-required suppressions and the fixture typed-null candidate scan run in the new sqlbuild-rules lint_text module. SQL lint preparation now handles non-ASCII text natively with character offsets, so the Python fallback is gone. Delete the Python token, suppression and candidate modules. * refactor(lint): format SQL bodies natively Body ranges by project role, trailing-whitespace and omitted ceremonial SELECT handling, sentinel widths, deduplicated batch formatting, restoration and splicing, and the format-unsafe faults run in sqlbuild-rules. Delete the Python body formatter. * refactor(lint): lint and map SQL bodies natively Build the native lint requests (rule selection, thresholds, identifiers, relation keys and the omitted ceremonial SELECT), run them in one deduplicated batch and map every diagnostic, end position and fix back through the expansion passes onto authored source in sqlbuild-rules. Parse failures become authored faults as before. Delete the Python native SQL lint boundary and its engine-response injection tests. * chore: update the Python boundary ratchet * test: report interpolation restore failures without branching in the lint helper * feat: run sqb query natively * feat: run sqb dag natively * chore: update the Python boundary ratchet * refactor(core): scenario command selection, namespaces, result rows and runtime SQL * feat: run sqb scenario test and capture natively * chore: update the Python boundary ratchet * feat: run sqb contract natively * feat: run sqb init natively * chore: update the Python boundary ratchet * refactor: share terminal cell widths and text wrapping in sqlbuild-core * refactor(format): render format reports and resolve formatter selectors in sqlbuild-core * feat: run sqb format natively * refactor(lint): run lint passes and collect lint inputs natively * chore: update the Python boundary ratchet * feat: run sqb cost natively * chore: update the Python boundary ratchet * refactor(core): build command safety gates, limits, run context, timings and Python-node rows * feat: enforce build plan safety gates natively * feat: run sqb build natively * chore: update the Python boundary ratchet * test: raise the dense 10k all-rules compile memory budget
1 parent fac17f3 commit a1d313e

411 files changed

Lines changed: 20091 additions & 11950 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/sqlbuild-cli/src/argv/_helpers/formatting.rs‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! `argparse.HelpFormatter`'s help, usage and version text in the Python 3.13 layout.
22
3-
use crate::argv::_helpers::text_wrap::{normalize_whitespace, wrap};
3+
use sqlbuild_core::text::main::text_wrap::text_wrap;
4+
5+
use crate::argv::_helpers::text_wrap::normalize_whitespace;
46
use crate::argv::constants::{
57
INDENT_INCREMENT, MAX_HELP_POSITION, MAXIMUM_BLANK_RUN, MINIMUM_TEXT_WIDTH, USAGE_PREFIX,
68
VERSION_PLACEHOLDER,
@@ -130,7 +132,11 @@ fn help_text(help: &HelpText) -> Option<&str> {
130132
fn format_text(formatter: &Formatter, text: &str, indent: i64) -> String {
131133
let width: i64 = (formatter.width - indent).max(MINIMUM_TEXT_WIDTH);
132134
let padding: String = " ".repeat(indent as usize);
133-
let lines: Vec<String> = wrap(&normalize_whitespace(text), width as usize, &padding);
135+
let lines: Vec<String> = text_wrap(
136+
&normalize_whitespace(text),
137+
width as usize,
138+
(&padding, &padding),
139+
);
134140
format!("{}\n\n", lines.join("\n"))
135141
}
136142

@@ -179,7 +185,7 @@ fn format_action(formatter: &Formatter, entry: &HelpEntry, indent: i64) -> Strin
179185
}
180186
};
181187
let lines: Vec<String> = help
182-
.map(|help| wrap(&normalize_whitespace(help), help_width as usize, ""))
188+
.map(|help| text_wrap(&normalize_whitespace(help), help_width as usize, ("", "")))
183189
.unwrap_or_default();
184190
if let Some((first, rest)) = lines.split_first() {
185191
text.push_str(&format!("{}{first}\n", " ".repeat(indent_first as usize)));
Lines changed: 1 addition & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
//! Python's `textwrap.wrap` with its default options, as `argparse` uses it.
2-
3-
use sqlbuild_core::text::main::is_decimal_digit::is_decimal_digit;
4-
use sqlbuild_core::text::main::python_isalnum::python_isalnum;
5-
6-
use crate::argv::constants::MINIMUM_EM_DASH;
1+
//! Whitespace normalization `argparse` applies before wrapping help text.
72
83
/// `_whitespace_matcher.sub(' ', text).strip()`: ASCII whitespace runs become one space.
94
pub(crate) fn normalize_whitespace(text: &str) -> String {
@@ -22,165 +17,3 @@ pub(crate) fn normalize_whitespace(text: &str) -> String {
2217
}
2318
normalized
2419
}
25-
26-
/// `textwrap.wrap(text, width, initial_indent=indent, subsequent_indent=indent)`.
27-
pub(crate) fn wrap(text: &str, width: usize, indent: &str) -> Vec<String> {
28-
let characters: Vec<char> = text.chars().collect();
29-
let mut chunks: Vec<Vec<char>> = split_chunks(&characters);
30-
chunks.reverse();
31-
let indent_length: usize = indent.chars().count();
32-
let mut lines: Vec<String> = Vec::new();
33-
while !chunks.is_empty() {
34-
let mut line: Vec<Vec<char>> = Vec::new();
35-
let mut length: usize = 0;
36-
let available: i64 = width as i64 - indent_length as i64;
37-
if !lines.is_empty() && chunks.last().is_some_and(|chunk| is_blank(chunk)) {
38-
chunks.pop();
39-
}
40-
while let Some(chunk) = chunks.last() {
41-
if (length + chunk.len()) as i64 <= available {
42-
length += chunk.len();
43-
line.push(chunks.pop().unwrap_or_default());
44-
} else {
45-
break;
46-
}
47-
}
48-
if let Some(chunk) = chunks.pop_if(|chunk| chunk.len() as i64 > available) {
49-
let (head, rest) = split_long_word(chunk, line.is_empty(), length, available);
50-
line.extend(head);
51-
chunks.extend(rest);
52-
}
53-
if line.last().is_some_and(|chunk| is_blank(chunk)) {
54-
line.pop();
55-
}
56-
if !line.is_empty() {
57-
let joined: String = line.iter().flatten().collect();
58-
lines.push(format!("{indent}{joined}"));
59-
}
60-
}
61-
lines
62-
}
63-
64-
/// `_handle_long_word`: the part of a too-long chunk the line takes, and what stays queued.
65-
fn split_long_word(
66-
mut chunk: Vec<char>,
67-
line_is_empty: bool,
68-
length: usize,
69-
width: i64,
70-
) -> (Option<Vec<char>>, Option<Vec<char>>) {
71-
let space_left: i64 = if width < 1 { 1 } else { width - length as i64 };
72-
if space_left <= 0 {
73-
return if line_is_empty {
74-
(Some(chunk), None)
75-
} else {
76-
(None, Some(chunk))
77-
};
78-
}
79-
let space_left: usize = space_left as usize;
80-
let mut end: usize = space_left.min(chunk.len());
81-
if chunk.len() > space_left
82-
&& let Some(hyphen) = chunk[..space_left]
83-
.iter()
84-
.rposition(|character| *character == '-')
85-
&& hyphen > 0
86-
&& chunk[..hyphen].iter().any(|character| *character != '-')
87-
{
88-
end = hyphen + 1;
89-
}
90-
let rest: Vec<char> = chunk.split_off(end);
91-
(Some(chunk), Some(rest))
92-
}
93-
94-
fn is_blank(chunk: &[char]) -> bool {
95-
chunk.iter().all(|character| character.is_whitespace())
96-
}
97-
98-
fn is_space(character: char) -> bool {
99-
matches!(character, '\t' | '\n' | '\x0b' | '\x0c' | '\r' | ' ')
100-
}
101-
102-
fn is_word(character: char) -> bool {
103-
character == '_' || python_isalnum(character)
104-
}
105-
106-
fn is_word_punct(character: char) -> bool {
107-
is_word(character) || matches!(character, '!' | '"' | '\'' | '&' | '.' | ',' | '?')
108-
}
109-
110-
fn is_letter(character: char) -> bool {
111-
is_word(character) && !is_decimal_digit(character)
112-
}
113-
114-
/// `TextWrapper.wordsep_re.split`: whitespace runs, em-dashes and possibly hyphenated words.
115-
fn split_chunks(text: &[char]) -> Vec<Vec<char>> {
116-
let mut chunks: Vec<Vec<char>> = Vec::new();
117-
let mut start: usize = 0;
118-
while start < text.len() {
119-
let end: usize = chunk_end(text, start);
120-
chunks.push(text[start..end].to_vec());
121-
start = end;
122-
}
123-
chunks
124-
}
125-
126-
fn at(text: &[char], index: usize) -> Option<char> {
127-
text.get(index).copied()
128-
}
129-
130-
fn letter_at(text: &[char], index: Option<usize>) -> bool {
131-
index
132-
.and_then(|index| at(text, index))
133-
.is_some_and(is_letter)
134-
}
135-
136-
/// The end of a `-{2,}(?=\w)` match at `start`, if there is one.
137-
fn em_dash_end(text: &[char], start: usize) -> Option<usize> {
138-
let count: usize = text[start..]
139-
.iter()
140-
.take_while(|character| **character == '-')
141-
.count();
142-
(count >= MINIMUM_EM_DASH && at(text, start + count).is_some_and(is_word))
143-
.then_some(start + count)
144-
}
145-
146-
fn chunk_end(text: &[char], start: usize) -> usize {
147-
if is_space(text[start]) {
148-
return start
149-
+ text[start..]
150-
.iter()
151-
.take_while(|character| is_space(**character))
152-
.count();
153-
}
154-
if start > 0
155-
&& is_word_punct(text[start - 1])
156-
&& let Some(end) = em_dash_end(text, start)
157-
{
158-
return end;
159-
}
160-
let mut position: usize = start + 1;
161-
loop {
162-
if at(text, position) == Some('-') {
163-
let two_letters: bool = letter_at(text, position.checked_sub(2))
164-
&& letter_at(text, position.checked_sub(1));
165-
let letter_hyphen_letter: bool = letter_at(text, position.checked_sub(3))
166-
&& at(text, position.wrapping_sub(2)) == Some('-')
167-
&& letter_at(text, position.checked_sub(1));
168-
let ahead: bool = letter_at(text, Some(position + 1))
169-
&& (letter_at(text, Some(position + 2))
170-
|| (at(text, position + 2) == Some('-')
171-
&& letter_at(text, Some(position + 3))));
172-
if (two_letters || letter_hyphen_letter) && ahead {
173-
return position + 1;
174-
}
175-
}
176-
match at(text, position) {
177-
None => return position,
178-
Some(character) if is_space(character) => return position,
179-
Some(_) => {}
180-
}
181-
if is_word_punct(text[position - 1]) && em_dash_end(text, position).is_some() {
182-
return position;
183-
}
184-
position += 1;
185-
}
186-
}

‎crates/sqlbuild-cli/src/argv/constants.rs‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ pub(crate) const MAX_HELP_POSITION: i64 = 24;
2020
pub(crate) const MINIMUM_TEXT_WIDTH: i64 = 11;
2121
/// `_long_break_matcher` folds longer runs of newlines into this many.
2222
pub(crate) const MAXIMUM_BLANK_RUN: usize = 2;
23-
/// `textwrap`'s em-dash is at least this many hyphens.
24-
pub(crate) const MINIMUM_EM_DASH: usize = 2;
2523

2624
/// `CliNamespace` attributes the post-parse rules read and write.
2725
pub(crate) const COMMAND_DEST: &str = "command";

‎crates/sqlbuild-cli/src/argv/tests/test_text_wrap.rs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::argv::_helpers::text_wrap::{normalize_whitespace, wrap};
1+
use sqlbuild_core::text::main::text_wrap::text_wrap;
2+
3+
use crate::argv::_helpers::text_wrap::normalize_whitespace;
24
use crate::argv::tests::test_types::WrapTestCase;
35

46
#[test]
@@ -29,10 +31,10 @@ fn given_help_text_when_wrapping_then_lines_match_python_textwrap() {
2931

3032
for test_case in test_cases {
3133
assert_eq!(
32-
wrap(
34+
text_wrap(
3335
&normalize_whitespace(test_case.text),
3436
test_case.width,
35-
test_case.indent
37+
(test_case.indent, test_case.indent)
3638
),
3739
test_case.expected_lines,
3840
"{}",

‎crates/sqlbuild-cli/src/diff_report/_helpers/layout.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! The subset of Rich's console layout the diff summary uses: text, groups, tables and columns.
22
3-
use crate::diff_report::_helpers::text_cells::{cell_len, wrap_line};
3+
use sqlbuild_core::text::main::cell_len::cell_len;
4+
5+
use crate::diff_report::_helpers::text_cells::wrap_line;
46

57
/// Text attributes and one standard foreground color, as Rich renders them.
68
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]

‎crates/sqlbuild-cli/src/diff_report/_helpers/text_cells.rs‎

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,7 @@
11
//! Terminal cell widths and Rich's word wrapping of one line of text.
22
3-
/// The terminal cells one character occupies: zero for combining marks, two for wide characters.
4-
pub(crate) fn char_cells(character: char) -> usize {
5-
let code: u32 = u32::from(character);
6-
let zero_width: bool = matches!(
7-
code,
8-
0x0300..=0x036F | 0x200B..=0x200F | 0x20D0..=0x20FF | 0xFE00..=0xFE0F | 0xFE20..=0xFE2F
9-
);
10-
let wide: bool = matches!(
11-
code,
12-
0x1100..=0x115F
13-
| 0x2E80..=0x303E
14-
| 0x3041..=0x33FF
15-
| 0x3400..=0x4DBF
16-
| 0x4E00..=0x9FFF
17-
| 0xA000..=0xA4CF
18-
| 0xAC00..=0xD7A3
19-
| 0xF900..=0xFAFF
20-
| 0xFE30..=0xFE4F
21-
| 0xFF00..=0xFF60
22-
| 0xFFE0..=0xFFE6
23-
| 0x1F300..=0x1F64F
24-
| 0x1F900..=0x1F9FF
25-
| 0x20000..=0x3FFFD
26-
);
27-
if zero_width {
28-
0
29-
} else if wide {
30-
2
31-
} else {
32-
1
33-
}
34-
}
35-
36-
/// The terminal cells a string occupies.
37-
pub(crate) fn cell_len(text: &str) -> usize {
38-
text.chars().map(char_cells).sum()
39-
}
3+
use sqlbuild_core::text::main::cell_len::cell_len;
4+
use sqlbuild_core::text::main::char_cells::char_cells;
405

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

0 commit comments

Comments
 (0)