Skip to documentation
SLOP

tiny.smg.scan.scan_files

Reference tiny.smg scan scan_files

Defined in scan.

API (22)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callsscanscan files
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsprivate; no linktools.smg.src.scan.filescollectFilesystemSupportedprivate; no linktools.smg.src.scan.filescollectGitSupportedscan.scan_filescollectSupportedscan.scan_filescontainsscan.scan_filesappendUnique
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsscan.scan_filesprojectStatescan.scan_filesappendUniqueprivate; no linktools.smg.src.scan.filescollectFilesystemSupportedprivate; no linktools.smg.src.scan.filescollectGitSupportedscan.scan_filesrelativePathscan.scan_filesshouldSkip+2 morescan.scan_filescollectSupported
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsscan.scan_filesappendUniquescan.scan_filescontains
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersscan.scan_filesremoveKnownExtensionscan.scan_filesisPackageFile
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsscan.scan_filessourceDigestscan.scan_fileslanguage
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.scan.fileshashZigTargetModulesscan.scan_filesprojectStateprivate; no linktools.smg.src.scan.filesmoduleNameFromRelprivate; no linktools.smg.src.scan.filesworkspaceModuleNamescan.scan_filesmoduleName
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsscan.scan_filesprojectStateprivate; no linktools.smg.src.scan.filesignorePatternscan.scan_filesprojectExcludes
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallstest; no linktools.smg.src.scan.filestest: project identity covers source ...scan.scan_filesprojectStatescan.scan_filesprojectIdentity
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsscan.scan_filesprojectIdentityprivate; no linktools.smg.src.scan.files.ProjectIdentityBuilderaddSkippedprivate; no linktools.smg.src.scan.files.ProjectIdentityBuilderaddSourceprivate; no linktools.smg.src.scan.files.ProjectIdentityBuilderfinishprivate; no linktools.smg.src.scan.files.ProjectIdentityBuilderinitscan.scan_filescollectSupported+3 morescan.scan_filesprojectState
Static calls · unresolved targets: 3 · external targets: 4.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.filescollectFilesystemSupportedscan.scan_filescollectSupportedscan.scan_filesshouldSkipscan.scan_filesrelativePath
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallsNo direct callsscan.scan_filesisPackageFileprivate; no linktools.smg.src.scan.filesmoduleNameFromRelscan.scan_filesremoveKnownExtension
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.scan.filescollectFilesystemSupportedprivate; no linktools.smg.src.scan.filescollectGitSupportedscan.scan_filescollectSupportedprivate; no linktools.smg.src.scan.filesexcludedscan.scan_filesrelativePathprivate; no linktools.smg.src.scan.filesskipscan.scan_filesshouldSkip
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsscan.scan_filesprojectStateprivate; no linktools.smg.src.scan.fileshashFieldprivate; no linktools.smg.src.scan.fileshashZigTargetModulesscan.scan_fileslanguagescan.scan_filessourceDigest
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.filescollectFilesystemSupportedprivate; no linktools.smg.src.scan.filescollectGitSupportedscan.scan_filescollectSupportedscan.scan_filesstatPath
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.filescollectFilesystemSupportedprivate; no linktools.smg.src.scan.filescollectGitSupportedscan.scan_filescollectSupportedscan.scan_filessupported
Static calls · unresolved targets: 0 · external targets: 0.

Source: tools/smg/src/scan/files.zig

