Bagian 1 memberi kita program yang menampilkan seluruh isi folder; Bagian ini mengubahnya menjadi alat pencari yang menampilkan hanya yang kita cari. Tiga filter kita bangun: cocokkan ekstensi berkas, cocokkan substring pada nama berkas, dan ambang ukuran minimum. Ketiganya dirancang sebagai satu struct Kriteria yang diisi dari opsi baris perintah — --ekstensi, --cari, dan --min-ukuran — sehingga pengguna bisa menggabungkannya secara bebas. Pemisahan ini mengikuti pola yang sama dengan Bagian 1: data disaring oleh function murni, dan hanya hasilnya yang dicetak.

Tiga Filter dalam Satu Struct #

Alih-alih tiga parameter boolean yang tersebar, ketiga syarat kita kemas dalam satu struct. Setiap field bernilai “netral” berarti filter itu tidak aktif:

cpp Kriteria — tiga filter, satu wadah
struct InfoFile {
    fs::path jalur;          // jalur lengkap berkas
    std::uintmax_t ukuran;   // ukuran dalam byte
};

struct Kriteria {
    std::string ekstensi;    // "" = tanpa filter ekstensi
    std::string kata;        // "" = tanpa filter nama
    std::uintmax_t ukuranMin = 0;
    bool aktif() const { return !ekstensi.empty() || !kata.empty() || ukuranMin > 0; }
};

Dua hal kecil di struct ini memikul beban besar. Pertama, InfoFile kini menyimpan ukuran berkas — filter ukuran butuh informasi itu, jadi scanner dari Bagian 1 naik kelas dari sekadar menyimpan jalur menjadi juga mengukur tiap berkas. Kedua, method aktif() menjawab pertanyaan “apakah ada filter yang menyala?” dari satu tempat saja; program memakainya untuk memutuskan apakah baris ringkasan perlu dicetak. Menyatukan pertanyaan seperti ini dalam satu method mencegah kondisi yang sama ditulis ulang dengan cara berbeda di tiga tempat lalu saling bertentangan.

Filter ekstensi dan ukuran bekerja berbeda secara semantik, dan penting kita pahami bedanya sebelum menulis lolos:

FilterPencocokanContoh cocokContoh tidak cocok
ekstensipersis sama, peka huruf besar-kecil.csv vs laporan.csvlaporan.CSV
katasubstring di mana pun pada namaan vs laporan.csvgambar.png
ukuranMinambang, bukan rentang30 vs laporan.csv (34 byte)gambar.png (26 byte)

Fungsi lolos: Satu Titik Keputusan #

Seluruh logika penyaringan bertumpu pada satu function kecil yang menjawab ya atau tidak:

cpp lolos — semua filter, satu urutan
bool lolos(const InfoFile& berkas, const Kriteria& kriteria) {
    if (!kriteria.ekstensi.empty() && berkas.jalur.extension().string() != kriteria.ekstensi) {
        return false;
    }
    if (!kriteria.kata.empty() && !berkas.jalur.filename().string().contains(kriteria.kata)) {
        return false;
    }
    if (berkas.ukuran < kriteria.ukuranMin) {
        return false;
    }
    return true;
}

Struktur if (...) return false; yang berulang punya nama resmi: filter ladder — setiap syarat yang tidak terpenuhi langsung menyingkirkan berkas, dan hanya berkas yang melewati semua pintu sampai ke return true. Pola ini lebih mudah diperiksa daripada ekspresi boolean raksasa yang menggabungkan semua syarat dalam satu baris, karena tiap syarat bisa dibaca dan diuji terpisah.

Teknis pencocokannya juga patut dicermati. extension() mengembalikan ekstensi lengkap dengan titiknya (.csv, bukan csv), sehingga nilai filter kita juga ditulis dengan titik. contains pada std::string adalah method C++23 — versi lama bahasa memakai find(kata) != std::string::npos untuk arti yang sama. Dan filename() memastikan pencarian kata hanya menyentuh nama berkas, bukan nama foldernya.

Program Lengkap Bagian 2 #

Simpan program berikut sebagai pengelola-v2.cpp — inilah kondisi src/pengelola-file.cpp setelah lapisan filter:

cpp src/pengelola-file.cpp — versi Bagian 2
// pengelola-v2.cpp — Bagian 2: filter ekstensi, kata kunci nama, dan
// ukuran minimum. Ini kondisi src/pengelola-file.cpp setelah lapisan filter.

#include <algorithm>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <print>
#include <string>
#include <system_error>
#include <utility>
#include <vector>

namespace fs = std::filesystem;

struct InfoFile {
    fs::path jalur;          // jalur lengkap berkas
    std::uintmax_t ukuran;   // ukuran dalam byte
};

struct Kriteria {
    std::string ekstensi;    // "" = tanpa filter ekstensi
    std::string kata;        // "" = tanpa filter nama
    std::uintmax_t ukuranMin = 0;
    bool aktif() const { return !ekstensi.empty() || !kata.empty() || ukuranMin > 0; }
};

// Satu titik keputusan: apakah sebuah berkas melewati semua filter?
bool lolos(const InfoFile& berkas, const Kriteria& kriteria) {
    if (!kriteria.ekstensi.empty() && berkas.jalur.extension().string() != kriteria.ekstensi) {
        return false;
    }
    if (!kriteria.kata.empty() && !berkas.jalur.filename().string().contains(kriteria.kata)) {
        return false;
    }
    if (berkas.ukuran < kriteria.ukuranMin) {
        return false;
    }
    return true;
}

// Membuat folder beserta tiga berkas contoh bila belum ada (idempoten).
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;
}

