Skip to content

Commit 06f0269

Browse files
authored
Extract shared project utilities (#29)
1 parent b6e7ba3 commit 06f0269

4 files changed

Lines changed: 47 additions & 45 deletions

File tree

PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ Exit criteria:
569569

570570
- M0 is complete as the Zig `0.16.0` runtime scaffold.
571571
- M1 has started on stacked branches with native Zig artifact-first parser slices.
572-
- M1A has started with behavior-preserving `src/project/types.zig`, `src/project/selector.zig`, and `src/project/manifest.zig` extractions. `src/project.zig` remains the public parser/list facade and still owns most loader, parser, and resolver code until follow-up extractions move those pieces behind focused internal modules.
572+
- M1A has started with behavior-preserving `src/project/types.zig`, `src/project/selector.zig`, `src/project/manifest.zig`, and `src/project/util.zig` extractions. `src/project.zig` remains the public parser/list facade and still owns most loader, parser, and resolver code until follow-up extractions move those pieces behind focused internal modules.
573573
- `dxt parse` now targets the supported Tier 0 subset: project name/model paths/seed paths/macro paths, project and package model path configs for literal `+materialized`, `+tags`, and model/seed `+docs.node_color`, root-project model config overrides for installed packages, SQL model discovery, CSV seed discovery, installed package SQL model and CSV seed discovery from `dbt_packages`, source discovery, installed package source discovery, exposure discovery, installed package exposure discovery, project macro discovery, installed package macro discovery from `dbt_packages`, macro property YAML for project macro descriptions and arguments, project and package docs block discovery, literal `ref` to models or seeds, two-argument package refs, package-local refs in installed package models and exposures, unique installed-package fallback for unqualified refs, literal `source`, package-local sources in installed package models, unique installed-package fallback for unqualified sources, literal `doc` in project and package descriptions, inline `config(materialized=..., tags=...)`, known project/package-qualified/package-local macro call dependencies, narrow project and package YAML model properties for scalar descriptions, simple columns, tags, materialization, disabled SQL models, dbt-shaped `unique`, `not_null`, `accepted_values`, and `relationships` generic test nodes, model/test `refs` and `sources` artifact fields, dependency maps, and deterministic partial `manifest.json`. The manifest includes the v12 top-level maps needed by the M1 artifact shape and is covered by a pinned local dbt Manifest v12 schema slice. YAML generic test arguments are currently supported for scalar values plus inline and block lists required by public Jaffle Shop DuckDB-style tests.
574574
- `dxt ls` now lists dbt-selectable resources from the same parser graph with stable text/JSON output and basic name/FQN wildcards, tag wildcards, slash-aware `path:` wildcards, exact `package:`/`package:this`, `source:` wildcards including package-qualified source selectors, `exposure:` wildcards, `resource_type:`, `test_type:generic`, config materialization, comma intersection, whitespace union, multi-argument selector lists, repeated selector flags, leading/trailing `+` graph expansion, and exact exclude filters; macros are emitted in artifacts but not exposed as `ls` resources.
575575
- Synthetic fixtures cover one model, model refs, seed refs, source refs, exposure refs to models and sources, combined source/model YAML, inline config/tag selection, config materialization selection, comma-intersection selection, YAML model properties and columns, emitted `unique`, `not_null`, `accepted_values`, and `relationships` generic test nodes, project macro artifacts and macro properties, configured `macro-paths` replacing the default macro directory, installed package macros with package-qualified calls and package-local macro calls, installed package models, seeds, sources, docs, exposures, package YAML model properties, root package config overrides, and package-qualified/package-local refs/sources, macro calls recorded in model and macro `depends_on.macros`, docs blocks with literal `doc` descriptions, disabled models, disabled ref diagnostics, unmatched model-property warnings, duplicate model and docs diagnostics, unsupported dynamic ref/doc diagnostics, missing doc diagnostics, malformed docs block diagnostics, unresolved package macro diagnostics, and unsupported unknown macro-call diagnostics.

src/project.zig

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const Io = std.Io;
33
const manifest = @import("project/manifest.zig");
44
const selector = @import("project/selector.zig");
55
const types = @import("project/types.zig");
6+
const util = @import("project/util.zig");
67

78
pub const Runtime = types.Runtime;
89
pub const Options = types.Options;
@@ -54,7 +55,7 @@ pub fn parse(runtime: Runtime, options: Options, stdout: *Io.Writer, stderr: *Io
5455
active_seeds,
5556
graph.sources.items.len,
5657
countActiveExposures(&graph),
57-
normalizeForDisplay(manifest_path),
58+
util.normalizeForDisplay(manifest_path),
5859
});
5960
}
6061

