diff --git a/.hyf/README.md b/.hyf/README.md index 38f1a4d..7475329 100644 --- a/.hyf/README.md +++ b/.hyf/README.md @@ -1,6 +1,7 @@ # Auto grade tool ## How it works + 1. The auto grade tool runs the `test.sh` script located in this directory. 2. `test.sh` should write to a file named `score.json` with following JSON format: ```json @@ -12,4 +13,3 @@ ``` All scores are out of 100. It is up to the assignment to determine how to calculate the score. 3. The auto grade runs via a github action on PR creation and updates the PR with the score. - diff --git a/.hyf/score.example.json b/.hyf/score.example.json index 8d931f5..e048aec 100644 --- a/.hyf/score.example.json +++ b/.hyf/score.example.json @@ -2,4 +2,4 @@ "score": 75, "pass": true, "passingScore": 50 -} \ No newline at end of file +} diff --git a/README.md b/README.md index c0b5992..9dcb586 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Core program week 6 assignment + The week 6 assignment for the HackYourFuture Core program can be found at the following link: https://hub.hackyourfuture.nl/core-program-week-6-assignment ## Implementation Instructions -- Implement the requirements in the `reading-list-manager` folder. -- Use the existing file structure. -- You are allowed to create additional files if needed. \ No newline at end of file + +- Implement the requirements in the `reading-list-manager` folder. +- Use the existing file structure. +- You are allowed to create additional files if needed. diff --git a/.gitignore b/reading-list-manager/.gitignore similarity index 100% rename from .gitignore rename to reading-list-manager/.gitignore diff --git a/reading-list-manager/.prettierrc b/reading-list-manager/.prettierrc new file mode 100644 index 0000000..b0ec5b3 --- /dev/null +++ b/reading-list-manager/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 80 +} diff --git a/reading-list-manager/app.js b/reading-list-manager/app.js index b0365ef..78dca25 100644 --- a/reading-list-manager/app.js +++ b/reading-list-manager/app.js @@ -1,13 +1,28 @@ -// This is the entrypoint for your application. -// node app.js +import { + loadBooks, + addBook, + getReadBooks, + getBooksByGenre, + markAsRead, + printAllBooks, + printSummary, +} from './readingList.js'; -// TODO: Implement the main application logic here -// 1. Load books on startup -// 2. Display all books -// 3. Show summary statistics -// 4. Add example of filtering by genre or read/unread status -// 5. Add example of marking a book as read - -console.log('📚 MY READING LIST 📚\n'); - -// Your implementation here +function app() { + console.log(loadBooks()); + console.log( + addBook({ + id: 8, + title: 'The Alchemist', + author: 'Paulo Coelho', + genre: 'Fiction', + read: false, + }) + ); + console.log(printAllBooks()); + console.log(getReadBooks()); + console.log(getBooksByGenre('Self-Help')); + console.log(markAsRead(3)); + console.log(printSummary()); +} +app(); \ No newline at end of file diff --git a/reading-list-manager/books.json b/reading-list-manager/books.json index 0637a08..8fc8057 100644 --- a/reading-list-manager/books.json +++ b/reading-list-manager/books.json @@ -1 +1,58 @@ -[] \ No newline at end of file +[ + { + "id": 1, + "title": "1984", + "author": "George Orwell", + "genre": "Fiction", + "read": false + }, + { + "id": 2, + "title": "To Kill a Mockingbird", + "author": "Harper Lee", + "genre": "Fiction", + "read": true + }, + { + "id": 3, + "title": "Sapiens", + "author": "Yuval Noah Harari", + "genre": "Non-Fiction", + "read": true + }, + { + "id": 4, + "title": "The Great Gatsby", + "author": "F. Scott Fitzgerald", + "genre": "Fiction", + "read": true + }, + { + "id": 5, + "title": "Dune", + "author": "Frank Herbert", + "genre": "Sci-Fi", + "read": false + }, + { + "id": 6, + "title": "Atomic Habits", + "author": "James Clear", + "genre": "Self-Help", + "read": true + }, + { + "id": 7, + "title": "The Hitchhiker's Guide to the Galaxy", + "author": "Douglas Adams", + "genre": "Sci-Fi", + "read": false + }, + { + "id": 8, + "title": "The Alchemist", + "author": "Paulo Coelho", + "genre": "Fiction", + "read": false + } +] \ No newline at end of file diff --git a/reading-list-manager/package-lock.json b/reading-list-manager/package-lock.json new file mode 100644 index 0000000..5707c1a --- /dev/null +++ b/reading-list-manager/package-lock.json @@ -0,0 +1,105 @@ +{ + "name": "c55-core-week-6", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "c55-core-week-6", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^4.1.2" + }, + "devDependencies": { + "prettier": "^3.8.1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/reading-list-manager/package.json b/reading-list-manager/package.json new file mode 100644 index 0000000..3eafa54 --- /dev/null +++ b/reading-list-manager/package.json @@ -0,0 +1,29 @@ +{ + "name": "c55-core-week-6", + "version": "1.0.0", + "description": "The week 6 assignment for the HackYourFuture Core program can be found at the following link: https://hub.hackyourfuture.nl/core-program-week-6-assignment", + "main": "app.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "format": "prettier --write .", + "start": "node app.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/pavel-tisner/c55-core-week-6.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/pavel-tisner/c55-core-week-6/issues" + }, + "homepage": "https://github.com/pavel-tisner/c55-core-week-6#readme", + "dependencies": { + "chalk": "^4.1.2" + }, + "devDependencies": { + "prettier": "^3.8.1" + } +} diff --git a/reading-list-manager/readingList.js b/reading-list-manager/readingList.js index 84febab..f9ed4de 100644 --- a/reading-list-manager/readingList.js +++ b/reading-list-manager/readingList.js @@ -1,53 +1,134 @@ -// Place here the file operation functions for loading and saving books +import fs from 'node:fs'; +import path from 'node:path'; +import chalk from 'chalk'; + +const FILE_PATH = path.join('books.json'); + +function createEmptyArray() { + fs.writeFileSync(FILE_PATH, JSON.stringify([])); + return []; +} function loadBooks() { - // TODO: Implement this function - // Read from books.json - // Handle missing file (create empty array) - // Handle invalid JSON (notify user, use empty array) - // Use try-catch for error handling + try { + if (!fs.existsSync(FILE_PATH)) { + throw new Error('Missing file'); + } + const books = fs.readFileSync(FILE_PATH, 'utf8'); + + if (!books) { + console.log('Books list cannot be empty'); + } + + return JSON.parse(books); + } catch (error) { + console.log(error.message); + return createEmptyArray(FILE_PATH); + } } function saveBooks(books) { - // TODO: Implement this function - // Write books array to books.json - // Use try-catch for error handling + try { + fs.writeFileSync(FILE_PATH, JSON.stringify(books, null, 2)); + return true; + } catch (error) { + console.error('Error saving books:', error.message); + return false; + } } function addBook(book) { - // TODO: Implement this function + const books = loadBooks(); + const newBook = { + id: books.length > 0 ? Math.max(...books.map((b) => b.id)) + 1 : 1, + title: book.title, + author: book.author, + genre: book.genre, + read: typeof book.read === 'boolean' ? book.read : false, + }; + const isBookAlreadyAdded = books.some( + ({ title, author }) => title === newBook.title && author === newBook.author + ); + if (isBookAlreadyAdded) { + return 'The book is already added'; + } + books.push(newBook); + saveBooks(books); + return newBook; +} + +function getReadBooks() { + const books = loadBooks(); + return books.filter((book) => book.read === true); } function getUnreadBooks() { - // TODO: Implement this function using filter() + const books = loadBooks(); + return books.filter((book) => book.read === false); } function getBooksByGenre(genre) { - // TODO: Implement this function using filter() + const books = loadBooks(); + return books.filter((book) => book.genre === genre); } function markAsRead(id) { - // TODO: Implement this function using map() + const books = loadBooks(); + const updatedBooks = books.map((book) => + book.id === id ? { ...book, read: true } : book + ); + saveBooks(updatedBooks); + return `${updatedBooks.find((book) => book.id === id)?.title} by ${updatedBooks.find((book) => book.id === id)?.author} - read`; } function getTotalBooks() { - // TODO: Implement this function using length + return loadBooks().length; } function hasUnreadBooks() { - // TODO: Implement this function using some() + const books = loadBooks(); + return books.some((book) => book.read === false); } function printAllBooks() { - // TODO: Implement this function - // Loop through and display with chalk - // Use green for read books, yellow for unread - // Use cyan for titles + const books = loadBooks(); + let result = ''; + books.forEach((book) => { + const { id, title, author, genre, read } = book; + const coloredRead = + read === true ? chalk.green('✓ Read') : chalk.yellow('⚠ Unread'); + const coloredTitle = chalk.cyan(title); + result += `${id}. ${coloredTitle} by ${author} (${genre}) ${coloredRead}\n`; + }); + return result; } function printSummary() { - // TODO: Implement this function - // Show statistics with chalk - // Display total books, read count, unread count - // Use bold for stats -} \ No newline at end of file + const allBooks = printAllBooks(); + const numTotalBooks = chalk.bold(getTotalBooks()); + const numReadBooks = chalk.bold.green(getReadBooks().length); + const numUnreadBooks = chalk.bold.yellow(getUnreadBooks().length); + return ` +📚 MY READING LIST 📚 + +All Books: +${allBooks} + +📊 SUMMARY 📊 +Total Books: ${numTotalBooks} +Read: ${numReadBooks} +Unread: ${numUnreadBooks} +`; +} + +export { + loadBooks, + saveBooks, + addBook, + getReadBooks, + getUnreadBooks, + getBooksByGenre, + markAsRead, + printAllBooks, + printSummary, +};