tiny.smg.name
Defined in tiny.smg.
API (5)
Actions
Public operations.
Source
Source: tools/smg/src/name.zig
zig
const std = @import("std");const testing_fallback_match_count = @import("root.zig").default_limits.suggestions.fallback_match_count;pub fn matchesSuffix(name: []const u8, suffix: []const u8) bool { if (!std.mem.endsWith(u8, name, suffix)) return false; if (name.len == suffix.len) return true; return name[name.len - suffix.len - 1] == '.';}pub fn leaf(raw: []const u8) []const u8 { if (std.mem.lastIndexOfScalar(u8, raw, '/')) |slash| { const base = raw[slash + 1 ..]; const dot = std.mem.lastIndexOfScalar(u8, base, '.') orelse return base; if (dot == 0) return base; return base[0..dot]; } const dot = std.mem.lastIndexOfScalar(u8, raw, '.') orelse return raw; return raw[dot + 1 ..];}pub fn dottedFromPath(allocator: std.mem.Allocator, raw: []const u8) !?[]const u8 { if (std.mem.indexOfScalar(u8, raw, '/') == null) return null; var trimmed = raw; while (std.mem.startsWith(u8, trimmed, "./")) trimmed = trimmed[2..]; while (trimmed.len != 0 and trimmed[0] == '/') trimmed = trimmed[1..]; while (trimmed.len != 0 and trimmed[trimmed.len - 1] == '/') trimmed = trimmed[0 .. trimmed.len - 1]; if (trimmed.len == 0) return null; const base_start = if (std.mem.lastIndexOfScalar(u8, trimmed, '/')) |slash| slash + 1 else 0; if (std.mem.lastIndexOfScalar(u8, trimmed[base_start..], '.')) |dot| { if (dot != 0) trimmed = trimmed[0 .. base_start + dot]; } const stem = trimmed[base_start..]; if (std.mem.eql(u8, stem, "index") or std.mem.eql(u8, stem, "__init__")) { trimmed = if (base_start == 0) "root" else trimmed[0 .. base_start - 1]; } const out = try allocator.dupe(u8, trimmed); for (out) |*char| { if (char.* == '/') char.* = '.'; } return out;}pub fn matchesSubsequence(name: []const u8, query: []const u8) bool { const query_dot = std.mem.lastIndexOfScalar(u8, query, '.') orelse return false; const name_dot = std.mem.lastIndexOfScalar(u8, name, '.') orelse return false; if (!std.mem.eql(u8, name[name_dot + 1 ..], query[query_dot + 1 ..])) return false; var query_segments = std.mem.splitScalar(u8, query[0..query_dot], '.'); var name_segments = std.mem.splitScalar(u8, name[0..name_dot], '.'); var pending = query_segments.next(); while (pending) |segment| { const candidate = name_segments.next() orelse return false; if (std.mem.eql(u8, candidate, segment)) pending = query_segments.next(); } return true;}pub fn fallbackMatches(allocator: std.mem.Allocator, names: []const []const u8, raw: []const u8, limit: usize) ![]const []const u8 { if (try dottedFromPath(allocator, raw)) |translated| { defer allocator.free(translated); for (names) |name| { if (std.mem.eql(u8, name, translated)) { const out = try allocator.alloc([]const u8, 1); out[0] = name; return out; } } const suffixed = try collect(allocator, names, translated, matchesSuffix, limit); if (suffixed.len != 0) return suffixed; return try collect(allocator, names, translated, matchesSubsequence, limit); } return try collect(allocator, names, raw, matchesSubsequence, limit);}fn collect(allocator: std.mem.Allocator, names: []const []const u8, query: []const u8, matches: fn ([]const u8, []const u8) bool, limit: usize) ![]const []const u8 { var out: std.ArrayList([]const u8) = .empty; errdefer out.deinit(allocator); for (names) |name| { if (!matches(name, query)) continue; if (out.items.len == limit) { out.deinit(allocator); return &.{}; } try out.append(allocator, name); } std.mem.sort([]const u8, out.items, {}, stringLess); return try out.toOwnedSlice(allocator);}fn stringLess(_: void, a: []const u8, b: []const u8) bool { return std.mem.lessThan(u8, a, b);}test "suffix matches whole dotted segments only" { try std.testing.expect(matchesSuffix("app.main", "main")); try std.testing.expect(matchesSuffix("app.main", "app.main")); try std.testing.expect(!matchesSuffix("app.domain", "main")); try std.testing.expect(!matchesSuffix("main", "app.main"));}test "leaf strips qualification for dotted names and extensions for paths" { try std.testing.expectEqualStrings("merge", leaf("lib.tracker.src.store.merge")); try std.testing.expectEqualStrings("merge", leaf("lib/tracker/src/store/merge.zig")); try std.testing.expectEqualStrings("tinyalloc", leaf("lib/tinyalloc")); try std.testing.expectEqualStrings(".hidden", leaf("dir/.hidden")); try std.testing.expectEqualStrings("plain", leaf("plain"));}test "dotted translation maps repo paths onto node names" { const allocator = std.testing.allocator; const translated = (try dottedFromPath(allocator, "lib/tracker/src/store/merge.zig")).?; defer allocator.free(translated); try std.testing.expectEqualStrings("lib.tracker.src.store.merge", translated); const trimmed = (try dottedFromPath(allocator, "./lib/term/src/")).?; defer allocator.free(trimmed); try std.testing.expectEqualStrings("lib.term.src", trimmed); const bare = (try dottedFromPath(allocator, "lib/tinyalloc")).?; defer allocator.free(bare); try std.testing.expectEqualStrings("lib.tinyalloc", bare); const index = (try dottedFromPath(allocator, "tools/glom/src/index.zig")).?; defer allocator.free(index); try std.testing.expectEqualStrings("tools.glom.src", index); const init = (try dottedFromPath(allocator, "analysis/src/analysis/__init__.py")).?; defer allocator.free(init); try std.testing.expectEqualStrings("analysis.src.analysis", init); try std.testing.expectEqual(@as(?[]const u8, null), try dottedFromPath(allocator, "app.main"));}test "subsequence anchors the leaf and orders the qualifiers" { try std.testing.expect(matchesSubsequence("lib.choir.src.core.context.dialects", "choir.context.dialects")); try std.testing.expect(matchesSubsequence("lib.tracker.src.store.merge", "store.merge")); try std.testing.expect(matchesSubsequence("app.main", "app.main")); try std.testing.expect(!matchesSubsequence("lib.choir.src.core.dialects", "choir.context.dialects")); try std.testing.expect(!matchesSubsequence("lib.choir.src.context.core.dialects", "choir.core.context.dialects")); try std.testing.expect(!matchesSubsequence("lib.choir.src.core.context.dialects", "dialects")); try std.testing.expect(!matchesSubsequence("dialects", "choir.context.dialects"));}test "fallback resolves path forms before subsequence forms" { const allocator = std.testing.allocator; const names = [_][]const u8{ "lib.choir.src.core.dialects", "lib.choir.src.core.context.dialects", "lib.tracker.src.store.merge", "lib.tracker.src.store", }; const path_hit = try fallbackMatches(allocator, &names, "lib/tracker/src/store/merge.zig", testing_fallback_match_count); defer allocator.free(path_hit); try std.testing.expectEqual(@as(usize, 1), path_hit.len); try std.testing.expectEqualStrings("lib.tracker.src.store.merge", path_hit[0]); const relative = try fallbackMatches(allocator, &names, "src/store/merge.zig", testing_fallback_match_count); defer allocator.free(relative); try std.testing.expectEqual(@as(usize, 1), relative.len); try std.testing.expectEqualStrings("lib.tracker.src.store.merge", relative[0]); const subsequence = try fallbackMatches(allocator, &names, "choir.context.dialects", testing_fallback_match_count); defer allocator.free(subsequence); try std.testing.expectEqual(@as(usize, 1), subsequence.len); try std.testing.expectEqualStrings("lib.choir.src.core.context.dialects", subsequence[0]); const ambiguous = try fallbackMatches(allocator, &names, "core.dialects", testing_fallback_match_count); defer allocator.free(ambiguous); try std.testing.expectEqual(@as(usize, 2), ambiguous.len); const missing = try fallbackMatches(allocator, &names, "absent.node", testing_fallback_match_count); defer allocator.free(missing); try std.testing.expectEqual(@as(usize, 0), missing.len);}test "fallback declares no resolution beyond the candidate bound" { const allocator = std.testing.allocator; var names: std.ArrayList([]const u8) = .empty; defer { for (names.items) |name| allocator.free(name); names.deinit(allocator); } for (0..testing_fallback_match_count + 1) |index| { try names.append(allocator, try std.fmt.allocPrint(allocator, "pkg{d}.src.core.leaf", .{index})); } const flooded = try fallbackMatches(allocator, names.items, "core.leaf", testing_fallback_match_count); defer allocator.free(flooded); try std.testing.expectEqual(@as(usize, 0), flooded.len);}Source: tools/smg/src/root.zig:27
zig
pub const name = @import("name.zig");Audit
| Definitions | 6 |
|---|---|
| Public names | 6 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |