Last active
December 15, 2023 09:25
-
-
Save aerphanas/5307ed968312ef23c7cb7c4cb39c2cc2 to your computer and use it in GitHub Desktop.
Group 1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| __pycache__ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Any copyright is dedicated to the Public Domain. | |
| # https://creativecommons.org/publicdomain/zero/1.0/ | |
| # Author : Muhammad Aviv Burhanudin | |
| def atop(f, g): | |
| """Fungsi komposisi (B Combinator)""" | |
| def go(x): | |
| return f(g(x)) | |
| return go | |
| def identity(x): | |
| """Fungsi yang mengembalikan argumenya (I Combinator)""" | |
| return x | |
| def interupt_input(msg): | |
| """Fungsi untuk menangani input | |
| Mengembalikan None jika gagal | |
| Seperti fungsi input biasa namun | |
| fungsi ini menangani pembatalan input | |
| saat kita memberikan sinyal SIGINT atau | |
| CTRL + c | |
| """ | |
| try: | |
| return input(msg) | |
| except KeyboardInterrupt: | |
| print("\nMembatalkan Program.") | |
| return None | |
| def int_input(msg): | |
| """Fungsi untuk menangani input | |
| Mengembalikan None jika gagal | |
| Validasi input yang hanya menerima | |
| bilangan bulat | |
| """ | |
| try: | |
| ip = interupt_input("{}".format(msg)) | |
| if ip is None: | |
| return None | |
| else: | |
| return atop(int, identity)(ip) | |
| except ValueError: | |
| print("Input harus berupa bilangan bulat. Coba lagi.") | |
| return int_input(msg) | |
| def safe_int_input(itm_chk): | |
| """Fungsi yang mengecek angka input""" | |
| while True: | |
| nomor = int_input("Masukan nomor: ") | |
| if nomor is None: | |
| return None | |
| elif nomor > len(itm_chk) or nomor < 1: | |
| print("Masukan angaka dengan benar") | |
| return safe_int_input(itm_chk) | |
| else: | |
| return nomor | |
| def print_separator(): | |
| """Menampilkan separator --- di terminal""" | |
| separator = "-" * 40 | |
| return print(separator) | |
| def p_double_newline(): | |
| """Menampilkan 2 new line di terminal""" | |
| newlines = "\n\n" | |
| return print(newlines) | |
| def pprint_judul(judul): | |
| """Menampilkan judul dengan style yang sudah ditetapkan""" | |
| styled_judul = "< {} >".format(judul).center(40, "=") | |
| return print(styled_judul) | |
| def print_menu(): | |
| """Menampilkan menu aplikas ke terminal""" | |
| pprint_judul("Menu") | |
| print("1. Tampilkan Semua Novel") | |
| print("2. Tampilkan Novel dengan Genre") | |
| print("3. Tambah Novel") | |
| print("4. Hapus Novel") | |
| print("5. Update Novel") | |
| print("6. Keluar") | |
| print_separator() | |
| def l_iota(lst): | |
| """Mengembalikan iota dari list""" | |
| return atop(range, len)(lst) | |
| def red_novel(database_novel): | |
| """Menampilkan semua novel di database_novel""" | |
| pprint_judul("List Novel") | |
| for n in l_iota(database_novel): | |
| nomor = n + 1 | |
| judul = database_novel[n][0] | |
| genre = database_novel[n][1] | |
| print("{}. {} - {}".format(nomor, judul, genre)) | |
| def search_novel_genre(database_novel, genre): | |
| """Menampilkan novel dengan genre yang ditetapkan""" | |
| pprint_judul("list novel dengan genre {}".format(genre)) | |
| nama_novel = [] | |
| for novel in database_novel: | |
| if genre in novel: | |
| nama_novel.append(novel[0]) | |
| if len(nama_novel) == 0: | |
| print("(!) Tidak ada genre {} dalam database".format(genre)) | |
| else: | |
| for i in l_iota(nama_novel): | |
| print("{} - {}".format(i + 1, nama_novel[i])) | |
| def red_novel_genre(database_novel, nomor, genre): | |
| """Menampilkan novel dengan nomor genre""" | |
| genre_name = genre[nomor - 1] | |
| return search_novel_genre(database_novel, genre_name) | |
| def red_genre(genre): | |
| """Menampilkan semua genre""" | |
| pprint_judul("List Genre") | |
| for n in l_iota(genre): | |
| nomor = n + 1 | |
| nama_genre = genre[n] | |
| print("{}. {}".format(nomor, nama_genre)) | |
| def add_novel(database_novel, title, genre): | |
| """Menambahkan novel""" | |
| novel = [title, genre] | |
| return database_novel.append(novel) | |
| def del_novel(database_novel, nomor): | |
| """Menghapus novel dengan nomor index""" | |
| index = nomor - 1 | |
| return database_novel.pop(index) | |
| def update_genre(database_novel): | |
| """Mengembalikan Genre list tanpa dulpikasi""" | |
| genre = [] | |
| for g in database_novel: | |
| if g[1] not in genre: | |
| genre.append(g[1]) | |
| return genre | |
| def main(): | |
| """Main Event Loop""" | |
| # dummy list for demonstration | |
| # database_novel = [ | |
| # [ "Haskell Books" , "Functional" ], | |
| # [ "Lambda Lisp" , "Lisp" ], | |
| # [ "Lisp for web" , "Website" ], | |
| # [ "Clojure for Brave" , "Lisp" ], | |
| # [ "Python Soyjak" , "Programming" ] | |
| # ^ ^ | |
| # Judul Novel Genre | |
| # ] | |
| database_novel = [] | |
| while True: | |
| p_double_newline() | |
| print_menu() | |
| pilih = int_input("Pilih [1-6]: ") | |
| if pilih is None: | |
| break | |
| p_double_newline() | |
| if pilih == 1: | |
| # Tampilkan Semua Novel | |
| if len(database_novel) == 0: | |
| print("Database kosong, tolong tambahkan novel") | |
| continue | |
| red_novel(database_novel) | |
| elif pilih == 2: | |
| # Tampilkan Novel dengan Genre | |
| if len(database_novel) == 0: | |
| print("Database kosong, tolong tambahkan novel") | |
| continue | |
| genre = update_genre(database_novel) | |
| atop(red_genre, identity)(genre) | |
| nomor = safe_int_input(database_novel) | |
| if nomor is None: | |
| break | |
| p_double_newline() | |
| red_novel_genre(database_novel, nomor, genre) | |
| elif pilih == 3: | |
| # Tambah Novel | |
| title = interupt_input("Masukan Judul Novel: ") | |
| if title is None: | |
| break | |
| genre = interupt_input("Masukan Genre: ") | |
| if title is None: | |
| break | |
| add_novel(database_novel, title, genre) | |
| elif pilih == 4: | |
| # Hapus Novel | |
| if len(database_novel) == 0: | |
| print("Database kosong, tolong tambahkan novel") | |
| continue | |
| red_novel(database_novel) | |
| nomor = safe_int_input(database_novel) | |
| if nomor is None: | |
| break | |
| del_novel(database_novel, nomor) | |
| elif pilih == 5: | |
| # Update Novel | |
| if len(database_novel) == 0: | |
| print("Database kosong, tolong tambahkan novel") | |
| continue | |
| red_novel(database_novel) | |
| nomor = safe_int_input(database_novel) | |
| if nomor is None: | |
| break | |
| title = interupt_input("Masukan Judul Baru: ") | |
| if title is None: | |
| break | |
| genre = interupt_input("Masukan Genre Baru: ") | |
| if genre is None: | |
| break | |
| del_novel(database_novel, nomor) | |
| add_novel(database_novel, title, genre) | |
| elif pilih == 6: | |
| # keluar | |
| print("Terimakasih sudah menggunakan aplikasi ini, Bye") | |
| break | |
| else: | |
| print("Tolong masukan input dengan benar") | |
| continue | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Any copyright is dedicated to the Public Domain. | |
| # https://creativecommons.org/publicdomain/zero/1.0/ | |
| # Author : Muhammad Aviv Burhanudin | |
| import unittest | |
| from unittest.mock import patch | |
| from kerkom import ( | |
| interupt_input, | |
| int_input, | |
| safe_int_input, | |
| pprint_judul, | |
| red_novel, | |
| red_novel_genre, | |
| red_genre, | |
| add_novel, | |
| del_novel, | |
| update_genre, | |
| ) | |
| class DatabaseNovel(): | |
| database_novel = [ | |
| [ "Haskell Novels" , "Functional" ], | |
| [ "Lambda Lisp" , "Lisp" ], | |
| [ "Lisp for web" , "Website" ], | |
| [ "Clojure for Brave" , "Lisp" ], | |
| [ "Python Soyjak" , "Programming" ] | |
| ] | |
| genre = [ | |
| "Functional", | |
| "Lisp", | |
| "Website", | |
| "Programming" | |
| ] | |
| class TestKerkomInput(unittest.TestCase, DatabaseNovel): | |
| @patch('builtins.input', return_value='Test Input') | |
| def test_interrupt_input(self, mock_input): | |
| result = interupt_input("Enter something: ") | |
| self.assertEqual(result, 'Test Input') | |
| @patch('builtins.input', return_value='42') | |
| def test_int_input(self, mock_input): | |
| result = int_input("Enter an integer: ") | |
| self.assertEqual(result, 42) | |
| @patch('builtins.input', return_value='2') | |
| def test_safe_int_input(self, moce_input): | |
| test_db = DatabaseNovel.database_novel.copy() | |
| result = safe_int_input(test_db) | |
| self.assertEqual(result, 2) | |
| class TestPrintFunction(unittest.TestCase): | |
| @patch('builtins.print') | |
| def test_pprint_judul(self, mock_print): | |
| pprint_judul("List Novel") | |
| expected_calls = "=============< List Novel >=============" | |
| actual_calls = [ | |
| call[0] | |
| for call in mock_print.call_args_list | |
| ][0][0] | |
| self.assertEqual(actual_calls, expected_calls) | |
| @patch('builtins.print') | |
| def test_red_novel(self, mock_print): | |
| red_novel(DatabaseNovel.database_novel) | |
| expected_calls = [ | |
| "=============< List Novel >=============", | |
| "1. Haskell Novels - Functional", | |
| "2. Lambda Lisp - Lisp", | |
| "3. Lisp for web - Website", | |
| "4. Clojure for Brave - Lisp", | |
| "5. Python Soyjak - Programming" | |
| ] | |
| actual_calls = [ | |
| call[0][0] | |
| for call in mock_print.call_args_list | |
| ] | |
| self.assertEqual(actual_calls, expected_calls) | |
| @patch('builtins.print') | |
| def test_red_novel_genre(self, mock_print): | |
| red_novel_genre(DatabaseNovel.database_novel, | |
| 2, # Lisp | |
| DatabaseNovel.genre) | |
| expected_calls = [ | |
| "====< list novel dengan genre Lisp >====", | |
| "1 - Lambda Lisp", | |
| "2 - Clojure for Brave" | |
| ] | |
| actual_calls = [ | |
| call[0][0] | |
| for call in mock_print.call_args_list | |
| ] | |
| self.assertEqual(actual_calls, expected_calls) | |
| @patch('builtins.print') | |
| def test_red_genre(self, mock_print): | |
| red_genre(DatabaseNovel.genre) | |
| expected_calls = [ | |
| "=============< List Genre >=============", | |
| "1. Functional", | |
| "2. Lisp", | |
| "3. Website", | |
| "4. Programming", | |
| ] | |
| actual_calls = [ | |
| call[0][0] | |
| for call in mock_print.call_args_list | |
| ] | |
| self.assertEqual(actual_calls, expected_calls) | |
| class TestSideEffectFunction(unittest.TestCase, DatabaseNovel): | |
| def test_add_novel(self): | |
| test_db = DatabaseNovel.database_novel.copy() | |
| add_novel(test_db, | |
| "test", | |
| "test") | |
| expected_result = [ | |
| [ "Haskell Novels" , "Functional" ], | |
| [ "Lambda Lisp" , "Lisp" ], | |
| [ "Lisp for web" , "Website" ], | |
| [ "Clojure for Brave" , "Lisp" ], | |
| [ "Python Soyjak" , "Programming" ], | |
| [ "test" , "test" ] | |
| ] | |
| self.assertEqual(test_db, expected_result) | |
| def test_del_novel(self): | |
| test_db = DatabaseNovel.database_novel.copy() | |
| del_novel(test_db, 5) | |
| expected_result = [ | |
| [ "Haskell Novels" , "Functional" ], | |
| [ "Lambda Lisp" , "Lisp" ], | |
| [ "Lisp for web" , "Website" ], | |
| [ "Clojure for Brave" , "Lisp" ] | |
| ] | |
| self.assertEqual(test_db, expected_result) | |
| def test_update_genre(self): | |
| test_db = DatabaseNovel.database_novel.copy() | |
| result = update_genre(test_db) | |
| expected_result = [ | |
| "Lisp", | |
| "Programming", | |
| "Functional", | |
| "Website" | |
| ] | |
| for el in result: | |
| self.assertTrue(el in expected_result) | |
| if __name__ == "__main__": | |
| unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment