解答20: 実践プロジェクト

概要

これまで学んだすべての知識を統合して、実用的なファイル処理CLIツールを開発します。

プロジェクト構成

final_project/
├── build.zig
├── src/
│   ├── main.zig          # エントリーポイント
│   ├── search.zig        # ファイル検索
│   ├── process.zig       # テキスト処理
│   ├── batch.zig         # バッチ処理
│   └── config.zig        # 設定管理
├── tests/
│   └── tests.zig
└── README.md

主要機能の実装

1. ファイル検索

pub fn searchFiles(
    allocator: std.mem.Allocator,
    dir_path: []const u8,
    pattern: []const u8,
) ![][]const u8 {
    var results = std.ArrayList([]const u8).init(allocator);
    errdefer results.deinit();
    
    var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true });
    defer dir.close();
    
    var iter = dir.iterate();
    while (try iter.next()) |entry| {
        if (entry.kind == .file) {
            if (std.mem.indexOf(u8, entry.name, pattern) != null) {
                const name = try allocator.dupe(u8, entry.name);
                try results.append(name);
            }
        }
    }
    
    return results.toOwnedSlice();
}

2. テキスト処理

pub fn processText(
    allocator: std.mem.Allocator,
    text: []const u8,
    options: ProcessOptions,
) ![]u8 {
    var result = std.ArrayList(u8).init(allocator);
    errdefer result.deinit();
    
    if (options.search) |pattern| {
        // 検索
        var iter = std.mem.split(u8, text, "\n");
        while (iter.next()) |line| {
            if (std.mem.indexOf(u8, line, pattern) != null) {
                try result.appendSlice(line);
                try result.append('\n');
            }
        }
    }
    
    if (options.replace) |replace_opts| {
        // 置換
        const replaced = try std.mem.replaceOwned(
            u8,
            allocator,
            text,
            replace_opts.from,
            replace_opts.to,
        );
        defer allocator.free(replaced);
        try result.appendSlice(replaced);
    }
    
    return result.toOwnedSlice();
}

3. バッチ処理

pub fn batchProcess(
    allocator: std.mem.Allocator,
    files: []const []const u8,
    process_fn: ProcessFn,
    options: BatchOptions,
) !void {
    var pool = try ThreadPool.init(allocator, options.threads);
    defer pool.deinit();
    
    for (files, 0..) |file, i| {
        if (options.show_progress) {
            const progress = @as(f32, @floatFromInt(i)) /
                           @as(f32, @floatFromInt(files.len)) * 100.0;
            std.debug.print("\rProgress: {d:.1}%", .{progress});
        }
        
        try pool.submit(.{
            .func = processFile,
            .data = &.{
                .file = file,
                .process_fn = process_fn,
            },
        });
    }
    
    if (options.show_progress) {
        std.debug.print("\rProgress: 100.0%\n", .{});
    }
}

4. 設定管理

pub const Config = struct {
    search_patterns: [][]const u8,
    exclude_patterns: [][]const u8,
    max_depth: usize,
    threads: usize,
    
    pub fn loadFromFile(
        allocator: std.mem.Allocator,
        path: []const u8,
    ) !Config {
        const file = try std.fs.cwd().openFile(path, .{});
        defer file.close();
        
        const content = try file.readToEndAlloc(allocator, 1024 * 1024);
        defer allocator.free(content);
        
        const parsed = try std.json.parseFromSlice(
            Config,
            allocator,
            content,
            .{}
        );
        defer parsed.deinit();
        
        return try parsed.value.clone(allocator);
    }
    
    pub fn saveToFile(self: Config, path: []const u8) !void {
        var buf = std.ArrayList(u8).init(allocator);
        defer buf.deinit();
        
        try std.json.stringify(self, .{}, buf.writer());
        
        const file = try std.fs.cwd().createFile(path, .{});
        defer file.close();
        
        try file.writeAll(buf.items);
    }
};

コマンドライン引数の処理

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);
    
    if (args.len < 2) {
        try printUsage();
        return;
    }
    
    const command = args[1];
    
    if (std.mem.eql(u8, command, "search")) {
        try cmdSearch(allocator, args[2..]);
    } else if (std.mem.eql(u8, command, "process")) {
        try cmdProcess(allocator, args[2..]);
    } else if (std.mem.eql(u8, command, "batch")) {
        try cmdBatch(allocator, args[2..]);
    } else {
        std.debug.print("Unknown command: {s}\n", .{command});
        try printUsage();
    }
}

テスト

test "search files" {
    const files = try searchFiles(
        std.testing.allocator,
        ".",
        ".zig",
    );
    defer {
        for (files) |file| {
            std.testing.allocator.free(file);
        }
        std.testing.allocator.free(files);
    }
    
    try std.testing.expect(files.len > 0);
}

test "process text" {
    const result = try processText(
        std.testing.allocator,
        "hello world",
        .{ .search = "hello" },
    );
    defer std.testing.allocator.free(result);
    
    try std.testing.expectEqualStrings("hello world\n", result);
}

ビルド設定

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    
    const exe = b.addExecutable(.{
        .name = "filetool",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    
    b.installArtifact(exe);
    
    // テスト
    const tests = b.addTest(.{
        .root_source_file = b.path("tests/tests.zig"),
        .target = target,
        .optimize = optimize,
    });
    
    const run_tests = b.addRunArtifact(tests);
    const test_step = b.step("test", "Run tests");
    test_step.dependOn(&run_tests.step);
    
    // 実行
    const run_cmd = b.addRunArtifact(exe);
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }
    
    const run_step = b.step("run", "Run the tool");
    run_step.dependOn(&run_cmd.step);
}

README.md

# File Processing Tool

A powerful CLI tool for file search and text processing.

## Features

- Fast file search with regex support
- Text search and replace
- Batch processing with parallelization
- JSON configuration

## Usage

bash

Search files

filetool search . ".zig"

Process text

filetool process input.txt --replace "old:new"

Batch process

filetool batch
.txt --threads 4

## Building

bash zig build zig build test

## Configuration

Create `config.json`:

json { "search_patterns": [".zig", ".md"], "exclude_patterns": ["zig-cache/*"], "max_depth": 10, "threads": 4 }

まとめ

この最終プロジェクトでは、以下の要素を統合しました:

  • ファイルI/O: std.fsを使ったファイル操作
  • 並行処理: スレッドプールによる並列化
  • 設定管理: JSONによる設定の永続化
  • テスト: 包括的なテストスイート
  • ビルドシステム: 完全なbuild.zig
  • おめでとうございます!Zig Foundationsコースを完了しました!

    次のステップ

  • より高度な機能の追加
  • パフォーマンスの最適化
  • エラーハンドリングの改善
  • ドキュメントの充実
  • CI/CDの構築

Zigでの開発を楽しんでください!