解答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
- より高度な機能の追加
- パフォーマンスの最適化
- エラーハンドリングの改善
- ドキュメントの充実
- CI/CDの構築
おめでとうございます!Zig Foundationsコースを完了しました!
次のステップ
Zigでの開発を楽しんでください!