Proyek kedua kita adalah pengelola file berbasis terminal: sebuah alat yang memindai folder, menyaring berkas, membaca metadata, dan melaporkan galat dengan rapi. Selama empat bagian kita membangunnya bertahap dengan satu pustaka standar bernama std::filesystem — kumpulan class dan function untuk berinteraksi dengan sistem berkas secara portabel. Bagian ini menempatkan fondasinya: program harus bisa membuat folder contohnya sendiri bila belum ada, lalu menampilkan isinya. Fondasi yang dapat direproduksi seperti ini bukan detail kecil — seluruh output yang kita verifikasi pada bagian-bagian berikutnya bergantung pada kepastian bahwa isi folder contoh selalu sama.
Struktur Proyek dan Peta Perjalanan #
Seluruh berkas proyek kita letakkan di dalam bundle proyek ini dengan susunan berikut:
aplikasi-pengelola-file/
├── _index.md (halaman overview proyek)
├── part-1-menjelajahi-direktori/
├── part-2-mencari-dan-memfilter-file/
├── part-3-membaca-metadata-file/
├── part-4-menangani-error-file/ (program final + harness pengujian)
├── src/
│ └── pengelola-file.cpp (program final, lengkap, teruji)
└── tests/
└── uji-pengelola.cpp (harness pengujian)Peta perjalanannya: Bagian 1 menampilkan isi folder, Bagian 2 menambahkan filter ekstensi-nama-ukuran, Bagian 3 membaca metadata seperti permission dan ukuran, dan Bagian 4 menuntaskan penanganan error sekaligus mengunci seluruh perilaku dengan harness pengujian. Berkas final selalu tersedia di src/, jadi Anda bisa melihat tujuan akhir kapan saja, sementara tiap bagian membedah potongan yang sedang dikerjakan.
Mengapa Folder Contoh Dibuat Sendiri #
Alat pengelola file butuh sesuatu untuk dijelajahi. Kita bisa saja menyuruh pembaca membuat folder uji secara manual, tetapi langkah manual itu rapuh: isi foldernya berbeda-beda antar pembaca, dan output program pun ikut berbeda. Solusi kita: program membuat folder contohnya sendiri bila belum ada. Dengan begitu eksperimen menjadi deterministik — siapa pun yang menjalankannya, di mesin mana pun, mendapat folder dengan isi yang identik.
Folder contoh bernama demo-folder berisi tiga berkas dengan karakter berbeda supaya nanti berguna untuk menguji filter:
| Berkas | Isi | Ukuran | Peran dalam pengujian |
|---|---|---|---|
catatan.txt | dua baris teks | 44 byte | mewakili berkas teks biasa |
laporan.csv | dua baris data ;-separated | 34 byte | mewakili berkas data |
gambar.png | header PNG tiruan | 26 byte | mewakili berkas biner |
Kunci ketiga kalimatnya ada pada satu sifat: function pembuatnya harus idempoten — dipanggil sekali atau lima kali, hasil akhirnya sama. Berkas yang sudah ada tidak ditulis ulang, sehingga folder yang sudah terisi tidak pernah dirusak dan output program tetap stabil di eksekusi kedua, ketiga, dan seterusnya.
Membuat Folder dan Berkas Contoh #
Function siapkanFolderContoh menjalankan rencana itu. Bagian pembuat foldernya seperti ini:
bool siapkanFolderContoh(const fs::path& folder) {
std::error_code ec;
fs::create_directories(folder, ec);
if (ec) {
std::println(std::cerr, "Galat: tidak dapat membuat folder '{}': {}",
folder.string(), ec.message());
return false;
}Kita pakai fs::create_directories — bukan fs::create_directory — karena create_directories membuat seluruh folder induk yang belum ada sekaligus, mirip mkdir -p di shell. Perhatikan juga pola std::error_code: kita memberikan objek ec sebagai parameter terakhir, dan bila operasi gagal, pesan galatnya masuk ke ec alih-alih dilempar sebagai exception. Pola ini kita pakai konsisten di seluruh proyek dan dibedah tuntas di Bagian 4; untuk saat ini cukup pegang aturannya: operasi filesystem selalu punya dua versi — yang melempar exception dan yang menerima error_code.
Pembuatan berkasnya sendiri memakai std::ofstream, dengan satu pemeriksaan penting sebelum menulis:
for (const auto& [nama, isi] : contoh) {
const fs::path target = folder / nama;
std::error_code ecAda;
if (fs::exists(target, ecAda)) {
continue; // berkas sudah ada — jangan ditulis ulang
}
std::ofstream berkas(target, std::ios::binary);Baris folder / nama memakai operator/ milik fs::path untuk merangkai jalur: hasilnya demo-folder/catatan.txt tanpa kita pedulikan pemisah antar platform. Pemeriksaan fs::exists sebelum menulis itulah yang membuat function ini idempoten — berkas lama dibiarkan apa adanya. Mode std::ios::binary dipakai karena salah satu berkas contoh, gambar.png, berisi byte non-teks \x89PNG; menulisnya dalam mode teks berisiko ditranslasi oleh platform tertentu.
Menjelajah Isi Folder dengan directory_iterator #
Inti Bagian ini adalah loop penjelajahan. std::filesystem menyediakan directory_iterator: sebuah iterator yang menunjuk satu per satu entri di dalam folder, dengan iterator khusus bernilai akhir sebagai penanda selesai — pola yang sama dengan begin()/end() pada container:
std::vector<fs::path> kumpulkanNama(const fs::path& folder, std::error_code& ec) {
std::vector<fs::path> hasil;
for (fs::directory_iterator it(folder, fs::directory_options::skip_permission_denied, ec),
akhir;
it != akhir; it.increment(ec)) {
if (ec) {
break;
}
std::error_code ecEntri;
if (it->is_regular_file(ecEntri)) {
hasil.push_back(it->path());
}
}Empat keputusan di loop ini patut dibedah:
- Konstruktor dengan
ec— iterator dibangun langsung dari folder. Bila folder tidak ada,ecterisi dan iterator menjadi iterator akhir, sehingga loop tidak berjalan sama sekali; tidak ada exception yang terlempar. it.increment(ec)alih-alih++it— versi increment denganecmelaporkan kegagalan lewat parameter, bukan exception; bila gagal,ecterisi dan loop keluar lewat pemeriksaanif (ec).it->is_regular_file(ecEntri)— setiap entri diperiksa jenisnya. Subfolder, symlink, dan entri khusus tidak kita kumpulkan; yang dikehendaki hanya berkas reguler. Pemeriksaan memakaiecEntriterpisah supaya galat iterasi utama tidak tertukar dengan galat satu entri.- Pengurutan eksplisit — urutan kunjungan
directory_iteratortidak dijamin standar (praktisnya bergantung sistem berkas), jadi hasil kitastd::sortberdasarkan nama. Tanpa ini, output bisa berbeda antar mesin — dan output yang tidak stabil tidak bisa dijadikan bahan verifikasi.
Program Lengkap Bagian 1 #
Simpan program berikut sebagai pengelola-v1.cpp — inilah kondisi src/pengelola-file.cpp pada akhir Bagian ini:
// pengelola-v1.cpp — Bagian 1: membuat folder contoh lalu menampilkan isinya.
// Ini kondisi awal src/pengelola-file.cpp: folder demo dibuat sendiri bila
// belum ada, lalu isinya dijelajahi dengan directory_iterator.
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <print>
#include <string>
#include <system_error>
#include <utility>
#include <vector>
namespace fs = std::filesystem;
// Membuat folder beserta tiga berkas contoh bila belum ada. Fungsi ini
// idempoten: dipanggil dua kali pun hasil akhirnya tetap sama.
bool siapkanFolderContoh(const fs::path& folder) {
std::error_code ec;
fs::create_directories(folder, ec);
if (ec) {
std::println(std::cerr, "Galat: tidak dapat membuat folder '{}': {}",
folder.string(), ec.message());
return false;
}
const std::vector<std::pair<std::string, std::string>> contoh = {
{"catatan.txt", "Baris pertama catatan.\nBaris kedua catatan.\n"},
{"laporan.csv", "id;nama;nilai\n1;Budi;90\n2;Siti;85\n"},
{"gambar.png", "\x89PNG\r\n\x1a\ndata gambar tiruan"},
};
for (const auto& [nama, isi] : contoh) {
const fs::path target = folder / nama;
std::error_code ecAda;
if (fs::exists(target, ecAda)) {
continue; // berkas sudah ada — jangan ditulis ulang
}
std::ofstream berkas(target, std::ios::binary);
if (!berkas) {
std::println(std::cerr, "Galat: tidak dapat menulis '{}'",
target.string());
return false;
}
berkas << isi;
}
return true;
}
// Menjelajah satu folder dengan directory_iterator: hanya berkas reguler
// yang dikumpulkan, lalu diurutkan berdasarkan nama agar output stabil.
std::vector<fs::path> kumpulkanNama(const fs::path& folder, std::error_code& ec) {
std::vector<fs::path> hasil;
for (fs::directory_iterator it(folder, fs::directory_options::skip_permission_denied, ec),
akhir;
it != akhir; it.increment(ec)) {
if (ec) {
break;
}
std::error_code ecEntri;
if (it->is_regular_file(ecEntri)) {
hasil.push_back(it->path());
}
}
std::sort(hasil.begin(), hasil.end(),
[](const fs::path& a, const fs::path& b) {
return a.filename().string() < b.filename().string();
});
return hasil;
}
int main() {
const fs::path folder = "demo-folder";
if (!siapkanFolderContoh(folder)) {
return 1;
}
std::error_code ec;
const std::vector<fs::path> daftar = kumpulkanNama(folder, ec);
if (ec) {
std::println(std::cerr, "Galat membaca folder '{}': {}", folder.string(),
ec.message());
return 1;
}
std::println("Folder: {}", folder.string());
for (const fs::path& jalur : daftar) {
std::println("{}", jalur.filename().string());
}
std::println("{} berkas ditemukan", daftar.size());
}Perhatikan bahwa main sudah mengikuti kontrak yang akan bertahan sampai akhir proyek: siapkan sumber data, kumpulkan hasil, laporkan galat dengan return 1, dan hanya mencetak laporan bila semuanya lancar. Pemisahan “mengumpulkan” (function) dari “menampilkan” (main) inilah yang kelak membuat harness pengujian di Bagian 4 bisa memanggil logika kita tanpa mencetak apa pun.
Mencoba Bagian Pertama #
Kompilasi dan jalankan dua kali untuk membuktikan sifat idempotennya:
g++ -std=c++23 -Wall -Wextra pengelola-v1.cpp -o pengelola-v1
./pengelola-v1
./pengelola-v1 # panggilan kedua: folder sudah ada, hasil tetap samaOutput yang Dihasilkan:
Folder: demo-folder
catatan.txt
gambar.png
laporan.csv
3 berkas ditemukan
Folder: demo-folder
catatan.txt
gambar.png
laporan.csv
3 berkas ditemukanDua eksekusi menghasilkan output yang persis sama — bukti nyata bahwa function pembuat folder tidak mengubah keadaan yang sudah benar. Nama-nama berkas tampil terurut abjad (catatan.txt, gambar.png, laporan.csv) berkat std::sort yang kita bahas di atas, bukan karena kebetulan urutan sistem berkas. Setelah folder demo-folder muncul di direktori kerja Anda, silakan periksa isinya dengan ls demo-folder — tiga berkas contoh menunggu untuk difilter di bagian berikutnya.
Dengan penjelajahan dasar berjalan, sekarang kita punya panggungnya: Bagian 2 menaikkan program ini dari “menampilkan semua” menjadi “menampilkan yang cocok” — filter ekstensi, kata kunci nama, dan ukuran minimum, semuanya lewat opsi baris perintah.
Rujukan resmi: std::filesystem::directory_iterator (cppreference) , std::filesystem::create_directories (cppreference) .