@@ -1650,10 +1651,10 @@ fn isSupportedGenericTest(test_def: GenericTestDef) bool {
16501651

16511652
fn writeWarnings(stderr: *Io.Writer, graph: *const Graph) !void {
16521653
for (graph.unmatched_model_properties.items) |property| {
1653-
try stderr.print("warning: did not find matching node for model property `{s}` in {s}\n", .{ property.name, normalizeForDisplay(property.patch_path) });
1654+
try stderr.print("warning: did not find matching node for model property `{s}` in {s}\n", .{ property.name, util.normalizeForDisplay(property.patch_path) });
16541655
}
16551656
for (graph.unmatched_macro_properties.items) |property| {
1656-
try stderr.print("warning: did not find matching macro for macro property `{s}` in {s}\n", .{ property.name, normalizeForDisplay(property.patch_path) });
1657+
try stderr.print("warning: did not find matching macro for macro property `{s}` in {s}\n", .{ property.name, util.normalizeForDisplay(property.patch_path) });
16571658
}
16581659
}
16591660

@@ -2466,10 +2467,6 @@ fn pathJoin(allocator: std.mem.Allocator, parts: []const []const u8) ![]const u8
24662467
return try std.fs.path.join(allocator, parts);
24672468
}
24682469

2469-
fn normalizeForDisplay(path: []const u8) []const u8 {
2470-
return path;
2471-
}
2472-
24732470
fn sortStrings(values: [][]const u8) void {
24742471
std.mem.sort([]const u8, values, {}, struct {
24752472
fn lessThan(_: void, a: []const u8, b: []const u8) bool {
@@ -2779,18 +2776,11 @@ fn testNameFromYamlItem(allocator: std.mem.Allocator, value: []const u8) ![]cons
27792776
}
27802777

27812778
fn appendUnique(allocator: std.mem.Allocator, values: *std.ArrayList([]const u8), value: []const u8) !void {
2782-
if (!containsString(values.items, value)) {
2779+
if (!util.containsString(values.items, value)) {
27832780
try values.append(allocator, value);
27842781
}
27852782
}
27862783

2787-
fn containsString(values: []const []const u8, value: []const u8) bool {
2788-
for (values) |candidate| {
2789-
if (std.mem.eql(u8, candidate, value)) return true;
2790-
}
2791-
return false;
2792-
}
2793-
27942784
test "project yaml parser reads dbt name and inline model paths" {
27952785
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
27962786
defer arena.deinit();

src/project/manifest.zig

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const std = @import("std");
22
const Io = std.Io;
33
const selector = @import("selector.zig");
44
const types = @import("types.zig");
5+
const util = @import("util.zig");
56

67
const Graph = types.Graph;
78
const Node = types.Node;
@@ -72,7 +73,7 @@ pub fn renderManifest(allocator: std.mem.Allocator, graph: *const Graph) ![]cons
7273
try writer.writeAll(",\"name\":");
7374
try writeJsonString(writer, source.table_name);
7475
try writer.writeAll(",\"original_file_path\":");
75-
try writeJsonString(writer, normalizeForDisplay(source.original_file_path));
76+
try writeJsonString(writer, util.normalizeForDisplay(source.original_file_path));
7677
try writer.writeAll("}");
7778
}
7879
try writer.writeAll("\n },\n \"macros\": {");
@@ -95,9 +96,9 @@ pub fn renderManifest(allocator: std.mem.Allocator, graph: *const Graph) ![]cons
9596
try writer.writeAll(",\"name\":");
9697
try writeJsonString(writer, doc.name);
9798
try writer.writeAll(",\"path\":");
98-
try writeJsonString(writer, normalizeForDisplay(doc.path));
99+
try writeJsonString(writer, util.normalizeForDisplay(doc.path));
99100
try writer.writeAll(",\"original_file_path\":");
100-
try writeJsonString(writer, normalizeForDisplay(doc.original_file_path));
101+
try writeJsonString(writer, util.normalizeForDisplay(doc.original_file_path));
101102
try writer.writeAll(",\"block_contents\":");
102103
try writeJsonString(writer, doc.block_contents);
103104
try writer.writeAll("}");
@@ -186,22 +187,22 @@ fn writeChildMapEntry(writer: *Io.Writer, graph: *const Graph, unique_id: []cons
186187
var child_first = true;
187188
for (graph.nodes.items) |node| {
188189
if (!node.enabled) continue;
189-
if (containsString(node.depends_on.items, unique_id)) {
190+
if (util.containsString(node.depends_on.items, unique_id)) {
190191
if (!child_first) try writer.writeAll(",");
191192
child_first = false;
192193
try writeJsonString(writer, node.unique_id);
193194
}
194195
}
195196
for (graph.tests.items) |test_node| {
196-
if (containsString(test_node.depends_on.items, unique_id)) {
197+
if (util.containsString(test_node.depends_on.items, unique_id)) {
197198
if (!child_first) try writer.writeAll(",");
198199
child_first = false;
199200
try writeJsonString(writer, test_node.unique_id);
200201
}
201202
}
202203
for (graph.exposures.items) |exposure| {
203204
if (!exposure.enabled) continue;
204-
if (containsString(exposure.depends_on.items, unique_id)) {
205+
if (util.containsString(exposure.depends_on.items, unique_id)) {
205206
if (!child_first) try writer.writeAll(",");
206207
child_first = false;
207208
try writeJsonString(writer, exposure.unique_id);
@@ -226,9 +227,9 @@ fn writeMacroNode(allocator: std.mem.Allocator, writer: *Io.Writer, macro: Macro
226227
try writer.writeAll(",\"name\":");
227228
try writeJsonString(writer, macro.name);
228229
try writer.writeAll(",\"path\":");
229-
try writeJsonString(writer, normalizeForDisplay(macro.path));
230+
try writeJsonString(writer, util.normalizeForDisplay(macro.path));
230231
try writer.writeAll(",\"original_file_path\":");
231-
try writeJsonString(writer, normalizeForDisplay(macro.original_file_path));
232+
try writeJsonString(writer, util.normalizeForDisplay(macro.original_file_path));
232233
try writer.writeAll(",\"macro_sql\":");
233234
try writeJsonString(writer, macro.macro_sql);
234235
try writer.writeAll(",\"depends_on\":{\"macros\":");
@@ -237,7 +238,7 @@ fn writeMacroNode(allocator: std.mem.Allocator, writer: *Io.Writer, macro: Macro
237238
try writeJsonString(writer, macro.description);
238239
try writer.writeAll(",\"meta\":{},\"docs\":{\"show\":true,\"node_color\":null},\"patch_path\":");
239240
if (macro.patch_path) |patch_path| {
240-
const dbt_patch_path = try std.fmt.allocPrint(allocator, "{s}://{s}", .{ macro.package_name, normalizeForDisplay(patch_path) });
241+
const dbt_patch_path = try std.fmt.allocPrint(allocator, "{s}://{s}", .{ macro.package_name, util.normalizeForDisplay(patch_path) });
241242
defer allocator.free(dbt_patch_path);
242243
try writeJsonString(writer, dbt_patch_path);
243244
} else {
@@ -256,9 +257,9 @@ fn writeExposureNode(writer: *Io.Writer, exposure: ExposureDef) !void {
256257
try writer.writeAll(",\"name\":");
257258
try writeJsonString(writer, exposure.name);
258259
try writer.writeAll(",\"path\":");
259-
try writeJsonString(writer, normalizeForDisplay(exposure.path));
260+
try writeJsonString(writer, util.normalizeForDisplay(exposure.path));
260261
try writer.writeAll(",\"original_file_path\":");
261-
try writeJsonString(writer, normalizeForDisplay(exposure.original_file_path));
262+
try writeJsonString(writer, util.normalizeForDisplay(exposure.original_file_path));
262263
try writer.writeAll(",\"fqn\":[");
263264
try writeJsonString(writer, exposure.package_name);
264265
try writer.writeAll(",");
@@ -306,12 +307,12 @@ fn writeModelNode(allocator: std.mem.Allocator, writer: *Io.Writer, node: Node)
306307
try writer.writeAll(",\"name\":");
307308
try writeJsonString(writer, node.name);
308309
try writer.writeAll(",\"path\":");
309-
try writeJsonString(writer, normalizeForDisplay(node.path));
310+
try writeJsonString(writer, util.normalizeForDisplay(node.path));
310311
try writer.writeAll(",\"original_file_path\":");
311-
try writeJsonString(writer, normalizeForDisplay(node.original_file_path));
312+
try writeJsonString(writer, util.normalizeForDisplay(node.original_file_path));
312313
try writer.writeAll(",\"patch_path\":");
313314
if (node.patch_path) |patch_path| {
314-
const dbt_patch_path = try std.fmt.allocPrint(allocator, "{s}://{s}", .{ node.package_name, normalizeForDisplay(patch_path) });
315+
const dbt_patch_path = try std.fmt.allocPrint(allocator, "{s}://{s}", .{ node.package_name, util.normalizeForDisplay(patch_path) });
315316
defer allocator.free(dbt_patch_path);
316317
try writeJsonString(writer, dbt_patch_path);
317318
} else {
@@ -364,9 +365,9 @@ fn writeSeedNode(writer: *Io.Writer, node: Node) !void {
364365
try writer.writeAll(",\"name\":");
365366
try writeJsonString(writer, node.name);
366367
try writer.writeAll(",\"path\":");
367-
try writeJsonString(writer, normalizeForDisplay(node.path));
368+
try writeJsonString(writer, util.normalizeForDisplay(node.path));
368369
try writer.writeAll(",\"original_file_path\":");
369-
try writeJsonString(writer, normalizeForDisplay(node.original_file_path));
370+
try writeJsonString(writer, util.normalizeForDisplay(node.original_file_path));
370371
try writer.writeAll(",\"config\":{\"enabled\":");
371372
try writer.writeAll(if (node.enabled) "true" else "false");
372373
try writer.writeAll(",\"materialized\":\"seed\",\"docs\":");
@@ -388,9 +389,9 @@ fn writeGenericTestNode(allocator: std.mem.Allocator, writer: *Io.Writer, test_n
388389
try writer.writeAll(",\"alias\":");
389390
try writeJsonString(writer, test_node.alias);
390391
try writer.writeAll(",\"path\":");
391-
try writeJsonString(writer, normalizeForDisplay(test_node.path));
392+
try writeJsonString(writer, util.normalizeForDisplay(test_node.path));
392393
try writer.writeAll(",\"original_file_path\":");
393-
try writeJsonString(writer, normalizeForDisplay(test_node.original_file_path));
394+
try writeJsonString(writer, util.normalizeForDisplay(test_node.original_file_path));
394395
try writer.writeAll(",\"patch_path\":null,\"language\":\"sql\",\"raw_code\":");
395396
try writeJsonString(writer, test_node.raw_code);
396397
try writer.writeAll(",\"attached_node\":");
@@ -551,14 +552,3 @@ fn modelNameFromUniqueId(unique_id: []const u8) []const u8 {
551552
}
552553
return unique_id;
553554
}
554-
555-
fn normalizeForDisplay(path: []const u8) []const u8 {
556-
return path;
557-
}
558-
559-
fn containsString(values: []const []const u8, value: []const u8) bool {
560-
for (values) |candidate| {
561-
if (std.mem.eql(u8, candidate, value)) return true;
562-
}
563-
return false;
564-
}

src/project/util.zig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const std = @import("std");
2+
3+
pub fn normalizeForDisplay(path: []const u8) []const u8 {
4+
return path;
5+
}
6+
7+
pub fn containsString(values: []const []const u8, value: []const u8) bool {
8+
for (values) |candidate| {
9+
if (std.mem.eql(u8, candidate, value)) return true;
10+
}
11+
return false;
12+
}
13+
14+
test "containsString matches exact byte strings only" {
15+
const values = [_][]const u8{ "customers", "orders" };
16+
try std.testing.expect(containsString(&values, "customers"));
17+
try std.testing.expect(!containsString(&values, "customer"));
18+
}
19+
20+
test "normalizeForDisplay preserves relative paths" {
21+
try std.testing.expectEqualStrings("models/customers.sql", normalizeForDisplay("models/customers.sql"));
22+
}

0 commit comments

Comments
 (0)