// Memindai satu folder, mengukur tiap berkas dengan file_size, dan
// mengurutkan hasilnya berdasarkan nama agar output stabil.
std::vector<InfoFile> kumpulkanEntri(const fs::path& folder, std::error_code& ec) {
    std::vector<InfoFile> 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) || ecEntri) {
            continue;   // subfolder dan sejenisnya dilewati
        }
        InfoFile info;
        info.jalur = it->path();
        info.ukuran = it->file_size(ecEntri);
        if (ecEntri) {
            info.ukuran = 0;
        }
        hasil.push_back(info);
    }

    std::sort(hasil.begin(), hasil.end(),
              [](const InfoFile& a, const InfoFile& b) {
                  return a.jalur.filename().string() < b.jalur.filename().string();
              });
    return hasil;
}

void tampilkan(const std::vector<InfoFile>& hasil) {
    if (hasil.empty()) {
        std::println("Tidak ada berkas yang cocok dengan kriteria.");
        return;
    }
    for (const InfoFile& berkas : hasil) {
        std::println("{}", berkas.jalur.filename().string());
    }
    std::println("ditemukan: {} berkas lolos filter", hasil.size());
}

int main(int argc, char* argv[]) {
    const std::vector<std::string> argumen(argv + 1, argv + argc);

    fs::path folder = "demo-folder";
    Kriteria kriteria;

    for (std::size_t i = 0; i < argumen.size(); ++i) {
        const std::string& arg = argumen[i];
        if (arg == "--cari" && i + 1 < argumen.size()) {
            kriteria.kata = argumen[++i];
        } else if (arg == "--ekstensi" && i + 1 < argumen.size()) {
            kriteria.ekstensi = argumen[++i];
        } else if (arg == "--min-ukuran" && i + 1 < argumen.size()) {
            kriteria.ukuranMin = std::stoull(argumen[++i]);
        } else {
            folder = arg;
        }
    }

    // Folder default dibuat sendiri bila belum ada; folder lain harus sudah ada.
    if (folder == "demo-folder") {
        if (!siapkanFolderContoh(folder)) {
            return 1;
        }
    } else {
        std::error_code ecAda;
        if (!fs::exists(folder, ecAda) || ecAda) {
            std::println(std::cerr, "Galat: folder '{}' tidak ada.", folder.string());
            return 1;
        }
    }

    std::error_code ec;
    const std::vector<InfoFile> semua = kumpulkanEntri(folder, ec);
    if (ec) {
        std::println(std::cerr, "Galat membaca folder '{}': {}", folder.string(),
                     ec.message());
        return 1;
    }

    std::vector<InfoFile> cocok;
    cocok.reserve(semua.size());
    for (const InfoFile& berkas : semua) {
        if (lolos(berkas, kriteria)) {
            cocok.push_back(berkas);
        }
    }

    tampilkan(cocok);
    return 0;
}

Tiga tambahan dibanding versi Bagian 1. Pengukuran di scanner: it->file_size(ecEntri) diambil langsung dari directory_entry — pustaka standar sudah mengukurnya saat berjalan, jadi kita tidak perlu stat kedua. Penguraian opsi: loop for di main berjalan di atas salinan argv; opsi yang butuh nilai mengonsumsi token berikutnya lewat argumen[++i]. Pemisahan dua tahap: kumpulkan dulu semua entri, lalu saring ke vector cocok — struktur ini kelak membuat harness pengujian bisa menguji filter tanpa menyentuh folder mana pun.

Satu kejujuran perlu dicatat sekarang: penguraian opsi versi ini masih percaya diri. std::stoull("abc") akan melempar exception, opsi ejaannya salah diam-diam diperlakukan sebagai nama folder, dan tidak ada --bantu. Semua celah itu kita tutup di Bagian 4 — dengan sengaja dibiarkan terbuka di sini agar perbaikannya terlihat nyata.

Mencoba Filter #

Empat percobaan berturut-turut pada folder contoh dari Bagian 1:

bash
g++ -std=c++23 -Wall -Wextra pengelola-v2.cpp -o pengelola-v2
./pengelola-v2 demo-folder --ekstensi .txt
./pengelola-v2 demo-folder --cari laporan
./pengelola-v2 demo-folder --min-ukuran 30
./pengelola-v2 demo-folder --ekstensi .md

Output yang Dihasilkan:

text
catatan.txt
ditemukan: 1 berkas lolos filter
laporan.csv
ditemukan: 1 berkas lolos filter
catatan.txt
laporan.csv
ditemukan: 2 berkas lolos filter
Tidak ada berkas yang cocok dengan kriteria.

Empat hasil ini melatih keempat jalur lolos. Filter .txt menyisakan catatan.txt — satu-satunya berkas dengan ekstensi itu. Kata laporan hanya ada pada laporan.csv; inilah pencarian substring, bukan pencocokan persis. Ambang 30 byte melewatkan catatan.txt (44) dan laporan.csv (34) tetapi menyisihkan gambar.png (26). Dan filter yang tidak cocok dengan apa pun tidak menghentikan program: ia mencetak baris “Tidak ada berkas yang cocok” dan keluar dengan exit code 0 — pencarian yang gagal bukan galat, melainkan hasil kosong yang sah.

Dengan filter bekerja, program kita sudah berguna — tetapi masih menyembunyikan informasi. Berapa byte persisnya tiap berkas, hak aksesnya apa, kapan terakhir diubah? Bagian 3 membuka lapisan metadata itu.

Rujukan resmi: std::filesystem::path (cppreference) , std::string::contains (cppreference) .