Setelah CRUD dasar berjalan, kita menambahkan dua fitur query: task_filter_status untuk menampilkan hanya tugas dengan status tertentu, dan task_cari untuk mencari tugas yang judulnya mengandung substring tertentu. Filter memakai perbandingan equality pada enum, sementara pencarian memakai strstr dari <string.h> untuk mencari substring dalam string.
Konsep #
Filter berbeda dari pencarian dalam dua hal: (1) filter membandingkan field diskret (enum) sedangkan pencarian membandingkan string; (2) filter mengembalikan banyak hasil sedangkan pencarian mengembalikan satu indeks pertama. Kedua operasi ini sama-sama iterasi linier O(n) — sederhana dan cukup untuk skala aplikasi pengelola tugas pribadi.
Fungsi task_filter_status menerima parameter TaskStatus status dan FILE *out untuk menulis hasilnya. Memakai FILE *out (bukan langsung stdout) membuat fungsi ini dapat diuji dengan menulis ke stderr atau fmemopen (buffer in-memory) — fleksibilitas yang penting untuk testing.
Fungsi task_cari memakai strstr(haystack, needle) yang mengembalikan pointer ke kemunculan pertama needle di haystack, atau NULL bila tidak ditemukan. Pencarian bersifat case-sensitive: “belajar” tidak cocok dengan “Belajar”. Untuk dukungan case-insensitive, kita perlu strcasestr (ekstensi GNU, tidak portabel) atau implementasi manual.
Implementasi #
Fungsi task_filter_status:
int task_filter_status(const TaskList *list, TaskStatus status, FILE *out) {
if (list == 0 || out == 0) return 0;
int count = 0;
fprintf(out, "%-4s %-30s %-12s %-10s\n",
"ID", "Judul", "Status", "Prioritas");
for (int i = 0; i < list->jumlah; i++) {
const Task *t = &list->data[i];
if (t->status == status) {
fprintf(out, "%-4d %-30s %-12s %-10s\n",
t->id, t->judul,
task_status_nama(t->status),
task_prioritas_nama(t->prioritas));
count++;
}
}
fprintf(out, "Ditemukan: %d tugas\n", count);
return count;
}Fungsi task_cari:
int task_cari(const TaskList *list, const char *kata_kunci) {
if (list == 0 || kata_kunci == 0) return -1;
for (int i = 0; i < list->jumlah; i++) {
if (strstr(list->data[i].judul, kata_kunci) != 0) {
return i;
}
}
return -1;
}Kode Lengkap #
Kedua fungsi tersebut sudah merupakan implementasi final. Pemanggilan dari main.c:
static void menu_filter(const TaskList *list) {
int s;
if (!baca_int("Filter status (0=Belum,1=Proses,2=Selesai): ", &s)) {
printf("Status tidak valid.\n"); return;
}
if (s < TASK_STATUS_BELUM || s > TASK_STATUS_SELESAI) {
printf("Status tidak valid.\n"); return;
}
task_filter_status(list, (TaskStatus)s, stdout);
}
static void menu_cari(const TaskList *list) {
char kata[TASK_JUDUL_MAKS];
baca_baris("Kata kunci judul: ", kata, sizeof(kata));
int idx = task_cari(list, kata);
if (idx < 0) {
printf("Tidak ada tugas dengan judul mengandung '%s'.\n", kata);
} else {
const Task *t = &list->data[idx];
printf("Ditemukan: [%d] %s | %s | %s\n",
t->id, t->judul,
task_status_nama(t->status),
task_prioritas_nama(t->prioritas));
}
}Pengujian #
Demo filter dan cari:
cat > /tmp/p4b3_demo.c << 'EOF'
#include "task.h"
#include <stdio.h>
int main(void) {
TaskList list;
task_list_init(&list);
task_tambah(&list, "Belajar C", "", TASK_PRIORITAS_SEDANG);
task_tambah(&list, "Belajar Python", "", TASK_PRIORITAS_SEDANG);
task_tambah(&list, "Belanja", "", TASK_PRIORITAS_SEDANG);
task_set_status(&list, 2, TASK_STATUS_PROSES);
printf("=== Filter PROSES ===\n");
int n = task_filter_status(&list, TASK_STATUS_PROSES, stdout);
printf("jumlah filter = %d\n\n", n);
printf("=== Cari 'Belajar' ===\n");
int idx = task_cari(&list, "Belajar");
printf("Indeks pertama: %d (judul: %s)\n", idx, list.data[idx].judul);
printf("=== Cari 'Rust' ===\n");
printf("Hasil: %d (harus -1)\n", task_cari(&list, "Rust"));
return 0;
}
EOF
gcc -std=c11 -Wall -Wextra -I/home/z/my-project/content/c/proyek-c/proyek-04-pengelola-tugas/ -o /tmp/p4b3_demo /tmp/p4b3_demo.c /home/z/my-project/content/c/proyek-c/proyek-04-pengelola-tugas/task.c && /tmp/p4b3_demoOutput yang Dihasilkan:
=== Filter PROSES ===
ID Judul Status Prioritas
2 Belajar Python Proses Sedang
Ditemukan: 1 tugas
jumlah filter = 1
=== Cari 'Belajar' ===
Indeks pertama: 0 (judul: Belajar C)
=== Cari 'Rust' ===
Hasil: -1 (harus -1)Mari kita telaah baris-baris kunci pada task_filter_status dan task_cari:
- Baris
if (list == 0 || out == 0) return 0;: dua pointer check sekaligus. BilaoutNULL, kita tidak dapat menulis hasil, sehingga tidak ada gunanya melanjutkan. - Baris
int count = 0;: penghitung lokal yang dilacak selama iterasi. Counter ini menjadi return value sehingga pemanggil tahu berapa banyak tugas yang cocok tanpa menghitung baris output. - Baris
if (t->status == status): perbandingan enum ke enum. Aman karena keduanya bertipeTaskStatus— compiler tidak akan menyilang tipe dengan integer. - Baris
fprintf(out, "%-4d %-30s ...", ...): format lebar tetap, sama dengantask_tampilkan. Header tabel juga dicetak sehingga output filter terlihat seperti tabel utuh. - Baris
if (strstr(list->data[i].judul, kata_kunci) != 0)ditask_cari:strstrmemulangkan pointer atauNULL. Bandingkan dengan!= 0(atau!= NULL) untuk menguji “ditemukan”. - Baris
return i;: pulangkan indeks pertama yang cocok. Tidak lanjut mencari yang lain — sesuai kontrak fungsi. - Baris
return -1;di akhir: bila loop selesai tanpa menemukan, pulangkan -1 sebagai penanda “tidak ada”.
Bagian 4 akan menambahkan penyimpanan ke file teks.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
main.c
Unduh berkas/* main.c -- titik masuk aplikasi pengelola tugas. */
#include "task.h"
#include "storage.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DB_PATH "tugas.txt"
static void baca_baris(const char *prompt, char *buf, size_t ukuran) {
printf("%s", prompt);
if (fgets(buf, (int)ukuran, stdin) == 0) {
buf[0] = '\0';
return;
}
size_t n = strlen(buf);
while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) {
buf[--n] = '\0';
}
}
static int baca_int(const char *prompt, int *out) {
char buf[32];
baca_baris(prompt, buf, sizeof(buf));
char *sisa = 0;
long v = strtol(buf, &sisa, 10);
if (sisa == buf) return 0;
*out = (int)v;
return 1;
}
static void menu_tambah(TaskList *list) {
char judul[TASK_JUDUL_MAKS];
char deskripsi[TASK_DESKRIPSI_MAKS];
baca_baris("Judul : ", judul, sizeof(judul));
baca_baris("Deskripsi : ", deskripsi, sizeof(deskripsi));
int p = 0;
if (!baca_int("Prioritas (1=Rendah,2=Sedang,3=Tinggi): ", &p)) {
printf("Prioritas tidak valid.\n"); return;
}
TaskResult r = task_tambah(list, judul, deskripsi, (TaskPrioritas)p);
if (r == TASK_OK) {
printf("Tugas ditambahkan (id=%d).\n", list->data[list->jumlah - 1].id);
} else {
printf("Gagal menambah (kode %d).\n", r);
}
}
static void menu_set_status(TaskList *list) {
int id, s;
if (!baca_int("ID tugas : ", &id)) { printf("ID tidak valid.\n"); return; }
if (!baca_int("Status (0=Belum,1=Proses,2=Selesai): ", &s)) {
printf("Status tidak valid.\n"); return;
}
TaskResult r = task_set_status(list, id, (TaskStatus)s);
if (r == TASK_OK) printf("Status diubah.\n");
else if (r == TASK_ERR_TIDAK_DITEMUKAN) printf("Tugas tidak ditemukan.\n");
else printf("Input tidak valid.\n");
}
static void menu_hapus(TaskList *list) {
int id;
if (!baca_int("ID tugas yang dihapus: ", &id)) { printf("ID tidak valid.\n"); return; }
TaskResult r = task_hapus(list, id);
if (r == TASK_OK) printf("Tugas dihapus.\n");
else if (r == TASK_ERR_TIDAK_DITEMUKAN) printf("Tugas tidak ditemukan.\n");
}
static void menu_filter(const TaskList *list) {
int s;
if (!baca_int("Filter status (0=Belum,1=Proses,2=Selesai): ", &s)) {
printf("Status tidak valid.\n"); return;
}
if (s < TASK_STATUS_BELUM || s > TASK_STATUS_SELESAI) {
printf("Status tidak valid.\n"); return;
}
task_filter_status(list, (TaskStatus)s, stdout);
}
static void menu_cari(const TaskList *list) {
char kata[TASK_JUDUL_MAKS];
baca_baris("Kata kunci judul: ", kata, sizeof(kata));
int idx = task_cari(list, kata);
if (idx < 0) {
printf("Tidak ada tugas dengan judul mengandung '%s'.\n", kata);
} else {
const Task *t = &list->data[idx];
printf("Ditemukan: [%d] %s | %s | %s\n",
t->id, t->judul,
task_status_nama(t->status),
task_prioritas_nama(t->prioritas));
}
}
int main(void) {
TaskList list;
task_list_init(&list);
StorageStatus sm = storage_muat(&list, DB_PATH);
if (sm == STORAGE_OK) {
printf("Memuat %d tugas dari %s.\n", list.jumlah, DB_PATH);
} else if (sm == STORAGE_ERR_BUKA_FILE) {
printf("Belum ada file %s. Memulai dengan daftar kosong.\n", DB_PATH);
} else {
printf("Peringatan: format file rusak (kode %d).\n", sm);
}
int lanjut = 1;
while (lanjut) {
printf("\n=== Pengelola Tugas ===\n");
printf("1. Tambah\n2. Tampilkan\n3. Filter status\n");
printf("4. Cari judul\n5. Set status\n6. Hapus\n7. Keluar\n");
int pilihan = 0;
if (!baca_int("Pilih: ", &pilihan)) { printf("Pilihan tidak valid.\n"); continue; }
switch (pilihan) {
case 1: menu_tambah(&list); break;
case 2: task_tampilkan(&list, stdout); break;
case 3: menu_filter(&list); break;
case 4: menu_cari(&list); break;
case 5: menu_set_status(&list); break;
case 6: menu_hapus(&list); break;
case 7: lanjut = 0; break;
default: printf("Pilihan tidak dikenal.\n"); break;
}
}
StorageStatus ss = storage_simpan(&list, DB_PATH);
if (ss == STORAGE_OK) printf("Menyimpan %d tugas ke %s.\n", list.jumlah, DB_PATH);
else printf("Gagal menyimpan (kode %d).\n", ss);
printf("Terima kasih.\n");
return 0;
}
storage.c
Unduh berkas/* storage.c -- persistensi tugas ke file teks. */
#include "storage.h"
#include <stdio.h>
#include <string.h>
static void potong_newline(char *s) {
size_t n = strlen(s);
while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) {
s[--n] = '\0';
}
}
StorageStatus storage_simpan(const TaskList *list, const char *path) {
if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
FILE *f = fopen(path, "w");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
fprintf(f, "%d\n", list->jumlah);
for (int i = 0; i < list->jumlah; i++) {
const Task *t = &list->data[i];
/* Ganti | di judul/deskripsi dengan spasi agar parsing tetap aman. */
fprintf(f, "%d|%s|%s|%d|%d\n",
t->id, t->judul, t->deskripsi, (int)t->status, (int)t->prioritas);
}
if (fclose(f) != 0) return STORAGE_ERR_TULIS;
return STORAGE_OK;
}
StorageStatus storage_muat(TaskList *list, const char *path) {
if (list == 0 || path == 0) return STORAGE_ERR_BUKA_FILE;
FILE *f = fopen(path, "r");
if (f == 0) return STORAGE_ERR_BUKA_FILE;
int jumlah = 0;
if (fscanf(f, "%d\n", &jumlah) != 1) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
if (jumlah < 0 || jumlah > TASK_MAKS) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
char baris[512];
int id_terbesar = list->id_berikutnya - 1;
for (int i = 0; i < jumlah; i++) {
if (fgets(baris, sizeof(baris), f) == 0) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
potong_newline(baris);
int id, status, prioritas;
char judul[TASK_JUDUL_MAKS];
char deskripsi[TASK_DESKRIPSI_MAKS];
int parsed = sscanf(baris, "%d|%79[^|]|%199[^|]|%d|%d",
&id, judul, deskripsi, &status, &prioritas);
if (parsed < 5) {
fclose(f);
return STORAGE_ERR_FORMAT;
}
if (list->jumlah >= TASK_MAKS) break;
Task *t = &list->data[list->jumlah];
t->id = id;
strncpy(t->judul, judul, TASK_JUDUL_MAKS - 1);
t->judul[TASK_JUDUL_MAKS - 1] = '\0';
strncpy(t->deskripsi, deskripsi, TASK_DESKRIPSI_MAKS - 1);
t->deskripsi[TASK_DESKRIPSI_MAKS - 1] = '\0';
t->status = (TaskStatus)status;
t->prioritas = (TaskPrioritas)prioritas;
list->jumlah++;
if (id > id_terbesar) id_terbesar = id;
}
list->id_berikutnya = id_terbesar + 1;
fclose(f);
return STORAGE_OK;
}
task.c
Unduh berkas/* task.c -- implementasi model dan operasi tugas. */
#include "task.h"
#include <string.h>
#include <stdio.h>
void task_list_init(TaskList *list) {
if (list == 0) return;
list->jumlah = 0;
list->id_berikutnya = 1;
}
int task_valid_judul(const char *judul) {
if (judul == 0 || judul[0] == '\0') return 0;
if (strlen(judul) >= TASK_JUDUL_MAKS) return 0;
return 1;
}
int task_valid_prioritas(int p) {
return p == TASK_PRIORITAS_RENDAH
|| p == TASK_PRIORITAS_SEDANG
|| p == TASK_PRIORITAS_TINGGI;
}
TaskResult task_tambah(TaskList *list,
const char *judul,
const char *deskripsi,
TaskPrioritas prioritas) {
if (list == 0) return TASK_ERR_INPUT_INVALID;
if (!task_valid_judul(judul)) return TASK_ERR_INPUT_INVALID;
if (!task_valid_prioritas((int)prioritas)) return TASK_ERR_INPUT_INVALID;
if (list->jumlah >= TASK_MAKS) return TASK_ERR_PENUH;
Task *t = &list->data[list->jumlah];
t->id = list->id_berikutnya++;
strncpy(t->judul, judul, TASK_JUDUL_MAKS - 1);
t->judul[TASK_JUDUL_MAKS - 1] = '\0';
strncpy(t->deskripsi, deskripsi ? deskripsi : "", TASK_DESKRIPSI_MAKS - 1);
t->deskripsi[TASK_DESKRIPSI_MAKS - 1] = '\0';
t->status = TASK_STATUS_BELUM;
t->prioritas = prioritas;
list->jumlah++;
return TASK_OK;
}
void task_tampilkan(const TaskList *list, FILE *out) {
if (list == 0 || out == 0) return;
fprintf(out, "%-4s %-30s %-12s %-10s\n",
"ID", "Judul", "Status", "Prioritas");
for (int i = 0; i < list->jumlah; i++) {
const Task *t = &list->data[i];
fprintf(out, "%-4d %-30s %-12s %-10s\n",
t->id, t->judul,
task_status_nama(t->status),
task_prioritas_nama(t->prioritas));
}
fprintf(out, "Total: %d tugas\n", list->jumlah);
}
TaskResult task_set_status(TaskList *list, int id, TaskStatus status) {
if (list == 0) return TASK_ERR_INPUT_INVALID;
if (status < TASK_STATUS_BELUM || status > TASK_STATUS_SELESAI) {
return TASK_ERR_INPUT_INVALID;
}
for (int i = 0; i < list->jumlah; i++) {
if (list->data[i].id == id) {
list->data[i].status = status;
return TASK_OK;
}
}
return TASK_ERR_TIDAK_DITEMUKAN;
}
TaskResult task_hapus(TaskList *list, int id) {
if (list == 0) return TASK_ERR_INPUT_INVALID;
for (int i = 0; i < list->jumlah; i++) {
if (list->data[i].id == id) {
for (int j = i; j < list->jumlah - 1; j++) {
list->data[j] = list->data[j + 1];
}
list->jumlah--;
return TASK_OK;
}
}
return TASK_ERR_TIDAK_DITEMUKAN;
}
int task_filter_status(const TaskList *list, TaskStatus status, FILE *out) {
if (list == 0 || out == 0) return 0;
int count = 0;
fprintf(out, "%-4s %-30s %-12s %-10s\n",
"ID", "Judul", "Status", "Prioritas");
for (int i = 0; i < list->jumlah; i++) {
const Task *t = &list->data[i];
if (t->status == status) {
fprintf(out, "%-4d %-30s %-12s %-10s\n",
t->id, t->judul,
task_status_nama(t->status),
task_prioritas_nama(t->prioritas));
count++;
}
}
fprintf(out, "Ditemukan: %d tugas\n", count);
return count;
}
int task_cari(const TaskList *list, const char *kata_kunci) {
if (list == 0 || kata_kunci == 0) return -1;
for (int i = 0; i < list->jumlah; i++) {
if (strstr(list->data[i].judul, kata_kunci) != 0) {
return i;
}
}
return -1;
}
const char *task_status_nama(TaskStatus s) {
switch (s) {
case TASK_STATUS_BELUM: return "Belum";
case TASK_STATUS_PROSES: return "Proses";
case TASK_STATUS_SELESAI:return "Selesai";
default: return "?";
}
}
const char *task_prioritas_nama(TaskPrioritas p) {
switch (p) {
case TASK_PRIORITAS_RENDAH: return "Rendah";
case TASK_PRIORITAS_SEDANG: return "Sedang";
case TASK_PRIORITAS_TINGGI: return "Tinggi";
default: return "?";
}
}
tests.c
Unduh berkas/* tests.c -- rangkaian test untuk modul task dan storage. */
#include "task.h"
#include "storage.h"
#include <stdio.h>
#include <string.h>
static int lulus = 0;
static int gagal = 0;
#define PERIKSA(kondisi, nama) \
do { \
if (kondisi) { lulus++; printf("[LULUS] %s\n", nama); } \
else { gagal++; printf("[GAGAL] %s\n", nama); } \
} while (0)
static void test_tambah(void) {
TaskList list;
task_list_init(&list);
TaskResult r1 = task_tambah(&list, "Belajar C", "Bab struct", TASK_PRIORITAS_TINGGI);
TaskResult r2 = task_tambah(&list, "Belanja", "Beli susu", TASK_PRIORITAS_SEDANG);
PERIKSA(r1 == TASK_OK && r2 == TASK_OK, "Tambah 2 tugas berhasil");
PERIKSA(list.jumlah == 2, "Jumlah = 2");
PERIKSA(list.data[0].id == 1, "ID pertama = 1");
PERIKSA(list.data[0].status == TASK_STATUS_BELUM, "Status awal = BELUM");
}
static void test_tambah_invalid(void) {
TaskList list;
task_list_init(&list);
PERIKSA(task_tambah(&list, "", "x", TASK_PRIORITAS_SEDANG) == TASK_ERR_INPUT_INVALID,
"Judul kosong ditolak");
PERIKSA(task_tambah(&list, "X", "Y", (TaskPrioritas)99) == TASK_ERR_INPUT_INVALID,
"Prioritas invalid ditolak");
PERIKSA(list.jumlah == 0, "Tidak ada tugas ditambahkan");
}
static void test_set_status(void) {
TaskList list;
task_list_init(&list);
task_tambah(&list, "T1", "D1", TASK_PRIORITAS_SEDANG);
TaskResult r = task_set_status(&list, 1, TASK_STATUS_SELESAI);
PERIKSA(r == TASK_OK, "Set status SELESAI berhasil");
PERIKSA(list.data[0].status == TASK_STATUS_SELESAI, "Status = SELESAI");
PERIKSA(task_set_status(&list, 999, TASK_STATUS_PROSES) == TASK_ERR_TIDAK_DITEMUKAN,
"Set status ID tidak ada -> error");
}
static void test_hapus(void) {
TaskList list;
task_list_init(&list);
task_tambah(&list, "T1", "D1", TASK_PRIORITAS_SEDANG);
task_tambah(&list, "T2", "D2", TASK_PRIORITAS_SEDANG);
task_tambah(&list, "T3", "D3", TASK_PRIORITAS_SEDANG);
TaskResult r = task_hapus(&list, 2);
PERIKSA(r == TASK_OK && list.jumlah == 2, "Hapus id=2 berhasil");
PERIKSA(strcmp(list.data[1].judul, "T3") == 0,
"Setelah hapus, elemen 1 = 'T3'");
}
static void test_filter_status(void) {
TaskList list;
task_list_init(&list);
task_tambah(&list, "T1", "D1", TASK_PRIORITAS_SEDANG); /* BELUM */
task_tambah(&list, "T2", "D2", TASK_PRIORITAS_SEDANG);
task_tambah(&list, "T3", "D3", TASK_PRIORITAS_SEDANG);
task_set_status(&list, 2, TASK_STATUS_PROSES);
task_set_status(&list, 3, TASK_STATUS_SELESAI);
int n = task_filter_status(&list, TASK_STATUS_PROSES, stdout);
PERIKSA(n == 1, "Filter PROSES menemukan 1 tugas");
}
static void test_cari(void) {
TaskList list;
task_list_init(&list);
task_tambah(&list, "Belajar C", "", TASK_PRIORITAS_SEDANG);
task_tambah(&list, "Belajar Python", "", TASK_PRIORITAS_SEDANG);
int idx = task_cari(&list, "Python");
PERIKSA(idx == 1, "Cari 'Python' -> indeks 1");
PERIKSA(task_cari(&list, "Rust") == -1, "Cari 'Rust' -> -1");
}
static void test_penyimpanan(void) {
TaskList list;
task_list_init(&list);
task_tambah(&list, "T1", "D1", TASK_PRIORITAS_TINGGI);
task_tambah(&list, "T2", "D2", TASK_PRIORITAS_RENDAH);
task_set_status(&list, 2, TASK_STATUS_SELESAI);
StorageStatus ss = storage_simpan(&list, "/tmp/test_tugas.txt");
PERIKSA(ss == STORAGE_OK, "Simpan ke /tmp/test_tugas.txt");
TaskList dimuat;
task_list_init(&dimuat);
ss = storage_muat(&dimuat, "/tmp/test_tugas.txt");
PERIKSA(ss == STORAGE_OK, "Muat dari /tmp/test_tugas.txt");
PERIKSA(dimuat.jumlah == 2, "Jumlah dimuat = 2");
PERIKSA(strcmp(dimuat.data[0].judul, "T1") == 0, "Judul dimuat = 'T1'");
PERIKSA(dimuat.data[1].status == TASK_STATUS_SELESAI, "Status dimuat = SELESAI");
PERIKSA(dimuat.id_berikutnya == 3, "id_berikutnya = 3");
}
int main(void) {
test_tambah();
test_tambah_invalid();
test_set_status();
test_hapus();
test_filter_status();
test_cari();
test_penyimpanan();
printf("\nRingkasan: %d lulus, %d gagal\n", lulus, gagal);
return gagal == 0 ? 0 : 1;
}