zig
const std = @import("std");const sys = @import("sys");const smg = @import("../root.zig");const text = @import("../text/root.zig");pub const ProjectIdentity = struct {    digest: [std.crypto.hash.sha2.Sha256.digest_length]u8,    files: usize,};pub const SourceDigest = [std.crypto.hash.sha2.Sha256.digest_length]u8;pub const SourceIdentity = struct {    path: []const u8,    digest: SourceDigest,};pub const ProjectState = struct {    project: ProjectIdentity,    sources: []const SourceIdentity,};const ProjectIdentityBuilder = struct {    hasher: std.crypto.hash.sha2.Sha256,    pub fn init(files: usize) ProjectIdentityBuilder {        var hasher = std.crypto.hash.sha2.Sha256.init(.{});        hasher.update("tiny.smg.project/v2\n");        hashInteger(&hasher, files);        return .{ .hasher = hasher };    }    pub fn addSkipped(self: *ProjectIdentityBuilder, relative: []const u8) void {        hashField(&self.hasher, relative);        self.hasher.update(&.{0});    }    pub fn addSource(self: *ProjectIdentityBuilder, relative: []const u8, digest: SourceDigest) void {        hashField(&self.hasher, relative);        self.hasher.update(&.{1});        hashField(&self.hasher, &digest);    }    pub fn finish(self: *ProjectIdentityBuilder) [std.crypto.hash.sha2.Sha256.digest_length]u8 {        var digest: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined;        self.hasher.final(&digest);        return digest;    }};pub fn sourceDigest(    allocator: std.mem.Allocator,    root: []const u8,    relative: []const u8,    module: []const u8,    source: []const u8,) !SourceDigest {    var hasher = std.crypto.hash.sha2.Sha256.init(.{});    hasher.update("tiny.smg.source-fragment/v1\n");    hashField(&hasher, relative);    hashField(&hasher, module);    hashField(&hasher, source);    if (std.mem.eql(u8, language(relative), "zig")) {        try hashZigTargetModules(&hasher, allocator, root, relative, source);    }    var digest: SourceDigest = undefined;    hasher.final(&digest);    return digest;}pub fn projectIdentity(    allocator: std.mem.Allocator,    phases: std.mem.Allocator,    root: []const u8,    explicit_excludes: []const []const u8,    limits: smg.ScanLimits,) !ProjectIdentity {    return (try projectState(allocator, phases, root, explicit_excludes, limits)).project;}pub fn projectState(    allocator: std.mem.Allocator,    phases: std.mem.Allocator,    root: []const u8,    explicit_excludes: []const []const u8,    limits: smg.ScanLimits,) !ProjectState {    const excludes = try projectExcludes(allocator, root, explicit_excludes, limits);    var files: std.ArrayList([]const u8) = .empty;    try collectSupported(allocator, root, root, &files, excludes, limits);    std.mem.sort([]const u8, files.items, {}, lessThan);    var identity = ProjectIdentityBuilder.init(files.items.len);    var sources: std.ArrayList(SourceIdentity) = .empty;    try sources.ensureTotalCapacity(allocator, files.items.len);    var scratch_arena = std.heap.ArenaAllocator.init(phases);    defer scratch_arena.deinit();    for (files.items) |relative| {        _ = scratch_arena.reset(.retain_capacity);        const scratch = scratch_arena.allocator();        const absolute = try std.fs.path.join(scratch, &.{ root, relative });        const source = text.readFileZ(            scratch,            absolute,            limits.max_source_file_bytes,        ) catch |err| switch (err) {            error.StreamTooLong => return error.MaxSourceFileBytesExceeded,            else => {                identity.addSkipped(relative);                continue;            },        };        const module = try moduleName(scratch, root, relative);        const digest = try sourceDigest(scratch, root, relative, module, source);        identity.addSource(relative, digest);        sources.appendAssumeCapacity(.{ .path = relative, .digest = digest });    }    return .{        .project = .{            .digest = identity.finish(),            .files = files.items.len,        },        .sources = try sources.toOwnedSlice(allocator),    };}fn hashField(hasher: *std.crypto.hash.sha2.Sha256, value: []const u8) void {    hashInteger(hasher, value.len);    hasher.update(value);}fn hashInteger(hasher: *std.crypto.hash.sha2.Sha256, value: anytype) void {    var encoded: [@sizeOf(u64)]u8 = undefined;    std.mem.writeInt(u64, &encoded, @intCast(value), .big);    hasher.update(&encoded);}fn hashZigTargetModules(    hasher: *std.crypto.hash.sha2.Sha256,    allocator: std.mem.Allocator,    root: []const u8,    relative_source: []const u8,    source: []const u8,) !void {    const absolute_root = try std.fs.path.resolve(allocator, &.{root});    const absolute_source = try std.fs.path.resolve(allocator, &.{ absolute_root, relative_source });    const source_dir = std.fs.path.dirname(absolute_source) orelse absolute_root;    var index: usize = 0;    while (index < source.len) {        if (source[index] != '"') {            index += 1;            continue;        }        const start = index + 1;        index = start;        while (index < source.len) : (index += 1) {            if (source[index] == '\\') {                index = @min(index + 1, source.len);                continue;            }            if (source[index] != '"') continue;            const raw = source[start..index];            index += 1;            if (!std.mem.endsWith(u8, raw, ".zig")) break;            const absolute_target = try std.fs.path.resolve(                allocator,                &.{ absolute_root, source_dir, raw },            );            if (!pathWithinRoot(absolute_root, absolute_target)) break;            var relative = absolute_target[absolute_root.len..];            while (relative.len != 0 and std.fs.path.isSep(relative[0])) relative = relative[1..];            hasher.update("zig-target\n");            hashField(hasher, relative);            hashField(hasher, try moduleName(allocator, absolute_root, relative));            break;        }    }}pub fn collectSupported(    allocator: std.mem.Allocator,    root: []const u8,    path: []const u8,    files: *std.ArrayList([]const u8),    excludes: []const []const u8,    limits: smg.ScanLimits,) !void {    const stat = statPath(path) catch return;    if (stat.kind == .directory) {        if (try shouldSkip(allocator, root, path, excludes)) return;        if (try collectGitSupported(allocator, root, path, files, excludes, limits)) return;        try collectFilesystemSupported(allocator, root, path, files, excludes, limits);        return;    }    if (stat.kind != .file) return;    if (!supported(path) or try shouldSkip(allocator, root, path, excludes)) return;    try appendUnique(allocator, files, try relativePath(allocator, root, path));}fn collectGitSupported(    allocator: std.mem.Allocator,    root: []const u8,    path: []const u8,    files: *std.ArrayList([]const u8),    excludes: []const []const u8,    limits: smg.ScanLimits,) !bool {    const repository = try smg.git.run(        allocator,        root,        &.{ "git", "rev-parse", "--is-inside-work-tree" },        64,    );    if (!repository.ok or !std.mem.eql(u8, std.mem.trim(u8, repository.stdout, " \t\r\n"), "true")) return false;    const pathspec = try gitPathspec(allocator, root, path) orelse return false;    const listed = smg.git.run(        allocator,        root,        &.{ "git", "--literal-pathspecs", "ls-files", "--cached", "--others", "--exclude-standard", "--deduplicate", "-z", "--", pathspec },        limits.max_git_file_list_bytes,    ) catch |err| switch (err) {        error.StreamTooLong => return error.MaxGitFileListBytesExceeded,        else => return err,    };    if (!listed.ok) return error.GitFileDiscoveryFailed;    var paths = std.mem.splitScalar(u8, listed.stdout, 0);    while (paths.next()) |relative| {        if (relative.len == 0) continue;        const absolute = try std.fs.path.join(allocator, &.{ root, relative });        const stat = statPath(absolute) catch continue;        if (stat.kind != .file or !supported(relative) or try shouldSkip(allocator, root, absolute, excludes)) continue;        try appendUnique(allocator, files, relative);    }    return true;}fn gitPathspec(allocator: std.mem.Allocator, root: []const u8, path: []const u8) !?[]const u8 {    const absolute_root = try sys.fs.realPathAlloc(allocator, root);    const absolute_path = try sys.fs.realPathAlloc(allocator, path);    if (!pathWithinRoot(absolute_root, absolute_path)) return null;    var relative = absolute_path[absolute_root.len..];    while (relative.len != 0 and (relative[0] == '/' or relative[0] == '\\')) relative = relative[1..];    return if (relative.len == 0) "." else relative;}fn pathWithinRoot(root: []const u8, path: []const u8) bool {    if (std.mem.eql(u8, root, path)) return true;    return path.len > root.len and        std.mem.startsWith(u8, path, root) and        (path[root.len] == '/' or path[root.len] == '\\');}fn collectFilesystemSupported(    allocator: std.mem.Allocator,    root: []const u8,    path: []const u8,    files: *std.ArrayList([]const u8),    excludes: []const []const u8,    limits: smg.ScanLimits,) !void {    const entries = sys.fs.listDirAlloc(allocator, path) catch return;    for (entries) |entry| {        const stat = statPath(entry.path) catch continue;        if (stat.kind == .directory) {            if (!try shouldSkip(allocator, root, entry.path, excludes)) {                try collectFilesystemSupported(                    allocator,                    root,                    entry.path,                    files,                    excludes,                    limits,                );            }        } else if (stat.kind == .file and supported(entry.path) and !try shouldSkip(allocator, root, entry.path, excludes)) {            try appendUnique(allocator, files, try relativePath(allocator, root, entry.path));        }    }}pub fn projectExcludes(    allocator: std.mem.Allocator,    root: []const u8,    explicit: []const []const u8,    limits: smg.ScanLimits,) ![]const []const u8 {    const path = try std.fs.path.join(allocator, &.{ root, smg.storage.paths.ignore_relative_path });    const source = sys.fs.readFileAlloc(        allocator,        path,        limits.max_ignore_file_bytes,    ) catch |err| switch (err) {        error.FileNotFound, error.NotDir => return explicit,        else => return err,    };    var count = explicit.len;    var lines = std.mem.splitScalar(u8, source, '\n');    while (lines.next()) |line| {        if (ignorePattern(line) != null) count += 1;    }    if (count == explicit.len) return explicit;    const excludes = try allocator.alloc([]const u8, count);    @memcpy(excludes[0..explicit.len], explicit);    var index = explicit.len;    lines.reset();    while (lines.next()) |line| {        const pattern = ignorePattern(line) orelse continue;        excludes[index] = pattern;        index += 1;    }    std.debug.assert(index == excludes.len);    return excludes;}pub fn statPath(path: []const u8) std.Io.Dir.StatFileError!std.Io.Dir.Stat {    return sys.fs.cwd().statFile(        sys.fs.debugIo(),        path,        .{ .follow_symlinks = false },    );}pub fn appendUnique(allocator: std.mem.Allocator, paths: *std.ArrayList([]const u8), path: []const u8) !void {    if (!contains(paths.items, path)) try paths.append(allocator, path);}pub fn contains(paths: []const []const u8, path: []const u8) bool {    for (paths) |item| if (std.mem.eql(u8, item, path)) return true;    return false;}pub fn underPrefixes(prefixes: []const []const u8, file: []const u8) bool {    for (prefixes) |prefix| {        if (std.mem.eql(u8, prefix, ".") or std.mem.startsWith(u8, file, prefix)) return true;    }    return false;}pub fn relativePath(allocator: std.mem.Allocator, root: []const u8, path: []const u8) ![]const u8 {    var rel = path;    if (std.fs.path.isAbsolute(path) and std.mem.startsWith(u8, path, root)) {        rel = path[root.len..];        while (rel.len != 0 and (rel[0] == '/' or rel[0] == '\\')) rel = rel[1..];    }    while (std.mem.startsWith(u8, rel, "./")) rel = rel[2..];    return try text.replaceSeparators(allocator, rel, '/');}pub fn moduleName(allocator: std.mem.Allocator, root: []const u8, rel_path: []const u8) ![]const u8 {    if (try workspaceModuleName(allocator, root, rel_path)) |name| return name;    var rel = rel_path;    while (std.mem.startsWith(u8, rel, "./")) rel = rel[2..];    if (std.mem.startsWith(u8, rel, "src/")) {        const rest = rel["src/".len..];        const slash = std.mem.indexOfScalar(u8, rest, '/');        if (slash) |index| {            const package = rest[0..index];            const candidate = try std.fs.path.join(allocator, &.{ root, "src", package });            const init_file = try std.fs.path.join(allocator, &.{ candidate, "__init__.py" });            const package_json = try std.fs.path.join(allocator, &.{ root, "package.json" });            const tsconfig = try std.fs.path.join(allocator, &.{ root, "tsconfig.json" });            if (sys.fs.exists(init_file) or sys.fs.exists(package_json) or sys.fs.exists(tsconfig)) {                rel = rest;            }        }    }    return try moduleNameFromRel(allocator, "", rel);}pub fn isPackageFile(path: []const u8) bool {    const stem = removeKnownExtension(text.basename(path));    return std.mem.eql(u8, stem, "__init__") or std.mem.eql(u8, stem, "index");}pub fn removeKnownExtension(path: []const u8) []const u8 {    const extensions = [_][]const u8{ ".py", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".zig", ".chic", ".c", ".h", ".cpp", ".hpp", ".cc", ".cxx", ".hh", ".hxx", ".cu", ".cuh", ".metal" };    for (extensions) |ext| {        if (std.mem.endsWith(u8, path, ext)) return path[0 .. path.len - ext.len];    }    return path;}pub fn language(path: []const u8) []const u8 {    if (std.mem.endsWith(u8, path, ".py")) return "python";    if (std.mem.endsWith(u8, path, ".zig")) return "zig";    if (std.mem.endsWith(u8, path, ".chic")) return "chiclet";    if (std.mem.endsWith(u8, path, ".ts") or std.mem.endsWith(u8, path, ".tsx")) return "typescript";    if (std.mem.endsWith(u8, path, ".h")) return "c_header";    if (std.mem.endsWith(u8, path, ".cpp") or std.mem.endsWith(u8, path, ".cc") or std.mem.endsWith(u8, path, ".cxx") or std.mem.endsWith(u8, path, ".cu") or std.mem.endsWith(u8, path, ".metal")) return "cpp";    if (std.mem.endsWith(u8, path, ".hpp") or std.mem.endsWith(u8, path, ".hh") or std.mem.endsWith(u8, path, ".hxx") or std.mem.endsWith(u8, path, ".cuh")) return "cpp_header";    if (std.mem.endsWith(u8, path, ".c")) return "c";    return "javascript";}pub fn displayName(lang: []const u8) []const u8 {    if (std.mem.eql(u8, lang, "python")) return "Python";    if (std.mem.eql(u8, lang, "zig")) return "Zig";    if (std.mem.eql(u8, lang, "chiclet")) return "Chiclet";    if (std.mem.eql(u8, lang, "typescript")) return "TypeScript";    if (std.mem.eql(u8, lang, "c")) return "C";    if (std.mem.eql(u8, lang, "c_header")) return "CHeader";    if (std.mem.eql(u8, lang, "cpp")) return "Cpp";    if (std.mem.eql(u8, lang, "cpp_header")) return "CppHeader";    return "JavaScript";}pub fn supported(path: []const u8) bool {    return std.mem.endsWith(u8, path, ".py") or        std.mem.endsWith(u8, path, ".zig") or        std.mem.endsWith(u8, path, ".chic") or        std.mem.endsWith(u8, path, ".c") or        std.mem.endsWith(u8, path, ".h") or        std.mem.endsWith(u8, path, ".cc") or        std.mem.endsWith(u8, path, ".cpp") or        std.mem.endsWith(u8, path, ".hpp") or        std.mem.endsWith(u8, path, ".cxx") or        std.mem.endsWith(u8, path, ".hh") or        std.mem.endsWith(u8, path, ".hxx") or        std.mem.endsWith(u8, path, ".cu") or        std.mem.endsWith(u8, path, ".cuh") or        std.mem.endsWith(u8, path, ".metal") or        std.mem.endsWith(u8, path, ".js") or        std.mem.endsWith(u8, path, ".jsx") or        std.mem.endsWith(u8, path, ".mjs") or        std.mem.endsWith(u8, path, ".cjs") or        std.mem.endsWith(u8, path, ".ts") or        std.mem.endsWith(u8, path, ".tsx");}pub fn shouldSkip(allocator: std.mem.Allocator, root: []const u8, path: []const u8, excludes: []const []const u8) !bool {    const rel = try relativePath(allocator, root, path);    if (skip(rel)) return true;    if (excludes.len == 0) return false;    return excluded(rel, excludes);}pub fn lessThan(_: void, a: []const u8, b: []const u8) bool {    return std.mem.lessThan(u8, a, b);}fn workspaceModuleName(allocator: std.mem.Allocator, root: []const u8, rel_path: []const u8) !?[]const u8 {    var current = std.fs.path.dirname(rel_path) orelse "";    while (true) {        const package_json = if (current.len == 0)            try std.fs.path.join(allocator, &.{ root, "package.json" })        else            try std.fs.path.join(allocator, &.{ root, current, "package.json" });        if (try loadPackageName(allocator, package_json)) |package_name| {            const package_rel = if (current.len == 0) rel_path else rel_path[current.len + 1 ..];            return try moduleNameFromRel(allocator, package_name, package_rel);        }        if (current.len == 0) break;        current = std.fs.path.dirname(current) orelse "";    }    return null;}fn loadPackageName(allocator: std.mem.Allocator, path: []const u8) !?[]const u8 {    const raw = text.readFile(allocator, path, 1024 * 1024) catch return null;    const parsed = std.json.parseFromSlice(std.json.Value, allocator, raw, .{}) catch return null;    const object = switch (parsed.value) {        .object => |object| object,        else => return null,    };    const value = object.get("name") orelse return null;    const name = switch (value) {        .string => |string| text.trim(string),        else => return null,    };    if (name.len == 0) return null;    var normalized = if (name[0] == '@') name[1..] else name;    normalized = try text.replaceSeparators(allocator, normalized, '.');    return normalized;}test "project identity covers source bytes and computed module identity" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const root = try std.fmt.allocPrint(allocator, "/tmp/smg-project-identity-test-{x}", .{@intFromPtr(&arena)});    defer sys.fs.deleteTree(root) catch {};    try sys.fs.createDirPath(try std.fs.path.join(allocator, &.{ root, "src", "app" }));    try sys.fs.createDirPath(try std.fs.path.join(allocator, &.{ root, "ignored" }));    const source_path = try std.fs.path.join(allocator, &.{ root, "src", "app", "service.zig" });    const package_path = try std.fs.path.join(allocator, &.{ root, "package.json" });    const target_package_path = try std.fs.path.join(allocator, &.{ root, "ignored", "package.json" });    try text.writeFile(source_path, "const target = @import(\"../../ignored/target.zig\");\npub fn value() u8 { return 1; }\n");    try text.writeFile(package_path, "{\"name\":\"first\"}\n");    try text.writeFile(target_package_path, "{\"name\":\"first-target\"}\n");    try text.writeFile(try std.fs.path.join(allocator, &.{ root, "ignored", "target.zig" }), "pub const value: u8 = 1;\n");    try text.writeFile(try std.fs.path.join(allocator, &.{ root, smg.storage.paths.ignore_relative_path }), "ignored.zig\nignored\n__init__.py\n");    const initial = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try text.writeFile(source_path, "const target = @import(\"../../ignored/target.zig\");\npub fn value() u8 { return 2; }\n");    const edited = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expect(!std.mem.eql(u8, &initial.digest, &edited.digest));    try text.writeFile(source_path, "const target = @import(\"../../ignored/target.zig\");\npub fn value() u8 { return 1; }\n");    const reverted = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expectEqualSlices(u8, &initial.digest, &reverted.digest);    try text.writeFile(package_path, "{\"name\":\"second\"}\n");    const renamed = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expect(!std.mem.eql(u8, &initial.digest, &renamed.digest));    try text.writeFile(package_path, "{\"name\":\"first\"}\n");    const ignored_path = try std.fs.path.join(allocator, &.{ root, "ignored.zig" });    try text.writeFile(ignored_path, "pub fn ignored() u8 { return 1; }\n");    const ignored = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expectEqualSlices(u8, &initial.digest, &ignored.digest);    try text.writeFile(ignored_path, "pub fn ignored() u8 { return 2; }\n");    const ignored_edited = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expectEqualSlices(u8, &initial.digest, &ignored_edited.digest);    try text.writeFile(target_package_path, "{\"name\":\"second-target\"}\n");    const target_renamed = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expect(!std.mem.eql(u8, &initial.digest, &target_renamed.digest));    try text.writeFile(target_package_path, "{\"name\":\"first-target\"}\n");    const extra_path = try std.fs.path.join(allocator, &.{ root, "src", "app", "extra.py" });    try text.writeFile(extra_path, "def extra():\n    return 1\n");    const added = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expect(!std.mem.eql(u8, &initial.digest, &added.digest));    try sys.fs.deleteFile(extra_path);    const deleted = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expectEqualSlices(u8, &initial.digest, &deleted.digest);    try sys.fs.deleteFile(package_path);    const without_config = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    const tsconfig_path = try std.fs.path.join(allocator, &.{ root, "tsconfig.json" });    try text.writeFile(tsconfig_path, "{}\n");    const with_tsconfig = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expect(!std.mem.eql(u8, &without_config.digest, &with_tsconfig.digest));    try sys.fs.deleteFile(tsconfig_path);    const marker_path = try std.fs.path.join(allocator, &.{ root, "src", "app", "__init__.py" });    try text.writeFile(marker_path, "");    const with_ignored_marker = try projectIdentity(allocator, allocator, root, &.{}, smg.default_limits.scan);    try std.testing.expect(!std.mem.eql(u8, &without_config.digest, &with_ignored_marker.digest));}fn moduleNameFromRel(allocator: std.mem.Allocator, prefix: []const u8, rel_raw: []const u8) ![]const u8 {    var rel = rel_raw;    if (prefix.len != 0 and std.mem.startsWith(u8, rel, "src/")) rel = rel["src/".len..];    const without_ext = removeKnownExtension(rel);    var dotted = try text.replaceSeparators(allocator, without_ext, '.');    while (std.mem.startsWith(u8, dotted, ".")) dotted = dotted[1..];    if (std.mem.endsWith(u8, dotted, ".__init__")) dotted = dotted[0 .. dotted.len - ".__init__".len];    if (std.mem.endsWith(u8, dotted, ".index")) dotted = dotted[0 .. dotted.len - ".index".len];    if (std.mem.eql(u8, dotted, "__init__") or std.mem.eql(u8, dotted, "index")) dotted = "";    if (prefix.len == 0) return if (dotted.len == 0) try allocator.dupe(u8, "root") else dotted;    if (dotted.len == 0) return prefix;    return try std.fmt.allocPrint(allocator, "{s}.{s}", .{ prefix, dotted });}fn skip(path: []const u8) bool {    const parts = [_][]const u8{ ".git", ".smg", ".claude", ".codex", ".zig-cache", "zig-cache", "zig-out", ".venv", "venv", ".env", "node_modules", "__pycache__", ".mypy_cache", ".pytest_cache", ".ruff_cache", ".hypothesis", ".tox", "dist", "site-packages", "vendor", "third_party" };    var split = std.mem.splitAny(u8, path, "/\\");    while (split.next()) |part| {        for (parts) |skip_part| if (std.mem.eql(u8, part, skip_part)) return true;    }    return false;}fn excluded(rel: []const u8, excludes: []const []const u8) bool {    for (excludes) |raw| {        const pattern = trimExcludePattern(raw);        if (pattern.len == 0) continue;        if (std.mem.indexOfAny(u8, pattern, "/\\") == null) {            var parts = std.mem.splitAny(u8, rel, "/\\");            while (parts.next()) |part| if (globMatch(part, pattern)) return true;        } else if (pathOrAncestorMatches(rel, pattern)) {            return true;        }    }    return false;}fn pathOrAncestorMatches(path: []const u8, pattern: []const u8) bool {    var end = path.len;    while (true) {        if (globMatch(path[0..end], pattern)) return true;        end = std.mem.lastIndexOfAny(u8, path[0..end], "/\\") orelse return false;    }}fn trimExcludePattern(raw: []const u8) []const u8 {    var pattern = std.mem.trim(u8, raw, " \t\r\n");    while (std.mem.startsWith(u8, pattern, "./")) pattern = pattern[2..];    while (std.mem.startsWith(u8, pattern, "/")) pattern = pattern[1..];    while (std.mem.endsWith(u8, pattern, "/")) pattern = pattern[0 .. pattern.len - 1];    return pattern;}fn ignorePattern(raw: []const u8) ?[]const u8 {    const pattern = trimExcludePattern(raw);    if (pattern.len == 0 or pattern[0] == '#') return null;    return pattern;}fn globMatch(value: []const u8, pattern: []const u8) bool {    var value_index: usize = 0;    var pattern_index: usize = 0;    var star_index: ?usize = null;    var retry_index: usize = 0;    while (value_index < value.len) {        if (pattern_index < pattern.len and (normalizedPathByte(pattern[pattern_index]) == normalizedPathByte(value[value_index]) or pattern[pattern_index] == '?')) {            value_index += 1;            pattern_index += 1;        } else if (pattern_index < pattern.len and pattern[pattern_index] == '*') {            star_index = pattern_index;            retry_index = value_index;            pattern_index += 1;        } else if (star_index) |star| {            pattern_index = star + 1;            retry_index += 1;            value_index = retry_index;        } else {            return false;        }    }    while (pattern_index < pattern.len and pattern[pattern_index] == '*') pattern_index += 1;    return pattern_index == pattern.len;}fn normalizedPathByte(byte: u8) u8 {    return if (byte == '\\') '/' else byte;}

Source: tools/smg/src/scan/root.zig:4

zig
pub const scan_files = @import("files.zig");

Complete call list for scan.scan_files.collectSupported

7 direct calls.

Complete call list for scan.scan_files.projectState

8 direct calls.

Audit

Definitions23
Public names24
Members6
Version26.7.0
Revisiondaab053ee433