From 0833543e7e996f96308a2c220ad56335a1a041c0 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 22:43:30 +0700 Subject: [PATCH 01/46] Create README_vi.md --- README_vi.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 README_vi.md diff --git a/README_vi.md b/README_vi.md new file mode 100644 index 0000000..761f944 --- /dev/null +++ b/README_vi.md @@ -0,0 +1,97 @@ +# Tech Notes + +**Ghi chú kỹ thuật & mã nguồn tổng hợp** — Một kho kiến thức tập trung, bao gồm Design Patterns, Thuật toán, Cấu trúc dữ liệu, AWS và nhiều chủ đề khác. Hoàn hảo cho việc học tập, tra cứu nhanh, và sử dụng hàng ngày của lập trình viên. + +📄 Bạn cũng có thể xem README này bằng tiếng Anh: + +- 🇺🇸 [English](README.md) + +--- + +## 📚 Tổng quan + +**Tech Notes Hub** là bộ sưu tập được chọn lọc kỹ lưỡng gồm các ghi chú kỹ thuật và đoạn mã nhằm giúp lập trình viên hiểu sâu hơn và tăng tốc khi làm việc. Kho tài liệu này bao gồm nhiều chủ đề như: + +* Design Patterns (Mẫu thiết kế) +* Thuật toán & Cấu trúc dữ liệu +* Điện toán đám mây & Dịch vụ AWS +* Kiến trúc phần mềm +* Thiết kế hệ thống +* Thực tiễn tốt & mẹo lập trình + +...và nhiều chủ đề khác. + +Dự án được thiết kế để trở thành tài nguyên tham khảo hàng đầu của bạn — dù là khi học, làm dự án, hay chuẩn bị phỏng vấn. + +--- + +## 🚀 Tính năng + +* Ghi chú rõ ràng, có cấu trúc khoa học +* Giải thích ngắn gọn, dễ hiểu +* Ý tưởng không phụ thuộc ngôn ngữ, có kèm mã ví dụ bằng các ngôn ngữ phổ biến +* Nội dung được cập nhật định kỳ +* Dễ dàng tra cứu và tìm kiếm + +--- + +## 📂 Nội dung chính + +* **Design Patterns:** Singleton, Factory, Observer, Strategy,... +* **Thuật toán:** Sắp xếp, Tìm kiếm, Đồ thị, Quy hoạch động +* **Cấu trúc dữ liệu:** Mảng, Danh sách liên kết, Cây, Đồ thị, Bảng băm +* **AWS:** EC2, S3, Lambda, CloudFormation, IAM,... +* **Thiết kế hệ thống:** Khả năng mở rộng, Bộ nhớ đệm, Cân bằng tải +* **Khác:** DevOps, CI/CD, Bảo mật, v.v. + +--- + +## 💡 Vì sao nên dùng Tech Notes Hub? + +* Tiết kiệm thời gian nhờ kiến thức tập trung, dễ tra cứu +* Đoạn mã rõ ràng giúp dễ hiểu và áp dụng +* Phù hợp với ôn luyện phỏng vấn hoặc giải bài tập hàng ngày +* Là dự án mã nguồn mở, được cộng đồng đóng góp liên tục + +--- + +## 📖 Cách sử dụng + +Bạn có thể duyệt các thư mục hoặc dùng tính năng tìm kiếm của GitHub để tra cứu chủ đề hoặc mẫu thiết kế bạn cần. +Mỗi ghi chú đều độc lập, bao gồm lý thuyết và mã ví dụ thực tế. + +--- + +## 🤝 Đóng góp + +Mọi đóng góp đều rất hoan nghênh! Nếu bạn muốn: + +* Thêm ghi chú hoặc đoạn mã mới +* Cải thiện nội dung hiện tại +* Báo lỗi hoặc đề xuất chủ đề mới + +Hãy xem hướng dẫn trong file [CONTRIBUTING_vi.md](CONTRIBUTING_vi.md). + +--- + +## 📜 Giấy phép + +Dự án này được phát hành theo giấy phép MIT. Xem chi tiết trong file [LICENSE](LICENSE). + +--- + +## 🙌 Lời cảm ơn + +Cảm ơn tất cả các contributors và cộng đồng mã nguồn mở đã cùng nhau làm phong phú thêm kho kiến thức này mỗi ngày. + +--- + +## 📬 Liên hệ + +Nếu bạn có câu hỏi hoặc góp ý, hãy mở một issue hoặc liên hệ với maintainer: + +* GitHub: [tanthanhdev](https://github.com/tanthanhdev) + +--- + +**Chúc bạn code vui vẻ!** 🚀 From 09813e67260dadd87193c096a671966b51630600 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 22:44:10 +0700 Subject: [PATCH 02/46] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8d280b4..753f137 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ **All-in-one technical notes & code snippets** — A centralized knowledge base covering design patterns, algorithms, data structures, AWS, and more. Perfect for learning, quick reference, and daily developer use. +📄 This README is also available in other languages: + +- 🇻🇳 [Tiếng Việt](README_vi.md) + --- ## 📚 Overview From 0137867bf7d036db7b5461c138e18d702f5c69d5 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 22:45:27 +0700 Subject: [PATCH 03/46] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 753f137..9b659b9 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ Contributions are highly welcome! If you want to: Please follow the guidelines in the [CONTRIBUTING.md](CONTRIBUTING.md) file. +Before submitting a pull request, make sure to check the [Pull Request Rules](PULL_REQUEST_RULES.md) to see what's allowed. ✅ + --- ## 📜 License From d9b88f6c8956a5481c4cfabf42bb2e433d971d6c Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 22:46:32 +0700 Subject: [PATCH 04/46] Update README_vi.md --- README_vi.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_vi.md b/README_vi.md index 761f944..8db6933 100644 --- a/README_vi.md +++ b/README_vi.md @@ -72,6 +72,8 @@ Mọi đóng góp đều rất hoan nghênh! Nếu bạn muốn: Hãy xem hướng dẫn trong file [CONTRIBUTING_vi.md](CONTRIBUTING_vi.md). +Trước khi gửi pull request, vui lòng đọc kỹ [Quy định nội dung Pull Request](PULL_REQUEST_RULES_vi.md) để biết những gì được chấp nhận. ✅ + --- ## 📜 Giấy phép From ece75c8e482503950ab12cb4f7fa9d60961697bf Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 23:16:41 +0700 Subject: [PATCH 05/46] Create .gitignore --- .gitignore | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69d6106 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +# Node modules (if using any JS tools) +node_modules/ + +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +.env +.venv/ + +# VSCode settings +.vscode/ + +# macOS system files +.DS_Store + +# Windows system files +Thumbs.db +ehthumbs.db +Desktop.ini + +# Temporary files +*.log +*.tmp +*.swp +*.swo +*.bak + +# Build output +dist/ +build/ + +# Jupyter notebooks checkpoints (if any) +.ipynb_checkpoints/ + +# Coverage reports +htmlcov/ +coverage.xml +*.cover + +# dotenv environment files +.env.local +.env.* + +# Ignore generated files and backups +*.orig +*.rej + +# Ignore public site build if using static site generator (optional) +public/ +.cache/ + +# Ignore Markdown preview temp files (e.g., Typora) +*.assets/ + +# Ignore personal notes or drafts +private-notes/ +drafts/ From 54fcdb4a3612a541dab54a025bfcd28401711242 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 23:24:05 +0700 Subject: [PATCH 06/46] Update README.md --- README.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/README.md b/README.md index 9b659b9..e75ea4d 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,6 @@ and many more. It's designed to be your go-to resource whether you're preparing for interviews, building projects, or learning new concepts. ---- - ## 🚀 Features * Well-organized notes and examples @@ -33,8 +31,6 @@ It's designed to be your go-to resource whether you're preparing for interviews, * Regularly updated content * Easy to browse and search ---- - ## 📂 Contents * **Design Patterns:** Singleton, Factory, Observer, Strategy, etc. @@ -44,8 +40,6 @@ It's designed to be your go-to resource whether you're preparing for interviews, * **System Design:** Scalability, Caching, Load Balancing * **Miscellaneous:** DevOps, CI/CD, Security tips, etc. ---- - ## 💡 Why Use Tech Notes Hub? * Centralized knowledge saves time searching multiple resources @@ -53,14 +47,10 @@ It's designed to be your go-to resource whether you're preparing for interviews, * Great for interview prep and daily coding challenges * Open source and community-driven — contributions welcome! ---- - ## 📖 How to Use Simply browse the folders or use GitHub's search feature to find the topic or pattern you need. Each note is designed to be self-contained with theory and practical code. ---- - ## 🤝 Contribution Contributions are highly welcome! If you want to: @@ -73,20 +63,14 @@ Please follow the guidelines in the [CONTRIBUTING.md](CONTRIBUTING.md) file. Before submitting a pull request, make sure to check the [Pull Request Rules](PULL_REQUEST_RULES.md) to see what's allowed. ✅ ---- - ## 📜 License This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. ---- - ## 🙌 Acknowledgements Thanks to all contributors and the open source community for making this knowledge base better every day. ---- - ## 📬 Contact If you have any questions or suggestions, feel free to open an issue or contact the maintainer: From b90ebee67b351364867fa4f19092ec1d6a86962d Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 23:24:47 +0700 Subject: [PATCH 07/46] Update README_vi.md --- README_vi.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/README_vi.md b/README_vi.md index 8db6933..b22c1bd 100644 --- a/README_vi.md +++ b/README_vi.md @@ -23,8 +23,6 @@ Dự án được thiết kế để trở thành tài nguyên tham khảo hàng đầu của bạn — dù là khi học, làm dự án, hay chuẩn bị phỏng vấn. ---- - ## 🚀 Tính năng * Ghi chú rõ ràng, có cấu trúc khoa học @@ -33,8 +31,6 @@ Dự án được thiết kế để trở thành tài nguyên tham khảo hàng * Nội dung được cập nhật định kỳ * Dễ dàng tra cứu và tìm kiếm ---- - ## 📂 Nội dung chính * **Design Patterns:** Singleton, Factory, Observer, Strategy,... @@ -44,8 +40,6 @@ Dự án được thiết kế để trở thành tài nguyên tham khảo hàng * **Thiết kế hệ thống:** Khả năng mở rộng, Bộ nhớ đệm, Cân bằng tải * **Khác:** DevOps, CI/CD, Bảo mật, v.v. ---- - ## 💡 Vì sao nên dùng Tech Notes Hub? * Tiết kiệm thời gian nhờ kiến thức tập trung, dễ tra cứu @@ -53,15 +47,11 @@ Dự án được thiết kế để trở thành tài nguyên tham khảo hàng * Phù hợp với ôn luyện phỏng vấn hoặc giải bài tập hàng ngày * Là dự án mã nguồn mở, được cộng đồng đóng góp liên tục ---- - ## 📖 Cách sử dụng Bạn có thể duyệt các thư mục hoặc dùng tính năng tìm kiếm của GitHub để tra cứu chủ đề hoặc mẫu thiết kế bạn cần. Mỗi ghi chú đều độc lập, bao gồm lý thuyết và mã ví dụ thực tế. ---- - ## 🤝 Đóng góp Mọi đóng góp đều rất hoan nghênh! Nếu bạn muốn: @@ -74,20 +64,14 @@ Hãy xem hướng dẫn trong file [CONTRIBUTING_vi.md](CONTRIBUTING_vi.md). Trước khi gửi pull request, vui lòng đọc kỹ [Quy định nội dung Pull Request](PULL_REQUEST_RULES_vi.md) để biết những gì được chấp nhận. ✅ ---- - ## 📜 Giấy phép Dự án này được phát hành theo giấy phép MIT. Xem chi tiết trong file [LICENSE](LICENSE). ---- - ## 🙌 Lời cảm ơn Cảm ơn tất cả các contributors và cộng đồng mã nguồn mở đã cùng nhau làm phong phú thêm kho kiến thức này mỗi ngày. ---- - ## 📬 Liên hệ Nếu bạn có câu hỏi hoặc góp ý, hãy mở một issue hoặc liên hệ với maintainer: From 6bb4b6b2c6d0f4377464d3a5ceec5e391954aac8 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 23:25:24 +0700 Subject: [PATCH 08/46] Update PULL_REQUEST_RULES.md --- PULL_REQUEST_RULES.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/PULL_REQUEST_RULES.md b/PULL_REQUEST_RULES.md index c9eb636..3a3793d 100644 --- a/PULL_REQUEST_RULES.md +++ b/PULL_REQUEST_RULES.md @@ -25,8 +25,6 @@ Please read carefully before submitting a PR. - Translations with `_vi.md`, `_fr.md`, etc. suffix - Must match the structure and logic of the original file ---- - ## ❌ Not Allowed in Pull Requests - ❌ **AI-generated content** without human review or editing @@ -37,8 +35,6 @@ Please read carefully before submitting a PR. - ❌ Files with broken structure, invalid markdown, or irrelevant naming - ❌ Notes that contain **plagiarized content** (copying from copyrighted materials) ---- - ## 🔖 Style & Structure Reminders - Use clear, **conversational but concise** explanations @@ -47,8 +43,6 @@ Please read carefully before submitting a PR. - For translated files, append language suffix: `binary-search_vi.md` - Place content in the appropriate folder (`algorithms/`, `design-patterns/`, etc.) ---- - ## 📢 Final Note We appreciate your contribution and effort! From fd4d772ff5bfc0096af23355d02a3b1555c4fb47 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 23:25:57 +0700 Subject: [PATCH 09/46] Update PULL_REQUEST_RULES_vi.md --- PULL_REQUEST_RULES_vi.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/PULL_REQUEST_RULES_vi.md b/PULL_REQUEST_RULES_vi.md index bea746b..4c09f7a 100644 --- a/PULL_REQUEST_RULES_vi.md +++ b/PULL_REQUEST_RULES_vi.md @@ -25,8 +25,6 @@ Vui lòng đọc kỹ trước khi gửi PR. - Dùng hậu tố `_vi.md`, `_ja.md`, v.v. - Nội dung bám sát logic file gốc ---- - ## ❌ Không được phép trong Pull Request - ❌ **Nội dung tạo bởi AI** mà không qua chỉnh sửa thủ công hoặc kiểm duyệt @@ -37,8 +35,6 @@ Vui lòng đọc kỹ trước khi gửi PR. - ❌ File đặt tên sai quy tắc, không dùng định dạng Markdown hợp lệ - ❌ Nội dung **sao chép có bản quyền** từ nguồn khác ---- - ## 🔖 Lưu ý về cấu trúc & định dạng - Trình bày **ngắn gọn, dễ hiểu, gần gũi** @@ -47,8 +43,6 @@ Vui lòng đọc kỹ trước khi gửi PR. - File dịch có hậu tố ngôn ngữ: `cay-nhi-phan_vi.md` - Đặt đúng thư mục chuyên đề (`algorithms/`, `design-patterns/`, `aws/`,...) ---- - ## 📢 Lưu ý cuối Cảm ơn bạn đã đóng góp cho dự án! From 73e81f75a645fab242af8454892dc31e1baeda26 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 23:52:38 +0700 Subject: [PATCH 10/46] Update CONTRIBUTING_vi.md --- CONTRIBUTING_vi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING_vi.md b/CONTRIBUTING_vi.md index df4d326..95d4ee2 100644 --- a/CONTRIBUTING_vi.md +++ b/CONTRIBUTING_vi.md @@ -81,7 +81,7 @@ Tôn trọng, cởi mở và xây dựng trong mọi tương tác. Chúng ta đa ## 📩 Cần hỗ trợ? -Nếu bạn có thắc mắc hoặc ý tưởng, hãy [tạo issue mới](https://github.com/tanthanhdev/tech-notes-hub/issues). +Nếu bạn có thắc mắc hoặc ý tưởng, hãy [tạo issue mới](https://github.com/tech-notes-hub/tech-notes/issues). Một lần nữa, cảm ơn bạn đã đóng góp cho **Tech Notes Hub**! 🙌 From 95c531feeab554c41e379a6e2d5eecd985d4ed08 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 23:52:56 +0700 Subject: [PATCH 11/46] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a764099..79909d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,7 +80,7 @@ Be respectful, open-minded, and constructive in your interactions. We’re build ## 📩 Need Help? -Feel free to [open an issue](https://github.com/tanthanhdev/tech-notes-hub/issues) if you have questions, ideas, or feedback. +Feel free to [open an issue](https://github.com/tech-notes-hub/tech-notes/issues) if you have questions, ideas, or feedback. Thanks for contributing to **Tech Notes Hub**! 🙌 From 9b153f85be28751a3fa399288e9b01b4ae2ac40f Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 23:54:14 +0700 Subject: [PATCH 12/46] Update CONTRIBUTING_vi.md --- CONTRIBUTING_vi.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CONTRIBUTING_vi.md b/CONTRIBUTING_vi.md index 95d4ee2..5f629e2 100644 --- a/CONTRIBUTING_vi.md +++ b/CONTRIBUTING_vi.md @@ -84,5 +84,3 @@ Tôn trọng, cởi mở và xây dựng trong mọi tương tác. Chúng ta đa Nếu bạn có thắc mắc hoặc ý tưởng, hãy [tạo issue mới](https://github.com/tech-notes-hub/tech-notes/issues). Một lần nữa, cảm ơn bạn đã đóng góp cho **Tech Notes Hub**! 🙌 - -``` From e24eafe49948d48d5cc37b1066831a20beeac5e7 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Sun, 1 Jun 2025 23:55:46 +0700 Subject: [PATCH 13/46] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79909d6..68aa017 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,5 +83,3 @@ Be respectful, open-minded, and constructive in your interactions. We’re build Feel free to [open an issue](https://github.com/tech-notes-hub/tech-notes/issues) if you have questions, ideas, or feedback. Thanks for contributing to **Tech Notes Hub**! 🙌 - -If you want me to write a `CONTRIBUTING_vi.md` file (Vietnamese version) in this format, I can help you right away. You can also request to create a template for pull request or issue if you want to be more professional. From 0abde6f8aebe7ebf5382536e34a1a38b3d138fc2 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Mon, 2 Jun 2025 07:34:31 +0700 Subject: [PATCH 14/46] Create funding.yml --- .github/funding.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/funding.yml diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..c548d3e --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +#github: tanthanhdev +ko_fi: devphan From b8bd3131f71e7feeb0a10ad7222ba775b8a1755c Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Mon, 2 Jun 2025 15:53:42 +0700 Subject: [PATCH 15/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e75ea4d..b81c3d1 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Before submitting a pull request, make sure to check the [Pull Request Rules](PU ## 📜 License -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. +This project is licensed under the MIT License. See the [LICENSE](LICENSE.txt) file for details. ## 🙌 Acknowledgements From 30ef5bf47d9c05683d6645c832b57cf45871e365 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Mon, 2 Jun 2025 15:54:01 +0700 Subject: [PATCH 16/46] Update README_vi.md --- README_vi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_vi.md b/README_vi.md index b22c1bd..a75e2f3 100644 --- a/README_vi.md +++ b/README_vi.md @@ -66,7 +66,7 @@ Trước khi gửi pull request, vui lòng đọc kỹ [Quy định nội dung P ## 📜 Giấy phép -Dự án này được phát hành theo giấy phép MIT. Xem chi tiết trong file [LICENSE](LICENSE). +Dự án này được phát hành theo giấy phép MIT. Xem chi tiết trong file [LICENSE](LICENSE.txt). ## 🙌 Lời cảm ơn From 174e38c8c0f7434949d52e20a17e604409049bb8 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 00:27:22 +0700 Subject: [PATCH 17/46] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68aa017..bbd60fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Click the "Fork" button on the top right of this page. This will create a copy o ### 2. Clone Your Fork ```bash -git clone https://github.com/your-username/tech-notes-hub.git +git clone https://github.com/your-username/tech-notes.git cd tech-notes-hub ``` From 2fa8a46ded666d3216227d2a0308890ac1f6efdf Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 00:27:28 +0700 Subject: [PATCH 18/46] Update CONTRIBUTING_vi.md --- CONTRIBUTING_vi.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING_vi.md b/CONTRIBUTING_vi.md index 5f629e2..8c76592 100644 --- a/CONTRIBUTING_vi.md +++ b/CONTRIBUTING_vi.md @@ -24,7 +24,7 @@ Nhấn nút "Fork" ở góc trên bên phải trang GitHub để tạo một b ### 2. Clone về máy ```bash -git clone https://github.com/ten-cua-ban/tech-notes-hub.git +git clone https://github.com/ten-cua-ban/tech-notes.git cd tech-notes-hub ``` @@ -33,7 +33,7 @@ cd tech-notes-hub Đặt tên nhánh rõ ràng, mô tả ngắn gọn nội dung bạn sẽ thêm hoặc sửa: ```bash -git checkout -b feature/them-thuat-toan-do-thi +git checkout -b feature/add-graph-algorithms ``` ### 4. Thực hiện thay đổi @@ -49,7 +49,7 @@ git checkout -b feature/them-thuat-toan-do-thi ```bash git add . git commit -m "Thêm ghi chú về thuật toán đồ thị" -git push origin feature/them-thuat-toan-do-thi +git push origin feature/add-graph-algorithms ``` ### 6. Tạo Pull Request @@ -71,8 +71,8 @@ Trước khi gửi, hãy đảm bảo: ## 📁 Quy tắc đặt tên file & thư mục -* Tên file và thư mục dùng chữ thường và dấu gạch ngang: `duyet-do-thi.md` -* Nếu là bản dịch, thêm hậu tố ngôn ngữ: `duyet-do-thi_vi.md` +* Tên file và thư mục dùng chữ thường và dấu gạch ngang: `graph-traversal.md` +* Nếu là bản dịch, thêm hậu tố ngôn ngữ: `graph-traversal_vi.md` * Ghi chú nên được nhóm theo thư mục chuyên đề (ví dụ: `algorithms/`, `aws/`, `design-patterns/`) ## 🤝 Quy tắc ứng xử From 39b60e241d8b9e5a04496225b3ac942f720efb84 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 00:27:50 +0700 Subject: [PATCH 19/46] Update PULL_REQUEST_RULES_vi.md --- PULL_REQUEST_RULES_vi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PULL_REQUEST_RULES_vi.md b/PULL_REQUEST_RULES_vi.md index 4c09f7a..58b9022 100644 --- a/PULL_REQUEST_RULES_vi.md +++ b/PULL_REQUEST_RULES_vi.md @@ -39,8 +39,8 @@ Vui lòng đọc kỹ trước khi gửi PR. - Trình bày **ngắn gọn, dễ hiểu, gần gũi** - Sử dụng đúng cấp độ tiêu đề: `#`, `##`, `###`,... -- Đặt tên file bằng chữ thường, dùng dấu gạch ngang: `cay-nhi-phan.md` -- File dịch có hậu tố ngôn ngữ: `cay-nhi-phan_vi.md` +- Đặt tên file bằng chữ thường, dùng dấu gạch ngang: `binary-search.md` +- File dịch có hậu tố ngôn ngữ: `binary-search_vi.md` - Đặt đúng thư mục chuyên đề (`algorithms/`, `design-patterns/`, `aws/`,...) ## 📢 Lưu ý cuối From 76457b42d5c7d2b5370ae459db0a4e81067bb3ec Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 00:42:05 +0700 Subject: [PATCH 20/46] Update CONTRIBUTING_vi.md --- CONTRIBUTING_vi.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/CONTRIBUTING_vi.md b/CONTRIBUTING_vi.md index 8c76592..81ffa4f 100644 --- a/CONTRIBUTING_vi.md +++ b/CONTRIBUTING_vi.md @@ -36,6 +36,23 @@ cd tech-notes-hub git checkout -b feature/add-graph-algorithms ``` +### 🧩 Quy tắc đặt tên nhánh (branch naming) + +Tên nhánh nên theo cấu trúc: + +```bash +/ +``` + +| Loại | Ý nghĩa | Ví dụ | +| ---------- | -------------------------------------------------------- | -------------------------------- | +| `feature` | Thêm ghi chú/mục mới | `feature/add-docker-notes` | +| `fix` | Sửa lỗi nội dung, chính tả, ví dụ | `fix/typo-in-graph-note` | +| `update` | Cập nhật hoặc cải tiến ghi chú hiện có | `update/aws-ec2-note` | +| `refactor` | Tái cấu trúc lại file/nội dung mà không thay đổi ý chính | `refactor/reorganize-folders` | +| `remove` | Xoá nội dung lỗi thời hoặc không còn phù hợp | `remove/duplicate-array-example` | +| `docs` | Cập nhật tài liệu dự án như README, CONTRIBUTING,... | `docs/improve-readme` | + ### 4. Thực hiện thay đổi * Tuân theo cấu trúc thư mục và file có sẵn @@ -44,6 +61,30 @@ git checkout -b feature/add-graph-algorithms * Diễn giải ngắn gọn, rõ ràng * Có thể thêm chú thích nội tuyến nếu cần thiết +### 💬 Quy tắc viết commit message + +Viết commit rõ ràng, có ý nghĩa và dễ hiểu. Cấu trúc đề xuất: + +```bash +: +``` + +#### 📌 Ví dụ: + +- `feature: thêm ghi chú về HTTP Status Codes` +- `fix: sửa chính tả trong file design-patterns.md` +- `update: cập nhật ví dụ cho binary search` +- `remove: xoá ghi chú trùng lặp trong folder aws` +- `docs: thêm hướng dẫn cách tạo pull request` + +#### 🧠 Gợi ý thêm: + +- Có thể viết bằng **tiếng Việt hoặc tiếng Anh** (nhất quán trong 1 pull request) +- **Tránh commit mơ hồ** như: `update 1`, `fix lỗi`, `test` +- Nếu liên quan issue, thêm số vào cuối: + 👉 `fix: lỗi chính tả trong aws-note #12` + + ### 5. Commit & Push ```bash From 80c54e81f90a26782d1a349c3197e93934443421 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 00:44:29 +0700 Subject: [PATCH 21/46] Update CONTRIBUTING.md --- CONTRIBUTING.md | 105 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bbd60fd..103620a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,27 +1,27 @@ # Contributing to Tech Notes Hub -First off, thank you for taking the time to contribute! 🎉 -Your help is what makes this project valuable for the whole developer community. +First of all, thank you for taking the time to contribute! 🎉 +Your contributions help make this project more valuable for the developer community. ## 🚀 Ways to Contribute -There are many ways you can contribute: +There are many ways you can participate: - 📚 Add new technical notes or topics - 💡 Improve existing explanations or code snippets -- 🐛 Report issues or suggest improvements -- ✨ Refactor or clean up content formatting +- 🐛 Report bugs or suggest improvements +- ✨ Clean up and standardize content formatting - 🌍 Translate notes to other languages (coming soon) ## 📝 Contribution Guidelines -Please follow these guidelines to keep things consistent and easy to manage: +Please follow these guidelines to ensure consistency and maintainability: ### 1. Fork the Repository -Click the "Fork" button on the top right of this page. This will create a copy of the repository in your account. +Click the "Fork" button in the top-right corner of the GitHub page to create a copy of the repository in your account. -### 2. Clone Your Fork +### 2. Clone to Your Machine ```bash git clone https://github.com/your-username/tech-notes.git @@ -30,19 +30,59 @@ cd tech-notes-hub ### 3. Create a New Branch -Use a clear, descriptive branch name for your changes: +Name your branch clearly, briefly describing what you'll add or modify: ```bash git checkout -b feature/add-graph-algorithms ``` -### 4. Make Changes +### 🧩 Branch Naming Rules -* Follow the structure of existing folders and files -* Use markdown (`.md`) for text-based notes -* Add code snippets inside fenced code blocks (e.g., \`\`\`python) -* Keep explanations clear and concise -* Add inline comments if needed +Branch names should follow this structure: + +```bash +/ +``` + +| Type | Meaning | Example | +| ---------- | ----------------------------------------------------- | -------------------------------- | +| `feature` | Add new notes/sections | `feature/add-docker-notes` | +| `fix` | Fix content errors, typos, examples | `fix/typo-in-graph-note` | +| `update` | Update or improve existing notes | `update/aws-ec2-note` | +| `refactor` | Restructure files/content without changing core ideas | `refactor/reorganize-folders` | +| `remove` | Remove outdated or inappropriate content | `remove/duplicate-array-example` | +| `docs` | Update project documentation like README, CONTRIBUTING| `docs/improve-readme` | + +### 4. Make Your Changes + +* Follow the existing folder and file structure +* Notes should use Markdown format (`.md`) +* Code should be placed in fenced code blocks, e.g., \`\`\`python +* Keep explanations concise and clear +* Add inline comments if necessary + +### 💬 Commit Message Rules + +Write clear, meaningful, and understandable commit messages. Suggested structure: + +```bash +: +``` + +#### 📌 Examples: + +- `feature: add notes on HTTP Status Codes` +- `fix: correct typos in design-patterns.md` +- `update: improve binary search examples` +- `remove: delete duplicate notes in aws folder` +- `docs: add instructions for creating pull requests` + +#### 🧠 Additional Tips: + +- You can write in **English or Vietnamese** (be consistent within one pull request) +- **Avoid vague commits** like: `update 1`, `fix bug`, `test` +- If related to an issue, add the number at the end: + 👉 `fix: typo in aws-note #12` ### 5. Commit & Push @@ -52,34 +92,35 @@ git commit -m "Add notes on graph algorithms" git push origin feature/add-graph-algorithms ``` -### 6. Open a Pull Request +### 6. Create a Pull Request -Go to the original repository and click **"Compare & Pull Request"**. Provide: +Go back to the original repository and click **"Compare & Pull Request"**. Remember to include: -* A clear title and description of your contribution -* Reference to any related issues (if any) +* A clear, concise title +* A detailed description of what you've added/modified +* References to related issues if applicable -## ✅ PR Review Checklist +## ✅ Checklist Before Submitting a Pull Request -Before submitting your pull request, make sure: +Before submitting, ensure: -* [ ] Your content is well-formatted and follows the repo structure -* [ ] You've checked for typos or broken links -* [ ] Code snippets (if any) are working and correct -* [ ] No sensitive or proprietary information is included +* [ ] Content is properly formatted and follows project structure +* [ ] No spelling errors or broken links +* [ ] Code snippets (if any) work correctly +* [ ] No sensitive information or proprietary assets included -## 📁 Naming & File Guidelines +## 📁 File & Folder Naming Conventions -* Use lowercase and hyphens for file/folder names: `graph-traversal.md` -* For non-English versions, use a suffix: `graph-traversal_vi.md` -* Group related notes into folders (e.g., `algorithms/`, `aws/`, `design-patterns/`) +* Use lowercase and hyphens for file and folder names: `graph-traversal.md` +* For translations, add language suffix: `graph-traversal_vi.md` +* Notes should be grouped by topic folders (e.g., `algorithms/`, `aws/`, `design-patterns/`) ## 🤝 Code of Conduct -Be respectful, open-minded, and constructive in your interactions. We’re building a friendly and inclusive learning space for all developers. +Be respectful, open, and constructive in all interactions. We're building a friendly and inclusive learning space for all developers. ## 📩 Need Help? -Feel free to [open an issue](https://github.com/tech-notes-hub/tech-notes/issues) if you have questions, ideas, or feedback. +If you have questions or ideas, please [create a new issue](https://github.com/tech-notes-hub/tech-notes/issues). -Thanks for contributing to **Tech Notes Hub**! 🙌 +Thank you again for contributing to **Tech Notes Hub**! 🙌 From 3fda3734075140fa81dc17fa64aba51fead8dacd Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 00:48:29 +0700 Subject: [PATCH 22/46] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 103620a..623f533 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,7 +79,7 @@ Write clear, meaningful, and understandable commit messages. Suggested structure #### 🧠 Additional Tips: -- You can write in **English or Vietnamese** (be consistent within one pull request) +- You can only write in **English** - **Avoid vague commits** like: `update 1`, `fix bug`, `test` - If related to an issue, add the number at the end: 👉 `fix: typo in aws-note #12` From 1058e45e8a9253178b3effd3f0dc3ea9634cc79d Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 00:48:51 +0700 Subject: [PATCH 23/46] Update CONTRIBUTING_vi.md --- CONTRIBUTING_vi.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING_vi.md b/CONTRIBUTING_vi.md index 81ffa4f..4e97119 100644 --- a/CONTRIBUTING_vi.md +++ b/CONTRIBUTING_vi.md @@ -71,18 +71,18 @@ Viết commit rõ ràng, có ý nghĩa và dễ hiểu. Cấu trúc đề xuất #### 📌 Ví dụ: -- `feature: thêm ghi chú về HTTP Status Codes` -- `fix: sửa chính tả trong file design-patterns.md` -- `update: cập nhật ví dụ cho binary search` -- `remove: xoá ghi chú trùng lặp trong folder aws` -- `docs: thêm hướng dẫn cách tạo pull request` +- `feature: add notes on HTTP Status Codes` +- `fix: correct typos in design-patterns.md` +- `update: improve binary search examples` +- `remove: delete duplicate notes in aws folder` +- `docs: add instructions for creating pull requests` #### 🧠 Gợi ý thêm: -- Có thể viết bằng **tiếng Việt hoặc tiếng Anh** (nhất quán trong 1 pull request) -- **Tránh commit mơ hồ** như: `update 1`, `fix lỗi`, `test` +- Bạn chỉ có thể viết bằng **tiếng Anh** +- **Tránh commit mơ hồ** như: `update 1`, `fix bug`, `test` - Nếu liên quan issue, thêm số vào cuối: - 👉 `fix: lỗi chính tả trong aws-note #12` + 👉 `fix: typo in aws-note #12` ### 5. Commit & Push From f28812a2fce403cdfa92f59fb03df970ce7e54c1 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 00:49:28 +0700 Subject: [PATCH 24/46] Update CONTRIBUTING_vi.md --- CONTRIBUTING_vi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING_vi.md b/CONTRIBUTING_vi.md index 4e97119..017d85a 100644 --- a/CONTRIBUTING_vi.md +++ b/CONTRIBUTING_vi.md @@ -89,7 +89,7 @@ Viết commit rõ ràng, có ý nghĩa và dễ hiểu. Cấu trúc đề xuất ```bash git add . -git commit -m "Thêm ghi chú về thuật toán đồ thị" +git commit -m "Add notes on graph algorithms" git push origin feature/add-graph-algorithms ``` From 6eb4089360933112f21d859c11408c79ae8f96bb Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 18:58:44 +0700 Subject: [PATCH 25/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b81c3d1..c555839 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Tech Notes +# Tech Notes Hub – Technical notes & practical code snippets **All-in-one technical notes & code snippets** — A centralized knowledge base covering design patterns, algorithms, data structures, AWS, and more. Perfect for learning, quick reference, and daily developer use. From e01cf3505433a2afdb3e7affdc40f4c84e6c8fea Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 18:59:08 +0700 Subject: [PATCH 26/46] Update README_vi.md --- README_vi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_vi.md b/README_vi.md index a75e2f3..3950470 100644 --- a/README_vi.md +++ b/README_vi.md @@ -1,4 +1,4 @@ -# Tech Notes +# Tech Notes - Technical notes & practical code snippets **Ghi chú kỹ thuật & mã nguồn tổng hợp** — Một kho kiến thức tập trung, bao gồm Design Patterns, Thuật toán, Cấu trúc dữ liệu, AWS và nhiều chủ đề khác. Hoàn hảo cho việc học tập, tra cứu nhanh, và sử dụng hàng ngày của lập trình viên. From 55c9efceaa1cd787358e579f808719352388e0de Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Wed, 4 Jun 2025 19:20:03 +0700 Subject: [PATCH 27/46] Update CONTRIBUTING_vi.md --- CONTRIBUTING_vi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING_vi.md b/CONTRIBUTING_vi.md index 017d85a..cb03727 100644 --- a/CONTRIBUTING_vi.md +++ b/CONTRIBUTING_vi.md @@ -66,7 +66,7 @@ Tên nhánh nên theo cấu trúc: Viết commit rõ ràng, có ý nghĩa và dễ hiểu. Cấu trúc đề xuất: ```bash -: +: ``` #### 📌 Ví dụ: From 68ab53ce5d47cd14e2af1e969f177ff97fc3dc6a Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 5 Jun 2025 00:56:15 +0700 Subject: [PATCH 28/46] refactor: knowledge base for all --- .editorconfig | 64 ++ .github/ISSUE_TEMPLATE/bug_report.md | 30 + .github/ISSUE_TEMPLATE/bug_report_vi.md | 30 + .github/ISSUE_TEMPLATE/feature_request.md | 23 + .github/ISSUE_TEMPLATE/feature_request_vi.md | 23 + .github/workflows/ci.yml | 45 + .gitignore | 35 +- .markdownlint.yml | 86 ++ CONTRIBUTING.md | 11 +- CONTRIBUTING_vi.md | 11 +- PULL_REQUEST_RULES.md | 44 +- PULL_REQUEST_RULES_vi.md | 42 +- README.md | 6 + README_vi.md | 8 +- SUMMARY.md | 34 + assets/diagrams/init.txt | 0 assets/init.txt | 0 changelog.md | 31 + changelog_vi.md | 31 + docs/_index.md | 53 ++ docs/algorithms/graph-traversal.md | 148 ++++ docs/databases/relational.md | 117 +++ docs/design-patterns/factory.md | 476 ++++++++++ docs/design-patterns/observer.md | 404 +++++++++ docs/design-patterns/singleton.md | 226 +++++ docs/devops/ci-cd.md | 162 ++++ docs/linux/bash-scripting.md | 325 +++++++ docs/system-design/microservices.md | 467 ++++++++++ docs/testing/unit-testing.md | 467 ++++++++++ i18n/es/init.txt | 0 i18n/vi/algorithms/graph-traversal_vi.md | 148 ++++ i18n/vi/databases/relational_vi.md | 117 +++ i18n/vi/design-patterns/factory_vi.md | 476 ++++++++++ i18n/vi/design-patterns/observer_vi.md | 404 +++++++++ i18n/vi/design-patterns/singleton_vi.md | 226 +++++ i18n/vi/devops/ci-cd_vi.md | 162 ++++ i18n/vi/linux/bash-scripting_vi.md | 325 +++++++ i18n/vi/system-design/microservices_vi.md | 467 ++++++++++ i18n/vi/testing/unit-testing_vi.md | 467 ++++++++++ .../graph-traversal/GraphTraversal.cs | 312 +++++++ .../graph-traversal/GraphTraversal.java | 295 +++++++ .../graph-traversal/graphTraversal.js | 229 +++++ .../graph-traversal/graph_traversal.c | 542 ++++++++++++ .../graph-traversal/graph_traversal.cpp | 318 +++++++ .../graph-traversal/graph_traversal.go | 242 +++++ .../graph-traversal/graph_traversal.php | 268 ++++++ .../graph-traversal/graph_traversal.py | 198 +++++ .../graph-traversal/graph_traversal.rb | 208 +++++ .../graph-traversal/graph_traversal.rs | 242 +++++ snippets/algorithms/init.txt | 0 snippets/databases/.env.example | 32 + snippets/databases/README.md | 186 ++++ snippets/databases/README_vi.md | 186 ++++ snippets/databases/relational_db_examples.cs | 829 ++++++++++++++++++ snippets/databases/sql_examples.py | 637 ++++++++++++++ .../factory/FactoryPattern.cpp | 362 ++++++++ .../design-patterns/factory/FactoryPattern.cs | 407 +++++++++ .../factory/FactoryPattern.java | 288 ++++++ .../design-patterns/factory/factory_pattern.c | 547 ++++++++++++ .../factory/factory_pattern.go | 396 +++++++++ .../factory/factory_pattern.js | 328 +++++++ .../factory/factory_pattern.php | 308 +++++++ .../factory/factory_pattern.py | 328 +++++++ .../factory/factory_pattern.rb | 310 +++++++ .../factory/factory_pattern.rs | 447 ++++++++++ .../observer/ObserverPattern.cpp | 363 ++++++++ .../observer/ObserverPattern.cs | 448 ++++++++++ .../observer/ObserverPattern.java | 192 ++++ .../observer/observer_pattern.c | 528 +++++++++++ .../observer/observer_pattern.go | 295 +++++++ .../observer/observer_pattern.js | 280 ++++++ .../observer/observer_pattern.php | 474 ++++++++++ .../observer/observer_pattern.py | 222 +++++ .../observer/observer_pattern.rb | 244 ++++++ .../observer/observer_pattern.rs | 338 +++++++ .../singleton/SingletonPattern.cpp | 369 ++++++++ .../singleton/SingletonPattern.cs | 500 +++++++++++ .../singleton/SingletonPattern.java | 296 +++++++ .../singleton/singleton_pattern.c | 599 +++++++++++++ .../singleton/singleton_pattern.go | 376 ++++++++ .../singleton/singleton_pattern.js | 396 +++++++++ .../singleton/singleton_pattern.php | 388 ++++++++ .../singleton/singleton_pattern.py | 255 ++++++ .../singleton/singleton_pattern.rb | 364 ++++++++ .../singleton/singleton_pattern.rs | 466 ++++++++++ snippets/devops/ci_cd_pipeline.sh | 97 ++ snippets/linux/system_monitor.sh | 204 +++++ .../system-design/microservices_example.js | 595 +++++++++++++ snippets/testing/unit_testing_example.js | 459 ++++++++++ tools/check_links.sh | 108 +++ tools/generate_summary.py | 65 ++ 91 files changed, 23532 insertions(+), 25 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report_vi.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request_vi.md create mode 100644 .github/workflows/ci.yml create mode 100644 .markdownlint.yml create mode 100644 SUMMARY.md create mode 100644 assets/diagrams/init.txt create mode 100644 assets/init.txt create mode 100644 changelog.md create mode 100644 changelog_vi.md create mode 100644 docs/_index.md create mode 100644 docs/algorithms/graph-traversal.md create mode 100644 docs/databases/relational.md create mode 100644 docs/design-patterns/factory.md create mode 100644 docs/design-patterns/observer.md create mode 100644 docs/design-patterns/singleton.md create mode 100644 docs/devops/ci-cd.md create mode 100644 docs/linux/bash-scripting.md create mode 100644 docs/system-design/microservices.md create mode 100644 docs/testing/unit-testing.md create mode 100644 i18n/es/init.txt create mode 100644 i18n/vi/algorithms/graph-traversal_vi.md create mode 100644 i18n/vi/databases/relational_vi.md create mode 100644 i18n/vi/design-patterns/factory_vi.md create mode 100644 i18n/vi/design-patterns/observer_vi.md create mode 100644 i18n/vi/design-patterns/singleton_vi.md create mode 100644 i18n/vi/devops/ci-cd_vi.md create mode 100644 i18n/vi/linux/bash-scripting_vi.md create mode 100644 i18n/vi/system-design/microservices_vi.md create mode 100644 i18n/vi/testing/unit-testing_vi.md create mode 100644 snippets/algorithms/graph-traversal/GraphTraversal.cs create mode 100644 snippets/algorithms/graph-traversal/GraphTraversal.java create mode 100644 snippets/algorithms/graph-traversal/graphTraversal.js create mode 100644 snippets/algorithms/graph-traversal/graph_traversal.c create mode 100644 snippets/algorithms/graph-traversal/graph_traversal.cpp create mode 100644 snippets/algorithms/graph-traversal/graph_traversal.go create mode 100644 snippets/algorithms/graph-traversal/graph_traversal.php create mode 100644 snippets/algorithms/graph-traversal/graph_traversal.py create mode 100644 snippets/algorithms/graph-traversal/graph_traversal.rb create mode 100644 snippets/algorithms/graph-traversal/graph_traversal.rs create mode 100644 snippets/algorithms/init.txt create mode 100644 snippets/databases/.env.example create mode 100644 snippets/databases/README.md create mode 100644 snippets/databases/README_vi.md create mode 100644 snippets/databases/relational_db_examples.cs create mode 100644 snippets/databases/sql_examples.py create mode 100644 snippets/design-patterns/factory/FactoryPattern.cpp create mode 100644 snippets/design-patterns/factory/FactoryPattern.cs create mode 100644 snippets/design-patterns/factory/FactoryPattern.java create mode 100644 snippets/design-patterns/factory/factory_pattern.c create mode 100644 snippets/design-patterns/factory/factory_pattern.go create mode 100644 snippets/design-patterns/factory/factory_pattern.js create mode 100644 snippets/design-patterns/factory/factory_pattern.php create mode 100644 snippets/design-patterns/factory/factory_pattern.py create mode 100644 snippets/design-patterns/factory/factory_pattern.rb create mode 100644 snippets/design-patterns/factory/factory_pattern.rs create mode 100644 snippets/design-patterns/observer/ObserverPattern.cpp create mode 100644 snippets/design-patterns/observer/ObserverPattern.cs create mode 100644 snippets/design-patterns/observer/ObserverPattern.java create mode 100644 snippets/design-patterns/observer/observer_pattern.c create mode 100644 snippets/design-patterns/observer/observer_pattern.go create mode 100644 snippets/design-patterns/observer/observer_pattern.js create mode 100644 snippets/design-patterns/observer/observer_pattern.php create mode 100644 snippets/design-patterns/observer/observer_pattern.py create mode 100644 snippets/design-patterns/observer/observer_pattern.rb create mode 100644 snippets/design-patterns/observer/observer_pattern.rs create mode 100644 snippets/design-patterns/singleton/SingletonPattern.cpp create mode 100644 snippets/design-patterns/singleton/SingletonPattern.cs create mode 100644 snippets/design-patterns/singleton/SingletonPattern.java create mode 100644 snippets/design-patterns/singleton/singleton_pattern.c create mode 100644 snippets/design-patterns/singleton/singleton_pattern.go create mode 100644 snippets/design-patterns/singleton/singleton_pattern.js create mode 100644 snippets/design-patterns/singleton/singleton_pattern.php create mode 100644 snippets/design-patterns/singleton/singleton_pattern.py create mode 100644 snippets/design-patterns/singleton/singleton_pattern.rb create mode 100644 snippets/design-patterns/singleton/singleton_pattern.rs create mode 100644 snippets/devops/ci_cd_pipeline.sh create mode 100644 snippets/linux/system_monitor.sh create mode 100644 snippets/system-design/microservices_example.js create mode 100644 snippets/testing/unit_testing_example.js create mode 100755 tools/check_links.sh create mode 100755 tools/generate_summary.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..18a6d25 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,64 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Default settings for all files +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +# Markdown files +[*.{md,markdown}] +trim_trailing_whitespace = false # Trailing whitespace is significant in Markdown + +# Python files +[*.py] +indent_size = 4 +max_line_length = 88 # Black formatter default + +# C# files +[*.cs] +indent_size = 4 +csharp_new_line_before_open_brace = all +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_parentheses = false + +# JavaScript/TypeScript files +[*.{js,ts,jsx,tsx}] +quote_type = single + +# JSON files +[*.json] +insert_final_newline = false + +# YAML files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +indent_size = 2 + +# HTML, CSS, SCSS files +[*.{html,css,scss}] +indent_size = 2 + +# Java files +[*.java] +indent_size = 4 + +# Go files +[*.go] +indent_style = tab +indent_size = 4 + +# Makefiles +[Makefile] +indent_style = tab diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..3b6828a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '[BUG] ' +labels: 'bug' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. + +**Possible solution** +If you have suggestions on how to fix the issue, please describe them here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report_vi.md b/.github/ISSUE_TEMPLATE/bug_report_vi.md new file mode 100644 index 0000000..1b16943 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report_vi.md @@ -0,0 +1,30 @@ +--- +name: Báo cáo lỗi +about: Tạo một báo cáo lỗi để giúp chúng tôi cải thiện +title: '[BUG] ' +labels: 'bug' +assignees: '' + +--- + +**Mô tả lỗi** +Mô tả ngắn gọn và rõ ràng về lỗi đang gặp phải. + +**Cách để tái tạo lỗi** +Các bước để tái hiện hành vi lỗi: +1. Truy cập vào '...' +2. Nhấn vào '...' +3. Cuộn xuống tới '...' +4. Thấy lỗi + +**Hành vi mong đợi** +Mô tả rõ ràng và ngắn gọn về điều bạn mong đợi sẽ xảy ra. + +**Ảnh chụp màn hình** +Nếu có, hãy thêm ảnh chụp màn hình để minh họa lỗi. + +**Ngữ cảnh bổ sung** +Thêm bất kỳ thông tin bổ sung nào về sự cố ở đây (thiết bị, trình duyệt, môi trường...). + +**Giải pháp khả thi** +Nếu bạn có gợi ý về cách khắc phục lỗi, vui lòng mô tả tại đây. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..59b656f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '[FEATURE] ' +labels: 'enhancement' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +**Target topics/areas** +Which topics or sections of the repository would be affected by this feature? \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request_vi.md b/.github/ISSUE_TEMPLATE/feature_request_vi.md new file mode 100644 index 0000000..003b8c0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request_vi.md @@ -0,0 +1,23 @@ +--- +name: Yêu cầu tính năng +about: Đề xuất một ý tưởng hoặc tính năng cho dự án +title: '[FEATURE] ' +labels: 'enhancement' +assignees: '' + +--- + +**Yêu cầu tính năng này có liên quan đến vấn đề nào không? Hãy mô tả.** +Mô tả rõ ràng và ngắn gọn về vấn đề. Ví dụ: Tôi thường cảm thấy khó chịu khi [...] + +**Mô tả giải pháp bạn muốn có** +Mô tả rõ ràng và ngắn gọn về điều bạn muốn xảy ra. + +**Mô tả các giải pháp thay thế bạn đã xem xét** +Mô tả rõ ràng và ngắn gọn về bất kỳ giải pháp thay thế hoặc tính năng khác mà bạn đã cân nhắc. + +**Ngữ cảnh bổ sung** +Thêm bất kỳ thông tin, bối cảnh hoặc ảnh chụp màn hình nào liên quan đến yêu cầu tính năng ở đây. + +**Các chủ đề/khu vực bị ảnh hưởng** +Những chủ đề hoặc phần nào trong repository sẽ bị ảnh hưởng bởi tính năng này? diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..917f537 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: Markdown CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + markdown-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install markdownlint-cli + run: npm install -g markdownlint-cli + + - name: Run markdownlint + run: markdownlint '**/*.md' --ignore node_modules + + check-links: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Install dependencies + run: npm install -g markdown-link-check + + - name: Check links + run: ./tools/check_links.sh + + - name: Generate TOC + run: | + python3 ./tools/generate_summary.py + git diff --exit-code SUMMARY.md || (echo "SUMMARY.md is out of date. Please run ./tools/generate_summary.py and commit the changes" && exit 1) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 69d6106..df28da4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,34 @@ node_modules/ # Python __pycache__/ -*.pyc -*.pyo -*.pyd -.env -.venv/ +*.py[cod] +*$py.class +*.so +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# C# +bin/ +obj/ +*.user +*.suo +*.userprefs +*.sln.docstates +.vs/ # VSCode settings .vscode/ @@ -42,6 +65,8 @@ coverage.xml # dotenv environment files .env.local .env.* +.env +!.env.example # Ignore generated files and backups *.orig diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..ea0a6a3 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,86 @@ +# Markdown linting rules for Tech Notes Hub +# See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md for details + +# Default state for all rules +default: true + +# MD001 header-increment - Headers should increment by one level at a time +MD001: true + +# MD003 header-style - Header style +MD003: + style: "atx" # Use # style headers + +# MD004 ul-style - Unordered list style +MD004: + style: "consistent" + +# MD007 ul-indent - Unordered list indentation +MD007: + indent: 2 # Use 2 spaces for indentation + +# MD009 no-trailing-spaces - No trailing spaces +MD009: true + +# MD010 no-hard-tabs - No hard tabs +MD010: true + +# MD012 no-multiple-blanks - Multiple consecutive blank lines +MD012: + maximum: 1 # Allow at most 1 blank line + +# MD013 line-length - Line length +MD013: + line_length: 120 # Allow longer lines for code blocks + code_blocks: false + tables: false + +# MD022 blanks-around-headers - Headers should be surrounded by blank lines +MD022: true + +# MD024 no-duplicate-header - Multiple headers with the same content +MD024: + siblings_only: true # Allow same title in different nesting + +# MD025 single-title/single-h1 - Only one top-level header +MD025: true + +# MD026 no-trailing-punctuation - No trailing punctuation in header +MD026: + punctuation: ".,;:!。,;:!" + +# MD029 ol-prefix - Ordered list item prefix +MD029: + style: "one_or_ordered" # Use 1. or ordered numbers + +# MD031 blanks-around-fences - Fenced code blocks should be surrounded by blank lines +MD031: true + +# MD032 blanks-around-lists - Lists should be surrounded by blank lines +MD032: true + +# MD033 no-inline-html - No inline HTML +MD033: + allowed_elements: ["br", "img", "a", "details", "summary"] + +# MD034 no-bare-urls - No bare URLs +MD034: true + +# MD036 no-emphasis-as-header - No emphasis as header +MD036: true + +# MD037 no-space-in-emphasis - No spaces inside emphasis markers +MD037: true + +# MD038 no-space-in-code - No spaces inside code span markers +MD038: true + +# MD040 fenced-code-language - Fenced code blocks should have a language specified +MD040: true + +# MD041 first-line-heading - First line should be a top-level header +MD041: true + +# MD046 code-block-style - Code block style +MD046: + style: "fenced" # Use ```code``` style diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 623f533..13e1ccd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to Tech Notes Hub -First of all, thank you for taking the time to contribute! 🎉 +First of all, thank you for taking the time to contribute! 🎉 Your contributions help make this project more valuable for the developer community. ## 🚀 Ways to Contribute @@ -74,6 +74,7 @@ Write clear, meaningful, and understandable commit messages. Suggested structure - `feature: add notes on HTTP Status Codes` - `fix: correct typos in design-patterns.md` - `update: improve binary search examples` +- `refactor: reorganize folder structure` - `remove: delete duplicate notes in aws folder` - `docs: add instructions for creating pull requests` @@ -81,7 +82,7 @@ Write clear, meaningful, and understandable commit messages. Suggested structure - You can only write in **English** - **Avoid vague commits** like: `update 1`, `fix bug`, `test` -- If related to an issue, add the number at the end: +- If related to an issue, add the number at the end: 👉 `fix: typo in aws-note #12` ### 5. Commit & Push @@ -111,9 +112,9 @@ Before submitting, ensure: ## 📁 File & Folder Naming Conventions -* Use lowercase and hyphens for file and folder names: `graph-traversal.md` -* For translations, add language suffix: `graph-traversal_vi.md` -* Notes should be grouped by topic folders (e.g., `algorithms/`, `aws/`, `design-patterns/`) +* Use lowercase and hyphens for file and folder names: `graph_traversal.md` (except for code files like C# using PascalCase such as `GraphTraversal.cs`, or Java using CamelCase like `GraphTraversal.java`) +* For translations, add language suffix: `graph_traversal_vi.md` +* Notes should be grouped by docs folders (e.g., `docs/algorithms/`, `docs/aws/`, `docs/design-patterns/`) ## 🤝 Code of Conduct diff --git a/CONTRIBUTING_vi.md b/CONTRIBUTING_vi.md index cb03727..11991dd 100644 --- a/CONTRIBUTING_vi.md +++ b/CONTRIBUTING_vi.md @@ -1,6 +1,6 @@ # Đóng góp vào Tech Notes Hub -Trước hết, xin chân thành cảm ơn bạn đã dành thời gian đóng góp! 🎉 +Trước hết, xin chân thành cảm ơn bạn đã dành thời gian đóng góp! 🎉 Sự đóng góp của bạn giúp dự án này trở nên hữu ích hơn cho cộng đồng lập trình viên. ## 🚀 Cách bạn có thể đóng góp @@ -74,6 +74,7 @@ Viết commit rõ ràng, có ý nghĩa và dễ hiểu. Cấu trúc đề xuất - `feature: add notes on HTTP Status Codes` - `fix: correct typos in design-patterns.md` - `update: improve binary search examples` +- `refactor: reorganize folder structure` - `remove: delete duplicate notes in aws folder` - `docs: add instructions for creating pull requests` @@ -81,7 +82,7 @@ Viết commit rõ ràng, có ý nghĩa và dễ hiểu. Cấu trúc đề xuất - Bạn chỉ có thể viết bằng **tiếng Anh** - **Tránh commit mơ hồ** như: `update 1`, `fix bug`, `test` -- Nếu liên quan issue, thêm số vào cuối: +- Nếu liên quan issue, thêm số vào cuối: 👉 `fix: typo in aws-note #12` @@ -112,9 +113,9 @@ Trước khi gửi, hãy đảm bảo: ## 📁 Quy tắc đặt tên file & thư mục -* Tên file và thư mục dùng chữ thường và dấu gạch ngang: `graph-traversal.md` -* Nếu là bản dịch, thêm hậu tố ngôn ngữ: `graph-traversal_vi.md` -* Ghi chú nên được nhóm theo thư mục chuyên đề (ví dụ: `algorithms/`, `aws/`, `design-patterns/`) +* Tên file và thư mục dùng chữ thường và dấu gạch ngang: `graph_traversal.md` (trừ code như C# sử dụng PascalCase như `GraphTraversal.cs`, hoặc Java sử dụng CamelCase như `GraphTraversal.java`) +* Nếu là bản dịch, thêm hậu tố ngôn ngữ: `graph_traversal_vi.md` +* Ghi chú nên được nhóm theo thư mục trong docs (ví dụ: `docs/algorithms/`, `docs/aws/`, `docs/design-patterns/`) ## 🤝 Quy tắc ứng xử diff --git a/PULL_REQUEST_RULES.md b/PULL_REQUEST_RULES.md index 3a3793d..94bb9b8 100644 --- a/PULL_REQUEST_RULES.md +++ b/PULL_REQUEST_RULES.md @@ -6,6 +6,37 @@ Please read carefully before submitting a PR. --- +## 📝 Pull Request Template + +When submitting a pull request, please include: + +### Description +A clear description of the changes you have made. + +### Related Issue +Link to any related issues: `Fixes #(issue number)` + +### Type of Change +Select the appropriate options: +- [ ] New content (notes, snippets) +- [ ] Content improvement (updates, fixes, expansion) +- [ ] Documentation update +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] Infrastructure/tooling (CI, scripts, etc.) +- [ ] Translation (i18n) - specify language: ______ +- [ ] Other (please describe) + +### Checklist +- [ ] My content follows the style guidelines of this project +- [ ] I have performed a self-review of my own content/code +- [ ] I have included references/links where appropriate +- [ ] My changes generate no new warnings or errors +- [ ] I have checked formatting with markdownlint or similar tools +- [ ] I have checked that all links are valid +- [ ] For translations: I've followed the i18n folder structure (i18n/[language_code]/...) + +--- + ## ✅ Allowed in Pull Requests - **New content** such as: @@ -22,8 +53,9 @@ Please read carefully before submitting a PR. - Typo and grammar fixes - Markdown formatting or link corrections - **New language versions**: - - Translations with `_vi.md`, `_fr.md`, etc. suffix + - Translations in the `i18n/[language_code]/` folder structure (e.g., `i18n/vi/`, `i18n/fr/`) - Must match the structure and logic of the original file + - Should be referenced in the SUMMARY.md under appropriate language section ## ❌ Not Allowed in Pull Requests @@ -34,19 +66,21 @@ Please read carefully before submitting a PR. - ❌ Personal promotion, affiliate links, or ads - ❌ Files with broken structure, invalid markdown, or irrelevant naming - ❌ Notes that contain **plagiarized content** (copying from copyrighted materials) +- ❌ Translations not following the proper i18n folder structure ## 🔖 Style & Structure Reminders - Use clear, **conversational but concise** explanations - Use correct heading hierarchy (`#`, `##`, `###`, etc.) - Name files using lowercase and hyphens: `binary-search.md` -- For translated files, append language suffix: `binary-search_vi.md` - Place content in the appropriate folder (`algorithms/`, `design-patterns/`, etc.) +- For translations, use the `i18n/[language_code]/` structure, mirroring the main docs structure +- Update SUMMARY.md to include new content or translations ## 📢 Final Note -We appreciate your contribution and effort! -All pull requests will be reviewed by maintainers before merging. +We appreciate your contribution and effort! +All pull requests will be reviewed by maintainers before merging. Feel free to open a discussion issue if you're unsure whether something fits. -Let’s build a high-quality, developer-friendly knowledge base together. 🚀 +Let's build a high-quality, developer-friendly knowledge base together. 🚀 diff --git a/PULL_REQUEST_RULES_vi.md b/PULL_REQUEST_RULES_vi.md index 58b9022..910239c 100644 --- a/PULL_REQUEST_RULES_vi.md +++ b/PULL_REQUEST_RULES_vi.md @@ -6,6 +6,37 @@ Vui lòng đọc kỹ trước khi gửi PR. --- +## 📝 Mẫu Pull Request + +Khi gửi pull request, vui lòng bao gồm: + +### Mô tả +Mô tả rõ ràng về các thay đổi bạn đã thực hiện. + +### Vấn đề liên quan +Liên kết đến các vấn đề liên quan: `Fixes #(số issue)` + +### Loại thay đổi +Chọn các tùy chọn thích hợp: +- [ ] Nội dung mới (ghi chú, đoạn mã) +- [ ] Cải thiện nội dung (cập nhật, sửa lỗi, mở rộng) +- [ ] Cập nhật tài liệu +- [ ] Sửa lỗi (thay đổi không gây ảnh hưởng) +- [ ] Cơ sở hạ tầng/công cụ (CI, scripts, v.v.) +- [ ] Bản dịch (i18n) - chỉ rõ ngôn ngữ: ______ +- [ ] Khác (vui lòng mô tả) + +### Danh sách kiểm tra +- [ ] Nội dung của tôi tuân theo hướng dẫn về phong cách của dự án +- [ ] Tôi đã tự đánh giá nội dung/mã của mình +- [ ] Tôi đã bao gồm tài liệu tham khảo/liên kết khi thích hợp +- [ ] Các thay đổi của tôi không tạo ra cảnh báo hoặc lỗi mới +- [ ] Tôi đã kiểm tra định dạng bằng markdownlint hoặc công cụ tương tự +- [ ] Tôi đã kiểm tra rằng tất cả các liên kết đều hợp lệ +- [ ] Đối với bản dịch: Tôi đã tuân theo cấu trúc thư mục i18n (i18n/[language_code]/...) + +--- + ## ✅ Được phép trong Pull Request - **Thêm nội dung mới** như: @@ -22,8 +53,9 @@ Vui lòng đọc kỹ trước khi gửi PR. - Sửa lỗi chính tả, ngữ pháp - Cập nhật liên kết hoặc định dạng Markdown - **Bản dịch ghi chú**: - - Dùng hậu tố `_vi.md`, `_ja.md`, v.v. + - Sử dụng cấu trúc thư mục `i18n/[mã_ngôn_ngữ]/` (ví dụ: `i18n/vi/`, `i18n/fr/`) - Nội dung bám sát logic file gốc + - Cập nhật trong SUMMARY.md dưới phần ngôn ngữ tương ứng ## ❌ Không được phép trong Pull Request @@ -34,19 +66,21 @@ Vui lòng đọc kỹ trước khi gửi PR. - ❌ Chèn liên kết quảng cáo, giới thiệu cá nhân, affiliate link - ❌ File đặt tên sai quy tắc, không dùng định dạng Markdown hợp lệ - ❌ Nội dung **sao chép có bản quyền** từ nguồn khác +- ❌ Bản dịch không tuân thủ cấu trúc thư mục i18n đúng quy định ## 🔖 Lưu ý về cấu trúc & định dạng - Trình bày **ngắn gọn, dễ hiểu, gần gũi** - Sử dụng đúng cấp độ tiêu đề: `#`, `##`, `###`,... - Đặt tên file bằng chữ thường, dùng dấu gạch ngang: `binary-search.md` -- File dịch có hậu tố ngôn ngữ: `binary-search_vi.md` - Đặt đúng thư mục chuyên đề (`algorithms/`, `design-patterns/`, `aws/`,...) +- Đối với bản dịch, sử dụng cấu trúc `i18n/[mã_ngôn_ngữ]/`, phản ánh cấu trúc thư mục chính +- Cập nhật SUMMARY.md để bao gồm nội dung mới hoặc bản dịch ## 📢 Lưu ý cuối -Cảm ơn bạn đã đóng góp cho dự án! -Tất cả pull request sẽ được review kỹ trước khi merge. +Cảm ơn bạn đã đóng góp cho dự án! +Tất cả pull request sẽ được review kỹ trước khi merge. Nếu chưa chắc nội dung có phù hợp không, bạn có thể mở một issue để trao đổi trước. Cùng nhau xây dựng một kho kiến thức chất lượng cho lập trình viên! 🚀 diff --git a/README.md b/README.md index c555839..3a87c67 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ It's designed to be your go-to resource whether you're preparing for interviews, Simply browse the folders or use GitHub's search feature to find the topic or pattern you need. Each note is designed to be self-contained with theory and practical code. +**For a complete table of contents with all available notes and resources, check out the [SUMMARY.md](SUMMARY.md) file.** + ## 🤝 Contribution Contributions are highly welcome! If you want to: @@ -67,6 +69,10 @@ Before submitting a pull request, make sure to check the [Pull Request Rules](PU This project is licensed under the MIT License. See the [LICENSE](LICENSE.txt) file for details. +## 📝 Changelog + +For a detailed list of all notable changes to this project, please see the [changelog](changelog.md) file. + ## 🙌 Acknowledgements Thanks to all contributors and the open source community for making this knowledge base better every day. diff --git a/README_vi.md b/README_vi.md index 3950470..1ff2b93 100644 --- a/README_vi.md +++ b/README_vi.md @@ -49,9 +49,11 @@ Dự án được thiết kế để trở thành tài nguyên tham khảo hàng ## 📖 Cách sử dụng -Bạn có thể duyệt các thư mục hoặc dùng tính năng tìm kiếm của GitHub để tra cứu chủ đề hoặc mẫu thiết kế bạn cần. +Bạn có thể duyệt các thư mục hoặc dùng tính năng tìm kiếm của GitHub để tra cứu chủ đề hoặc mẫu thiết kế bạn cần. Mỗi ghi chú đều độc lập, bao gồm lý thuyết và mã ví dụ thực tế. +**Để xem danh mục đầy đủ với tất cả các ghi chú và tài nguyên có sẵn, hãy xem file [SUMMARY.md](SUMMARY.md).** + ## 🤝 Đóng góp Mọi đóng góp đều rất hoan nghênh! Nếu bạn muốn: @@ -68,6 +70,10 @@ Trước khi gửi pull request, vui lòng đọc kỹ [Quy định nội dung P Dự án này được phát hành theo giấy phép MIT. Xem chi tiết trong file [LICENSE](LICENSE.txt). +## 📝 Changelog + +Để biết danh sách chi tiết về tất cả những thay đổi đáng chú ý trong dự án này, vui lòng xem tệp [changelog](changelog_vi.md). + ## 🙌 Lời cảm ơn Cảm ơn tất cả các contributors và cộng đồng mã nguồn mở đã cùng nhau làm phong phú thêm kho kiến thức này mỗi ngày. diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..3497c69 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,34 @@ +# Tech Notes Hub + +## Table of Contents + +### Algorithms + +- [Graph Traversal Algorithms](docs/algorithms/graph-traversal.md) + +### Databases + +- [Relational Databases](docs/databases/relational.md) + +### Design Patterns + +- [Factory Design Pattern](docs/design-patterns/factory.md) +- [Observer Design Pattern](docs/design-patterns/observer.md) +- [Singleton Design Pattern](docs/design-patterns/singleton.md) + +### Devops + +- [Continuous Integration and Continuous Deployment (CI/CD)](docs/devops/ci-cd.md) + +### Linux + +- [Bash Scripting](docs/linux/bash-scripting.md) + +### System Design + +- [Microservices Architecture](docs/system-design/microservices.md) + +### Testing + +- [Unit Testing](docs/testing/unit-testing.md) + diff --git a/assets/diagrams/init.txt b/assets/diagrams/init.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/init.txt b/assets/init.txt new file mode 100644 index 0000000..e69de29 diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..e12ea66 --- /dev/null +++ b/changelog.md @@ -0,0 +1,31 @@ +# Big changelog + +All notable changes to the Tech Notes Hub project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- Security enhancement: Moved database credentials to environment variables +- Added database code examples for Python and C# +- Added design pattern implementations (Observer, Factory, Singleton) +- Created comprehensive documentation in English and Vietnamese + +### Changed +- Restructured repository to match documentation and code examples +- Updated SQL examples to use parameterized queries for better security +- Improved code organization in snippets directory + +### Security +- Removed hardcoded database credentials +- Added .env.example file with placeholder values +- Updated .gitignore to prevent committing sensitive information + +## [1.0.0] - 2025-06-04 + +### Added +- Initial repository structure +- Basic documentation framework +- Core code examples for algorithms and data structures diff --git a/changelog_vi.md b/changelog_vi.md new file mode 100644 index 0000000..a4bc9b1 --- /dev/null +++ b/changelog_vi.md @@ -0,0 +1,31 @@ +# Nhật ký thay đổi lớn + +Tất cả những thay đổi đáng chú ý đối với dự án Tech Notes Hub sẽ được ghi lại trong tệp này. + +Định dạng dựa trên [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +và dự án này tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Chưa phát hành] + +### Đã thêm +- Tăng cường bảo mật: Chuyển thông tin đăng nhập cơ sở dữ liệu vào biến môi trường +- Thêm các ví dụ mã nguồn cơ sở dữ liệu cho Python và C# +- Thêm các triển khai mẫu thiết kế (Observer, Factory, Singleton) +- Tạo tài liệu toàn diện bằng tiếng Anh và tiếng Việt + +### Đã thay đổi +- Cơ cấu lại kho lưu trữ để phù hợp với tài liệu và các ví dụ mã nguồn +- Cập nhật các ví dụ SQL để sử dụng truy vấn có tham số để bảo mật tốt hơn +- Cải thiện tổ chức mã trong thư mục snippets + +### Bảo mật +- Loại bỏ thông tin đăng nhập cơ sở dữ liệu được mã hóa cứng +- Thêm tệp .env.example với các giá trị giữ chỗ +- Cập nhật .gitignore để ngăn chặn việc commit thông tin nhạy cảm + +## [1.0.0] - 2025-06-04 + +### Đã thêm +- Cấu trúc kho lưu trữ ban đầu +- Khung tài liệu cơ bản +- Các ví dụ mã nguồn cốt lõi cho thuật toán và cấu trúc dữ liệu diff --git a/docs/_index.md b/docs/_index.md new file mode 100644 index 0000000..dac2e6e --- /dev/null +++ b/docs/_index.md @@ -0,0 +1,53 @@ +# Tech Notes Hub + +Welcome to the Tech Notes Hub! This repository is a comprehensive collection of technical notes, code snippets, and examples covering various topics in software development, system design, and computer science. + +## Purpose + +Tech Notes Hub aims to: + +- Provide clear, concise explanations of important technical concepts +- Offer practical code examples in multiple programming languages +- Serve as a reference for developers at all skill levels +- Create a collaborative knowledge base for the tech community + +## Repository Structure + +The repository is organized into the following main sections: + +### Algorithms +Implementations and explanations of common algorithms, including search, sort, graph traversal, and more. + +### Databases +Notes on database systems, query optimization, data modeling, and best practices for both SQL and NoSQL databases. + +### Design Patterns +Detailed explanations and implementations of software design patterns across multiple programming languages. + +### DevOps +Information about continuous integration, deployment, containerization, and infrastructure management. + +### Linux +Guides for Linux system administration, shell scripting, and command-line tools. + +### System Design +Approaches to designing scalable, reliable, and maintainable software systems. + +### Testing +Best practices for unit testing, integration testing, and test-driven development. + +## How to Use This Repository + +Each section contains markdown files with explanations and code snippets. You can: + +1. Browse the sections to find topics of interest +2. Use the code examples as reference for your own projects +3. Contribute by adding new content or improving existing documentation + +## Contributing + +We welcome contributions from the community! Please see our [CONTRIBUTING.md](../CONTRIBUTING.md) file for guidelines on how to contribute. + +## License + +This project is licensed under the MIT License - see the [LICENSE.txt](../LICENSE.txt) file for details. diff --git a/docs/algorithms/graph-traversal.md b/docs/algorithms/graph-traversal.md new file mode 100644 index 0000000..b16b9a2 --- /dev/null +++ b/docs/algorithms/graph-traversal.md @@ -0,0 +1,148 @@ +# Graph Traversal Algorithms + +Graph traversal algorithms are fundamental techniques used to visit every vertex in a graph. They serve as building blocks for many more complex graph algorithms. + +## Table of Contents + +- [Breadth-First Search (BFS)](#breadth-first-search-bfs) +- [Depth-First Search (DFS)](#depth-first-search-dfs) +- [Comparison of BFS and DFS](#comparison-of-bfs-and-dfs) +- [Applications](#applications) +- [Time and Space Complexity](#time-and-space-complexity) + +## Breadth-First Search (BFS) + +Breadth-First Search is a graph traversal algorithm that explores all vertices at the current depth level before moving to vertices at the next depth level. + +### How BFS Works + +1. Start at a source vertex and mark it as visited +2. Visit all its unvisited neighbors and mark them as visited +3. For each of those neighbors, visit all of their unvisited neighbors +4. Repeat until all vertices have been visited + +### Implementation + +```python +from collections import deque + +def bfs(graph, start): + visited = set([start]) + queue = deque([start]) + result = [] + + while queue: + vertex = queue.popleft() + result.append(vertex) + + for neighbor in graph[vertex]: + if neighbor not in visited: + visited.add(neighbor) + queue.append(neighbor) + + return result + +# Example usage +graph = { + 'A': ['B', 'C'], + 'B': ['A', 'D', 'E'], + 'C': ['A', 'F'], + 'D': ['B'], + 'E': ['B', 'F'], + 'F': ['C', 'E'] +} + +print(bfs(graph, 'A')) # Output: ['A', 'B', 'C', 'D', 'E', 'F'] +``` + +## Depth-First Search (DFS) + +Depth-First Search is a graph traversal algorithm that explores as far as possible along each branch before backtracking. + +### How DFS Works + +1. Start at a source vertex and mark it as visited +2. Recursively visit one of its unvisited neighbors +3. Continue this process, going deeper into the graph +4. When you reach a vertex with no unvisited neighbors, backtrack + +### Implementation + +```python +def dfs_recursive(graph, vertex, visited=None, result=None): + if visited is None: + visited = set() + if result is None: + result = [] + + visited.add(vertex) + result.append(vertex) + + for neighbor in graph[vertex]: + if neighbor not in visited: + dfs_recursive(graph, neighbor, visited, result) + + return result + +# Iterative implementation using a stack +def dfs_iterative(graph, start): + visited = set() + stack = [start] + result = [] + + while stack: + vertex = stack.pop() + if vertex not in visited: + visited.add(vertex) + result.append(vertex) + + # Add neighbors in reverse order to simulate recursive DFS + for neighbor in reversed(graph[vertex]): + if neighbor not in visited: + stack.append(neighbor) + + return result + +# Example usage (same graph as BFS example) +print(dfs_recursive(graph, 'A')) # Output might be: ['A', 'B', 'D', 'E', 'F', 'C'] +print(dfs_iterative(graph, 'A')) # Similar output, might vary depending on neighbor order +``` + +## Comparison of BFS and DFS + +| Aspect | BFS | DFS | +|--------|-----|-----| +| Data Structure | Queue | Stack (or recursion) | +| Space Complexity | O(b^d) where b is branching factor and d is distance from source | O(h) where h is the height of the tree | +| Completeness | Complete (finds all nodes at a given depth before moving deeper) | Not complete for infinite graphs | +| Optimality | Optimal for unweighted graphs | Not optimal in general | +| Use Case | Shortest path in unweighted graphs, level order traversal | Topological sorting, cycle detection, path finding | + +## Applications + +- **BFS Applications**: + - Finding the shortest path in unweighted graphs + - Finding all nodes within one connected component + - Testing bipartiteness of a graph + - Web crawlers + - Social networking features (e.g., "Friends within 2 connections") + +- **DFS Applications**: + - Topological sorting + - Finding strongly connected components + - Solving puzzles with only one solution (e.g., mazes) + - Cycle detection + - Path finding in games and puzzles + +## Time and Space Complexity + +Both BFS and DFS have a time complexity of O(V + E) where V is the number of vertices and E is the number of edges. This is because in the worst case, each vertex and each edge will be explored once. + +Space complexity: +- BFS: O(V) in the worst case when all vertices are stored in the queue +- DFS: O(h) where h is the maximum depth of the recursion stack (which could be O(V) in the worst case) + +## References + +1. Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction to Algorithms (3rd ed.). MIT Press. +2. Sedgewick, R., & Wayne, K. (2011). Algorithms (4th ed.). Addison-Wesley Professional. \ No newline at end of file diff --git a/docs/databases/relational.md b/docs/databases/relational.md new file mode 100644 index 0000000..82132a6 --- /dev/null +++ b/docs/databases/relational.md @@ -0,0 +1,117 @@ +# Relational Databases + +Relational databases are organized collections of data that store information in tables with rows and columns. They follow the relational model proposed by Edgar F. Codd in 1970, which emphasizes relationships between data entities. + +## Core Concepts + +### Tables (Relations) + +The fundamental structure in relational databases: +- Each **table** represents an entity type (e.g., customers, products) +- Each **row** (tuple) represents an instance of that entity +- Each **column** (attribute) represents a property of that entity + +### Keys + +Keys establish relationships and ensure data integrity: + +- **Primary Key**: Uniquely identifies each record in a table +- **Foreign Key**: References a primary key in another table, establishing relationships +- **Composite Key**: Combines multiple columns to form a unique identifier +- **Candidate Key**: A column or set of columns that could serve as a primary key + +### Schema + +A schema defines the structure of the database: +- Table definitions +- Column data types and constraints +- Relationships between tables + +## SQL (Structured Query Language) + +SQL is the standard language for interacting with relational databases. + +### Basic SQL Commands + +```sql +-- Create a table +CREATE TABLE customers ( + customer_id INT PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + signup_date DATE +); + +-- Insert data +INSERT INTO customers (customer_id, name, email, signup_date) +VALUES (1, 'John Smith', 'john@example.com', '2023-01-15'); + +-- Query data +SELECT * FROM customers WHERE signup_date > '2023-01-01'; + +-- Update data +UPDATE customers SET email = 'john.smith@example.com' WHERE customer_id = 1; + +-- Delete data +DELETE FROM customers WHERE customer_id = 1; +``` + +## Normalization + +Normalization is the process of organizing data to reduce redundancy and improve data integrity: + +- **First Normal Form (1NF)**: Eliminate duplicate columns and create separate tables for related data +- **Second Normal Form (2NF)**: Meet 1NF requirements and remove partial dependencies +- **Third Normal Form (3NF)**: Meet 2NF requirements and remove transitive dependencies + +## ACID Properties + +Transactions in relational databases follow ACID properties: + +- **Atomicity**: Transactions are all-or-nothing operations +- **Consistency**: Transactions bring the database from one valid state to another +- **Isolation**: Concurrent transactions don't interfere with each other +- **Durability**: Completed transactions persist even in case of system failure + +## Popular Relational Database Systems + +- **MySQL**: Open-source, widely used for web applications +- **PostgreSQL**: Advanced open-source database with extensive features +- **Oracle Database**: Enterprise-level commercial database +- **Microsoft SQL Server**: Microsoft's commercial database solution +- **SQLite**: Lightweight, serverless database engine + +## Indexes + +Indexes speed up data retrieval operations: +- Similar to a book index +- Improves query performance but adds overhead for write operations +- Types include B-tree, hash, and bitmap indexes + +## Joins + +Joins combine records from two or more tables: +- **INNER JOIN**: Returns records with matching values in both tables +- **LEFT JOIN**: Returns all records from the left table and matching records from the right +- **RIGHT JOIN**: Returns all records from the right table and matching records from the left +- **FULL JOIN**: Returns all records when there's a match in either table + +```sql +SELECT customers.name, orders.order_date +FROM customers +INNER JOIN orders ON customers.customer_id = orders.customer_id; +``` + +## When to Use Relational Databases + +Relational databases are ideal for: +- Structured data with clear relationships +- Applications requiring complex queries and transactions +- Systems where data integrity is critical +- Scenarios where consistency is more important than speed + +## References + +- Codd, E.F. (1970). "A Relational Model of Data for Large Shared Data Banks" +- Date, C.J. "An Introduction to Database Systems" +- Garcia-Molina, H., Ullman, J.D., & Widom, J. "Database Systems: The Complete Book" \ No newline at end of file diff --git a/docs/design-patterns/factory.md b/docs/design-patterns/factory.md new file mode 100644 index 0000000..2ab9d1b --- /dev/null +++ b/docs/design-patterns/factory.md @@ -0,0 +1,476 @@ +# Factory Design Pattern + +The Factory pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. + +## Intent + +- Create objects without exposing the instantiation logic to the client +- Refer to newly created objects using a common interface +- Decouple the implementation of an object from its use + +## Problem + +When should you use the Factory pattern? + +- When a class cannot anticipate the type of objects it needs to create +- When a class wants its subclasses to specify the objects it creates +- When you want to localize the knowledge of which class gets created + +## Types of Factory Patterns + +There are several variations of the Factory pattern: + +1. **Simple Factory** - Not a formal pattern, but a simple way to separate object creation +2. **Factory Method** - Defines an interface for creating objects, but lets subclasses decide which classes to instantiate +3. **Abstract Factory** - Provides an interface for creating families of related or dependent objects + +## Structure + +### Factory Method Pattern + +![Factory Method Pattern Structure](https://refactoring.guru/images/patterns/diagrams/factory-method/structure.png) + +### Abstract Factory Pattern + +![Abstract Factory Pattern Structure](https://refactoring.guru/images/patterns/diagrams/abstract-factory/structure.png) + +## Implementation + +### Simple Factory Example + +```java +// Product interface +interface Product { + void operation(); +} + +// Concrete products +class ConcreteProductA implements Product { + @Override + public void operation() { + System.out.println("ConcreteProductA operation"); + } +} + +class ConcreteProductB implements Product { + @Override + public void operation() { + System.out.println("ConcreteProductB operation"); + } +} + +// Simple factory +class SimpleFactory { + public Product createProduct(String type) { + if (type.equals("A")) { + return new ConcreteProductA(); + } else if (type.equals("B")) { + return new ConcreteProductB(); + } + throw new IllegalArgumentException("Invalid product type: " + type); + } +} + +// Client code +class Client { + public static void main(String[] args) { + SimpleFactory factory = new SimpleFactory(); + + Product productA = factory.createProduct("A"); + productA.operation(); + + Product productB = factory.createProduct("B"); + productB.operation(); + } +} +``` + +### Factory Method Example + +```java +// Product interface +interface Product { + void operation(); +} + +// Concrete products +class ConcreteProductA implements Product { + @Override + public void operation() { + System.out.println("ConcreteProductA operation"); + } +} + +class ConcreteProductB implements Product { + @Override + public void operation() { + System.out.println("ConcreteProductB operation"); + } +} + +// Creator abstract class with factory method +abstract class Creator { + public abstract Product createProduct(); + + // The creator can also include some business logic + public void someOperation() { + // Call the factory method to create a Product object + Product product = createProduct(); + // Use the product + product.operation(); + } +} + +// Concrete creators override factory method +class ConcreteCreatorA extends Creator { + @Override + public Product createProduct() { + return new ConcreteProductA(); + } +} + +class ConcreteCreatorB extends Creator { + @Override + public Product createProduct() { + return new ConcreteProductB(); + } +} + +// Client code +class Client { + public static void main(String[] args) { + Creator creatorA = new ConcreteCreatorA(); + creatorA.someOperation(); + + Creator creatorB = new ConcreteCreatorB(); + creatorB.someOperation(); + } +} +``` + +### Abstract Factory Example + +```java +// Abstract products +interface ProductA { + void operationA(); +} + +interface ProductB { + void operationB(); +} + +// Concrete products for family 1 +class ConcreteProductA1 implements ProductA { + @Override + public void operationA() { + System.out.println("Product A1 operation"); + } +} + +class ConcreteProductB1 implements ProductB { + @Override + public void operationB() { + System.out.println("Product B1 operation"); + } +} + +// Concrete products for family 2 +class ConcreteProductA2 implements ProductA { + @Override + public void operationA() { + System.out.println("Product A2 operation"); + } +} + +class ConcreteProductB2 implements ProductB { + @Override + public void operationB() { + System.out.println("Product B2 operation"); + } +} + +// Abstract factory interface +interface AbstractFactory { + ProductA createProductA(); + ProductB createProductB(); +} + +// Concrete factories +class ConcreteFactory1 implements AbstractFactory { + @Override + public ProductA createProductA() { + return new ConcreteProductA1(); + } + + @Override + public ProductB createProductB() { + return new ConcreteProductB1(); + } +} + +class ConcreteFactory2 implements AbstractFactory { + @Override + public ProductA createProductA() { + return new ConcreteProductA2(); + } + + @Override + public ProductB createProductB() { + return new ConcreteProductB2(); + } +} + +// Client code +class Client { + private ProductA productA; + private ProductB productB; + + public Client(AbstractFactory factory) { + productA = factory.createProductA(); + productB = factory.createProductB(); + } + + public void executeOperations() { + productA.operationA(); + productB.operationB(); + } +} +``` + +## Examples in Different Languages + +### JavaScript + +```javascript +// Factory Method in JavaScript + +// Product interface is implicit in JavaScript +class Dog { + speak() { + return "Woof!"; + } +} + +class Cat { + speak() { + return "Meow!"; + } +} + +// Creator +class AnimalFactory { + // Factory method + createAnimal(type) { + switch(type) { + case 'dog': + return new Dog(); + case 'cat': + return new Cat(); + default: + throw new Error(`Animal type ${type} is not supported.`); + } + } +} + +// Usage +const factory = new AnimalFactory(); +const dog = factory.createAnimal('dog'); +const cat = factory.createAnimal('cat'); + +console.log(dog.speak()); // Outputs: Woof! +console.log(cat.speak()); // Outputs: Meow! +``` + +### Python + +```python +from abc import ABC, abstractmethod + +# Abstract Product +class Button(ABC): + @abstractmethod + def render(self): + pass + + @abstractmethod + def on_click(self): + pass + +# Concrete Products +class HTMLButton(Button): + def render(self): + return "" + + def on_click(self): + return "HTML Button clicked!" + +class WindowsButton(Button): + def render(self): + return "Windows Button" + + def on_click(self): + return "Windows Button clicked!" + +# Abstract Creator +class Dialog(ABC): + @abstractmethod + def create_button(self) -> Button: + pass + + def render(self): + # Call the factory method to create a button object + button = self.create_button() + # Now use the product + return f"Dialog rendering with {button.render()}" + +# Concrete Creators +class HTMLDialog(Dialog): + def create_button(self) -> Button: + return HTMLButton() + +class WindowsDialog(Dialog): + def create_button(self) -> Button: + return WindowsButton() + +# Client code +def client_code(dialog: Dialog): + print(dialog.render()) + +# Based on environment, we select the appropriate dialog +import sys +if sys.platform.startswith('win'): + dialog = WindowsDialog() +else: + dialog = HTMLDialog() + +client_code(dialog) +``` + +### C# + +```csharp +using System; + +// Abstract Product +public interface IVehicle +{ + void Drive(); +} + +// Concrete Products +public class Car : IVehicle +{ + public void Drive() + { + Console.WriteLine("Driving a car..."); + } +} + +public class Motorcycle : IVehicle +{ + public void Drive() + { + Console.WriteLine("Driving a motorcycle..."); + } +} + +// Abstract Creator +public abstract class VehicleFactory +{ + // Factory Method + public abstract IVehicle CreateVehicle(); + + public void DeliverVehicle() + { + IVehicle vehicle = CreateVehicle(); + Console.WriteLine("Delivering the vehicle..."); + vehicle.Drive(); + } +} + +// Concrete Creators +public class CarFactory : VehicleFactory +{ + public override IVehicle CreateVehicle() + { + return new Car(); + } +} + +public class MotorcycleFactory : VehicleFactory +{ + public override IVehicle CreateVehicle() + { + return new Motorcycle(); + } +} + +// Client code +public class Program +{ + public static void Main() + { + VehicleFactory factory = GetFactory("car"); + factory.DeliverVehicle(); + + factory = GetFactory("motorcycle"); + factory.DeliverVehicle(); + } + + private static VehicleFactory GetFactory(string vehicleType) + { + switch (vehicleType.ToLower()) + { + case "car": + return new CarFactory(); + case "motorcycle": + return new MotorcycleFactory(); + default: + throw new ArgumentException($"Vehicle type {vehicleType} is not supported."); + } + } +} +``` + +## Use Cases + +- **UI Component Creation**: Creating different UI components based on user preferences or platform +- **Database Connections**: Creating the right database connection based on configuration +- **Document Generation**: Creating different document types (PDF, Word, etc.) +- **Vehicle Manufacturing**: Creating different types of vehicles in a simulation +- **Payment Processing**: Creating different payment methods in an e-commerce application + +## Pros and Cons + +### Pros + +- Avoids tight coupling between creator and concrete products +- Single Responsibility Principle: Move product creation code to one place +- Open/Closed Principle: New products can be added without breaking existing code +- Creates objects on demand, rather than at initialization time + +### Cons + +- Code may become more complicated due to introduction of many new subclasses +- Client might be limited to the products exposed by the factory interface + +## Relations with Other Patterns + +- **Abstract Factory** classes are often implemented with Factory Methods +- **Factory Methods** are often used in Template Methods +- **Prototype** can be an alternative to Factory when the goal is to reduce subclassing +- **Builder** focuses on constructing complex objects step by step, while Factory Method is a single method call + +## Real-World Examples + +- Java's `Calendar.getInstance()` +- UI frameworks' widget factories +- Database connection factories +- Document generators in office suites + +## References + +- "Design Patterns: Elements of Reusable Object-Oriented Software" by Gang of Four (GoF) +- [Refactoring Guru - Factory Method Pattern](https://refactoring.guru/design-patterns/factory-method) +- [Refactoring Guru - Abstract Factory Pattern](https://refactoring.guru/design-patterns/abstract-factory) \ No newline at end of file diff --git a/docs/design-patterns/observer.md b/docs/design-patterns/observer.md new file mode 100644 index 0000000..b15b4b9 --- /dev/null +++ b/docs/design-patterns/observer.md @@ -0,0 +1,404 @@ +# Observer Design Pattern + +The Observer pattern is a behavioral design pattern where an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. + +## Intent + +- Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. +- Encapsulate the core components in a Subject abstraction, and the variable components in an Observer hierarchy. +- The Subject and Observer classes can vary independently. + +## Problem + +In many applications, specific types of objects need to be informed about changes in other objects. However, we don't want to couple these different types of objects too tightly to maintain flexibility and reusability. + +You need a way for an object to notify an open-ended number of other objects about changes, without having those objects tightly coupled to each other. + +## Structure + +![Observer Pattern Structure](https://refactoring.guru/images/patterns/diagrams/observer/structure.png) + +- **Subject**: Interface or abstract class defining operations for attaching, detaching, and notifying observers. +- **ConcreteSubject**: Maintains state of interest to observers and sends notifications when state changes. +- **Observer**: Interface or abstract class with an update method that gets called when the subject's state changes. +- **ConcreteObserver**: Implements the Observer interface to keep its state consistent with the subject's state. + +## Implementation + +### Basic Implementation + +```java +// Observer interface +interface Observer { + void update(Subject subject); +} + +// Subject interface +interface Subject { + void attach(Observer observer); + void detach(Observer observer); + void notifyObservers(); +} + +// Concrete Subject +class ConcreteSubject implements Subject { + private List observers = new ArrayList<>(); + private int state; + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + notifyObservers(); + } + + @Override + public void attach(Observer observer) { + observers.add(observer); + } + + @Override + public void detach(Observer observer) { + observers.remove(observer); + } + + @Override + public void notifyObservers() { + for (Observer observer : observers) { + observer.update(this); + } + } +} + +// Concrete Observer +class ConcreteObserver implements Observer { + private int observerState; + + @Override + public void update(Subject subject) { + if (subject instanceof ConcreteSubject) { + observerState = ((ConcreteSubject) subject).getState(); + System.out.println("Observer state updated to: " + observerState); + } + } +} +``` + +### Push vs. Pull Models + +#### Push Model + +In the push model, the Subject sends detailed information about the change to all Observers, whether they need it or not: + +```java +// In ConcreteSubject +public void notifyObservers(int state) { + for (Observer observer : observers) { + observer.update(state); + } +} + +// In Observer interface +void update(int state); +``` + +#### Pull Model + +In the pull model, the Subject simply notifies Observers that a change occurred, and Observers are responsible for pulling the needed data: + +```java +// In ConcreteSubject +public void notifyObservers() { + for (Observer observer : observers) { + observer.update(this); + } +} + +// In Observer interface +void update(Subject subject); +``` + +## Examples in Different Languages + +### JavaScript + +```javascript +// Using ES6 classes +class Subject { + constructor() { + this.observers = []; + } + + attach(observer) { + if (!this.observers.includes(observer)) { + this.observers.push(observer); + } + } + + detach(observer) { + const index = this.observers.indexOf(observer); + if (index !== -1) { + this.observers.splice(index, 1); + } + } + + notify() { + for (const observer of this.observers) { + observer.update(this); + } + } +} + +class WeatherStation extends Subject { + constructor() { + super(); + this.temperature = 0; + this.humidity = 0; + } + + setMeasurements(temperature, humidity) { + this.temperature = temperature; + this.humidity = humidity; + this.notify(); + } + + getTemperature() { + return this.temperature; + } + + getHumidity() { + return this.humidity; + } +} + +class Observer { + update(subject) {} +} + +class DisplayDevice extends Observer { + constructor(name) { + super(); + this.name = name; + } + + update(weatherStation) { + console.log(`${this.name} Display: Temperature ${weatherStation.getTemperature()}°C, Humidity ${weatherStation.getHumidity()}%`); + } +} + +// Usage +const weatherStation = new WeatherStation(); +const phoneDisplay = new DisplayDevice('Phone'); +const laptopDisplay = new DisplayDevice('Laptop'); + +weatherStation.attach(phoneDisplay); +weatherStation.attach(laptopDisplay); + +weatherStation.setMeasurements(25, 60); // Both displays update +weatherStation.detach(laptopDisplay); +weatherStation.setMeasurements(26, 70); // Only phone display updates +``` + +### Python + +```python +from abc import ABC, abstractmethod + +# Observer interface +class Observer(ABC): + @abstractmethod + def update(self, subject): + pass + +# Subject interface +class Subject(ABC): + @abstractmethod + def attach(self, observer): + pass + + @abstractmethod + def detach(self, observer): + pass + + @abstractmethod + def notify(self): + pass + +# Concrete Subject +class NewsPublisher(Subject): + def __init__(self): + self._observers = [] + self._latest_news = None + + def attach(self, observer): + self._observers.append(observer) + + def detach(self, observer): + self._observers.remove(observer) + + def notify(self): + for observer in self._observers: + observer.update(self) + + def add_news(self, news): + self._latest_news = news + self.notify() + + @property + def latest_news(self): + return self._latest_news + +# Concrete Observer +class NewsSubscriber(Observer): + def __init__(self, name): + self._name = name + + def update(self, subject): + print(f"{self._name} received news: {subject.latest_news}") + +# Usage +if __name__ == "__main__": + publisher = NewsPublisher() + + subscriber1 = NewsSubscriber("Subscriber 1") + subscriber2 = NewsSubscriber("Subscriber 2") + + publisher.attach(subscriber1) + publisher.attach(subscriber2) + + publisher.add_news("Breaking News: Observer Pattern in Action!") + + publisher.detach(subscriber1) + + publisher.add_news("Another Update: Subscriber 1 has unsubscribed!") +``` + +### C# + +```csharp +using System; +using System.Collections.Generic; + +// Observer interface +public interface IObserver +{ + void Update(ISubject subject); +} + +// Subject interface +public interface ISubject +{ + void Attach(IObserver observer); + void Detach(IObserver observer); + void Notify(); +} + +// Concrete Subject +public class StockMarket : ISubject +{ + private List _observers = new List(); + private Dictionary _stocks = new Dictionary(); + + public void Attach(IObserver observer) + { + Console.WriteLine("StockMarket: Attached an observer."); + _observers.Add(observer); + } + + public void Detach(IObserver observer) + { + _observers.Remove(observer); + Console.WriteLine("StockMarket: Detached an observer."); + } + + public void Notify() + { + Console.WriteLine("StockMarket: Notifying observers..."); + + foreach (var observer in _observers) + { + observer.Update(this); + } + } + + public void UpdateStockPrice(string stockSymbol, double price) + { + Console.WriteLine($"StockMarket: {stockSymbol} price updated to {price}"); + _stocks[stockSymbol] = price; + Notify(); + } + + public Dictionary GetStocks() + { + return _stocks; + } +} + +// Concrete Observer +public class Investor : IObserver +{ + private string _name; + private Dictionary _watchlist = new Dictionary(); + + public Investor(string name) + { + _name = name; + } + + public void Update(ISubject subject) + { + if (subject is StockMarket stockMarket) + { + var stocks = stockMarket.GetStocks(); + foreach (var stock in stocks) + { + if (_watchlist.ContainsKey(stock.Key) && _watchlist[stock.Key] != stock.Value) + { + Console.WriteLine($"{_name}: Noticed {stock.Key} price changed from {_watchlist[stock.Key]} to {stock.Value}"); + } + _watchlist[stock.Key] = stock.Value; + } + } + } +} +``` + +## Real-World Use Cases + +1. **Event Handling Systems**: UI frameworks use Observer pattern to handle user actions. +2. **News Subscription Services**: Users subscribe to topics and receive updates. +3. **Stock Market Monitoring**: Investors monitor stock price changes. +4. **Social Media Notifications**: Users get notified about activities related to their account. +5. **Message Queue Systems**: Publishers send messages to subscribed consumers. +6. **Monitoring Systems**: Applications monitor system resources or services. + +## Pros and Cons + +### Pros + +- **Open/Closed Principle**: You can introduce new subscriber classes without changing the publisher's code. +- **Loose Coupling**: Publishers don't need to know anything about subscribers. +- **Dynamic Relationships**: Relationships between publishers and subscribers can be established at runtime. +- **Event Handling**: Effective for implementing event handling systems. + +### Cons + +- **Unexpected Updates**: Subscribers can be notified in an unpredictable order. +- **Memory Leaks**: If observers forget to unsubscribe, they might not be garbage collected. +- **Performance Overhead**: Notification can be costly if there are many observers or frequent state changes. +- **Complexity**: Debugging can be challenging because the flow of control is less obvious. + +## Relations with Other Patterns + +- **Mediator**: While Observer distributes communication by introducing subscriber and publisher objects, Mediator encapsulates the communication between objects. +- **Command**: Commands can be used to implement the Observer pattern by turning requests into objects. +- **Memento**: Can be used with Observer to undo operations after notifying observers about the changes. +- **MVC Pattern**: The Observer pattern is often used in MVC architectures where the View observes changes in the Model. + +## References + +- "Design Patterns: Elements of Reusable Object-Oriented Software" by Gang of Four (GoF) +- [Refactoring Guru - Observer Pattern](https://refactoring.guru/design-patterns/observer) +- [SourceMaking - Observer Pattern](https://sourcemaking.com/design_patterns/observer) \ No newline at end of file diff --git a/docs/design-patterns/singleton.md b/docs/design-patterns/singleton.md new file mode 100644 index 0000000..2e72e49 --- /dev/null +++ b/docs/design-patterns/singleton.md @@ -0,0 +1,226 @@ +# Singleton Design Pattern + +The Singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. + +## Intent + +- Ensure a class has only one instance. +- Provide a global access point to that instance. +- Control concurrent access to shared resources. + +## Problem + +When should you use the Singleton pattern? + +- When you need exactly one instance of a class to coordinate actions across the system +- When you want to restrict the instantiation of a class to just one object +- When you need stricter control over global variables + +## Structure + +``` ++----------------+ +| Singleton | ++----------------+ +| -instance | ++----------------+ +| +getInstance() | +| -constructor() | ++----------------+ +``` + +![Singleton Pattern Structure](https://refactoring.guru/images/patterns/diagrams/singleton/structure-en.png) + +## Implementation + +### Basic Implementation + +```java +public class Singleton { + // The private static instance of the class + private static Singleton instance; + + // Private constructor prevents instantiation from other classes + private Singleton() { } + + // The public static method to get the instance + public static Singleton getInstance() { + if (instance == null) { + instance = new Singleton(); + } + return instance; + } + + // Other methods and fields + public void doSomething() { + System.out.println("Singleton is doing something"); + } +} +``` + +### Thread-Safe Implementation + +```java +public class ThreadSafeSingleton { + private static volatile ThreadSafeSingleton instance; + + private ThreadSafeSingleton() { } + + public static ThreadSafeSingleton getInstance() { + // Double-checked locking + if (instance == null) { + synchronized (ThreadSafeSingleton.class) { + if (instance == null) { + instance = new ThreadSafeSingleton(); + } + } + } + return instance; + } +} +``` + +### Eager Initialization + +```java +public class EagerSingleton { + // Instance is created at load time + private static final EagerSingleton INSTANCE = new EagerSingleton(); + + private EagerSingleton() { } + + public static EagerSingleton getInstance() { + return INSTANCE; + } +} +``` + +### Using Enum (Java) + +```java +public enum EnumSingleton { + INSTANCE; + + public void doSomething() { + System.out.println("Singleton enum is doing something"); + } +} +``` + +## Examples in Different Languages + +### JavaScript + +```javascript +class Singleton { + constructor() { + if (Singleton.instance) { + return Singleton.instance; + } + + // Initialize the singleton + this.data = []; + Singleton.instance = this; + } + + add(item) { + this.data.push(item); + } + + get(index) { + return this.data[index]; + } +} + +// Usage +const instance1 = new Singleton(); +const instance2 = new Singleton(); +console.log(instance1 === instance2); // true +``` + +### Python + +```python +class Singleton: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(Singleton, cls).__new__(cls) + # Initialize your singleton here + cls._instance.value = 0 + return cls._instance + +# Usage +s1 = Singleton() +s2 = Singleton() +print(s1 is s2) # True +``` + +### C# + +```csharp +public sealed class Singleton +{ + private static Singleton instance = null; + private static readonly object padlock = new object(); + + Singleton() {} + + public static Singleton Instance + { + get + { + lock(padlock) + { + if (instance == null) + { + instance = new Singleton(); + } + return instance; + } + } + } +} +``` + +## Use Cases + +- **Database connections**: Manage a connection pool +- **Logger**: Create a single logging instance for an application +- **Configuration settings**: Store application settings +- **Cache**: Create a single cache manager +- **Thread pools**: Manage thread creation and assignment + +## Pros and Cons + +### Pros + +- Ensures a class has just a single instance +- Provides a global access point to that instance +- The singleton object is initialized only when it's requested for the first time + +### Cons + +- Violates the Single Responsibility Principle (the class manages its own creation) +- Can mask bad design, for instance, when components know too much about each other +- Requires special treatment in a multithreaded environment +- Makes unit testing more difficult + +## Relations with Other Patterns + +- A **Facade** might look like a Singleton if it only hides one object, but they have different purposes +- **Abstract Factories**, **Builders**, and **Prototypes** can all be implemented as Singletons + +## Real-World Examples + +- Java's `java.lang.Runtime` class +- UI Managers in many GUI frameworks +- Windows Registry +- Browser's window object + +## References + +- "Design Patterns: Elements of Reusable Object-Oriented Software" by Gang of Four (GoF) +- [Refactoring Guru - Singleton Pattern](https://refactoring.guru/design-patterns/singleton) +- [SourceMaking - Singleton Pattern](https://sourcemaking.com/design_patterns/singleton) \ No newline at end of file diff --git a/docs/devops/ci-cd.md b/docs/devops/ci-cd.md new file mode 100644 index 0000000..bdaa6f5 --- /dev/null +++ b/docs/devops/ci-cd.md @@ -0,0 +1,162 @@ +# Continuous Integration and Continuous Deployment (CI/CD) + +Continuous Integration and Continuous Deployment (CI/CD) is a method to frequently deliver apps to customers by introducing automation into the stages of app development. + +## What is CI/CD? + +### Continuous Integration (CI) +Continuous Integration is a development practice where developers integrate code into a shared repository frequently, preferably several times a day. Each integration can then be verified by an automated build and automated tests. + +### Continuous Delivery (CD) +Continuous Delivery is an extension of continuous integration to ensure that you can release new changes to your customers quickly in a sustainable way. This means that on top of having automated your testing, you also have automated your release process and you can deploy your application at any point of time by clicking a button. + +### Continuous Deployment +Continuous Deployment goes one step further than Continuous Delivery. With this practice, every change that passes all stages of your production pipeline is released to your customers. There's no human intervention, and only a failed test will prevent a new change to be deployed to production. + +## CI/CD Pipeline Components + +A typical CI/CD pipeline includes the following stages: + +1. **Source**: Code is committed to a version control system (Git, SVN, etc.) +2. **Build**: Code is compiled, dependencies are resolved +3. **Test**: Automated tests are run (unit tests, integration tests, etc.) +4. **Deploy**: Application is deployed to staging/production environments +5. **Monitor**: Application performance and errors are monitored + +## Popular CI/CD Tools + +### Jenkins + +Jenkins is an open-source automation server that enables developers to build, test, and deploy their software. + +```yaml +# Example Jenkinsfile +pipeline { + agent any + + stages { + stage('Build') { + steps { + echo 'Building..' + sh 'npm install' + } + } + stage('Test') { + steps { + echo 'Testing..' + sh 'npm test' + } + } + stage('Deploy') { + steps { + echo 'Deploying....' + sh 'npm run deploy' + } + } + } +} +``` + +### GitHub Actions + +GitHub Actions is a CI/CD platform that allows you to automate your build, test, and deployment pipeline directly from GitHub. + +```yaml +# Example GitHub Actions workflow +name: Node.js CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: '14.x' + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm test + - name: Deploy + if: github.ref == 'refs/heads/main' + run: npm run deploy +``` + +### GitLab CI/CD + +GitLab CI/CD is a part of GitLab that allows you to apply all the continuous methods (Continuous Integration, Delivery, and Deployment) to your software. + +```yaml +# Example .gitlab-ci.yml +stages: + - build + - test + - deploy + +build: + stage: build + script: + - echo "Building the app" + - npm install + +test: + stage: test + script: + - echo "Running tests" + - npm test + +deploy: + stage: deploy + script: + - echo "Deploying application" + - npm run deploy + only: + - main +``` + +### CircleCI + +CircleCI is a cloud-based CI/CD tool that automates the software development process. + +```yaml +# Example CircleCI configuration +version: 2.1 +jobs: + build: + docker: + - image: cimg/node:14.17 + steps: + - checkout + - run: npm install + - run: npm test + - run: npm run deploy +``` + +## Best Practices for CI/CD + +1. **Automate Everything**: Automate as much of the software delivery process as possible. +2. **Fail Fast**: Detect and address issues as early as possible in the development process. +3. **Keep the Build Green**: A broken build should be the team's highest priority to fix. +4. **Build Only Once**: Build artifacts once and promote the same artifacts through the pipeline. +5. **Deploy the Same Way to Every Environment**: Use the same deployment process for all environments. +6. **Smoke Test Your Deployments**: Run basic tests after deployment to verify the system is running correctly. +7. **Keep Your CI/CD Pipeline Fast**: Aim for a pipeline that completes in less than 10 minutes. +8. **Maintain Good Test Coverage**: Ensure your tests cover most of your codebase. + +## References + +- [Martin Fowler on Continuous Integration](https://martinfowler.com/articles/continuousIntegration.html) +- [The DevOps Handbook](https://itrevolution.com/book/the-devops-handbook/) +- [Continuous Delivery](https://continuousdelivery.com/) +- [Jenkins Documentation](https://www.jenkins.io/doc/) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [GitLab CI/CD Documentation](https://docs.gitlab.com/ee/ci/) +- [CircleCI Documentation](https://circleci.com/docs/) diff --git a/docs/linux/bash-scripting.md b/docs/linux/bash-scripting.md new file mode 100644 index 0000000..3173040 --- /dev/null +++ b/docs/linux/bash-scripting.md @@ -0,0 +1,325 @@ +# Bash Scripting + +Bash (Bourne Again SHell) is a command language interpreter that is widely used on various operating systems, and is the default shell on most Linux distributions. + +## Introduction to Bash Scripting + +Bash scripts are text files containing a series of commands that are executed by the Bash shell. They allow you to automate repetitive tasks, combine complex commands, and create custom utilities. + +## Basic Syntax + +### Creating a Bash Script + +1. Create a file with a `.sh` extension +2. Add the shebang line at the top: `#!/bin/bash` +3. Make the script executable: `chmod +x script.sh` +4. Run the script: `./script.sh` + +### Hello World Example + +```bash +#!/bin/bash +# This is a comment +echo "Hello, World!" +``` + +## Variables + +### Variable Declaration and Usage + +```bash +#!/bin/bash + +# Variable declaration +name="John" +age=30 + +# Using variables +echo "Name: $name" +echo "Age: $age" + +# Command substitution +current_date=$(date) +echo "Current date: $current_date" + +# Arithmetic operations +result=$((10 + 5)) +echo "10 + 5 = $result" +``` + +### Special Variables + +| Variable | Description | +|----------|-------------| +| `$0` | The name of the script | +| `$1` to `$9` | The first 9 arguments passed to the script | +| `$#` | The number of arguments passed to the script | +| `$@` | All arguments passed to the script | +| `$?` | The exit status of the last command | +| `$$` | The process ID of the current script | +| `$USER` | The username of the user running the script | +| `$HOSTNAME` | The hostname of the machine | +| `$RANDOM` | A random number | +| `$HOME` | The home directory of the user | + +## Control Structures + +### Conditional Statements + +#### If-Else Statement + +```bash +#!/bin/bash + +age=25 + +if [ $age -lt 18 ]; then + echo "You are a minor." +elif [ $age -ge 18 ] && [ $age -lt 65 ]; then + echo "You are an adult." +else + echo "You are a senior." +fi +``` + +#### Case Statement + +```bash +#!/bin/bash + +fruit="apple" + +case $fruit in + "apple") + echo "This is an apple." + ;; + "banana") + echo "This is a banana." + ;; + "orange") + echo "This is an orange." + ;; + *) + echo "Unknown fruit." + ;; +esac +``` + +### Loops + +#### For Loop + +```bash +#!/bin/bash + +# Simple for loop +for i in 1 2 3 4 5; do + echo "Number: $i" +done + +# For loop with range +for i in {1..5}; do + echo "Number: $i" +done + +# For loop with step +for i in {1..10..2}; do + echo "Odd number: $i" +done + +# For loop with command output +for file in $(ls); do + echo "File: $file" +done +``` + +#### While Loop + +```bash +#!/bin/bash + +count=1 + +while [ $count -le 5 ]; do + echo "Count: $count" + ((count++)) +done +``` + +#### Until Loop + +```bash +#!/bin/bash + +count=1 + +until [ $count -gt 5 ]; do + echo "Count: $count" + ((count++)) +done +``` + +## Functions + +### Function Definition and Usage + +```bash +#!/bin/bash + +# Function definition +greet() { + echo "Hello, $1!" +} + +# Function with return value +add() { + local result=$(($1 + $2)) + echo $result +} + +# Function calls +greet "John" +sum=$(add 5 3) +echo "5 + 3 = $sum" +``` + +## Input and Output + +### Reading User Input + +```bash +#!/bin/bash + +# Read a single value +echo "Enter your name:" +read name +echo "Hello, $name!" + +# Read multiple values +echo "Enter your first and last name:" +read first_name last_name +echo "Hello, $first_name $last_name!" + +# Read with prompt +read -p "Enter your age: " age +echo "You are $age years old." + +# Read password (hidden input) +read -sp "Enter your password: " password +echo -e "\nPassword received." +``` + +### File Input/Output + +```bash +#!/bin/bash + +# Writing to a file +echo "Hello, World!" > output.txt +echo "This is a new line." >> output.txt + +# Reading from a file +while IFS= read -r line; do + echo "Line: $line" +done < input.txt + +# Process each line of a file +cat input.txt | while read line; do + echo "Processing: $line" +done +``` + +## Arrays + +### Array Operations + +```bash +#!/bin/bash + +# Declare an array +fruits=("apple" "banana" "orange" "grape") + +# Access array elements +echo "First fruit: ${fruits[0]}" +echo "All fruits: ${fruits[@]}" +echo "Number of fruits: ${#fruits[@]}" + +# Iterate through array +for fruit in "${fruits[@]}"; do + echo "Fruit: $fruit" +done + +# Add element to array +fruits+=("kiwi") + +# Remove element from array +unset fruits[1] +``` + +## String Manipulation + +### String Operations + +```bash +#!/bin/bash + +# String length +str="Hello, World!" +echo "Length: ${#str}" + +# Substring +echo "Substring: ${str:7:5}" + +# String replacement +echo "Replace: ${str/World/Bash}" + +# Convert to uppercase/lowercase +echo "Uppercase: ${str^^}" +echo "Lowercase: ${str,,}" +``` + +## Error Handling + +### Basic Error Handling + +```bash +#!/bin/bash + +# Exit on error +set -e + +# Custom error handling +handle_error() { + echo "Error occurred at line $1" + exit 1 +} + +# Trap errors +trap 'handle_error $LINENO' ERR + +# Check command success +if ! command -v git &> /dev/null; then + echo "Git is not installed." + exit 1 +fi +``` + +## Best Practices + +1. **Use Shebang**: Always include `#!/bin/bash` at the top of your scripts. +2. **Comments**: Add comments to explain complex logic. +3. **Error Handling**: Implement proper error handling. +4. **Indentation**: Use consistent indentation for readability. +5. **Naming Conventions**: Use descriptive names for variables and functions. +6. **Quoting Variables**: Always quote variables to handle spaces and special characters. +7. **Exit Codes**: Return appropriate exit codes. +8. **Modularity**: Break complex scripts into functions. +9. **Debugging**: Use `set -x` for debugging. +10. **Testing**: Test your scripts with different inputs. + +## References + +- [GNU Bash Manual](https://www.gnu.org/software/bash/manual/) +- [Bash Guide for Beginners](https://tldp.org/LDP/Bash-Beginners-Guide/html/) +- [Advanced Bash-Scripting Guide](https://tldp.org/LDP/abs/html/) +- [ShellCheck](https://www.shellcheck.net/) - A shell script analysis tool diff --git a/docs/system-design/microservices.md b/docs/system-design/microservices.md new file mode 100644 index 0000000..df86b54 --- /dev/null +++ b/docs/system-design/microservices.md @@ -0,0 +1,467 @@ +# Microservices Architecture + +Microservices architecture is an architectural style that structures an application as a collection of small, loosely coupled services that can be developed, deployed, and scaled independently. + +## What are Microservices? + +Microservices are small, autonomous services that work together to form a complete application. Each microservice: + +- Focuses on a single business capability +- Runs in its own process +- Communicates via well-defined APIs +- Can be deployed independently +- Can be written in different programming languages +- Can use different data storage technologies + +## Monolithic vs. Microservices Architecture + +| Aspect | Monolithic | Microservices | +|--------|------------|---------------| +| Structure | Single, unified codebase | Multiple, independent services | +| Development | Simpler to develop initially | More complex initial setup | +| Deployment | Deploy entire application | Deploy individual services | +| Scaling | Scale entire application | Scale individual services as needed | +| Technology | Single technology stack | Multiple technology stacks possible | +| Team Structure | Larger teams working on same codebase | Smaller teams working on individual services | +| Failure Impact | Single point of failure | Isolated failures | +| Data Management | Shared database | Database per service | + +## Key Principles of Microservices + +1. **Single Responsibility**: Each service is responsible for a specific business capability. +2. **Autonomy**: Services can be developed, deployed, and scaled independently. +3. **Resilience**: Failure in one service should not cascade to others. +4. **Decentralization**: Decentralized governance and data management. +5. **Continuous Delivery**: Frequent, automated deployment of individual services. +6. **Observability**: Comprehensive monitoring and logging. +7. **Domain-Driven Design**: Services are designed around business domains. + +## Microservices Communication Patterns + +### Synchronous Communication + +#### REST API + +```json +// Example REST API request +GET /api/products/123 +Accept: application/json + +// Example response +{ + "id": "123", + "name": "Product Name", + "price": 19.99, + "category": "Electronics" +} +``` + +#### gRPC + +```protobuf +// Example gRPC service definition +syntax = "proto3"; + +service ProductService { + rpc GetProduct(ProductRequest) returns (Product); +} + +message ProductRequest { + string id = 1; +} + +message Product { + string id = 1; + string name = 2; + double price = 3; + string category = 4; +} +``` + +### Asynchronous Communication + +#### Message Queue + +```javascript +// Example message producer (Node.js with RabbitMQ) +const amqp = require('amqplib'); + +async function sendOrderCreatedEvent(order) { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); + + const queue = 'order_events'; + const message = JSON.stringify({ + type: 'ORDER_CREATED', + data: order + }); + + await channel.assertQueue(queue, { durable: true }); + channel.sendToQueue(queue, Buffer.from(message)); + + console.log(`Sent: ${message}`); + + setTimeout(() => { + connection.close(); + }, 500); +} +``` + +```javascript +// Example message consumer (Node.js with RabbitMQ) +const amqp = require('amqplib'); + +async function processOrderEvents() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); + + const queue = 'order_events'; + + await channel.assertQueue(queue, { durable: true }); + console.log(`Waiting for messages in ${queue}`); + + channel.consume(queue, (msg) => { + const event = JSON.parse(msg.content.toString()); + console.log(`Received: ${event.type}`); + + if (event.type === 'ORDER_CREATED') { + // Process the order + processOrder(event.data); + } + + channel.ack(msg); + }); +} +``` + +#### Event Streaming + +```java +// Example Kafka producer (Java) +Properties props = new Properties(); +props.put("bootstrap.servers", "localhost:9092"); +props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); +props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + +Producer producer = new KafkaProducer<>(props); + +String topic = "order-events"; +String key = order.getId(); +String value = objectMapper.writeValueAsString(order); + +ProducerRecord record = new ProducerRecord<>(topic, key, value); +producer.send(record); +producer.close(); +``` + +```java +// Example Kafka consumer (Java) +Properties props = new Properties(); +props.put("bootstrap.servers", "localhost:9092"); +props.put("group.id", "order-processing-group"); +props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); +props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + +Consumer consumer = new KafkaConsumer<>(props); +consumer.subscribe(Arrays.asList("order-events")); + +while (true) { + ConsumerRecords records = consumer.poll(Duration.ofMillis(100)); + for (ConsumerRecord record : records) { + String key = record.key(); + String value = record.value(); + + Order order = objectMapper.readValue(value, Order.class); + processOrder(order); + } +} +``` + +## Service Discovery and API Gateway + +### Service Discovery + +```yaml +# Example Consul service registration +{ + "service": { + "name": "product-service", + "id": "product-service-1", + "tags": ["api", "v1"], + "address": "10.0.0.1", + "port": 8080, + "checks": [ + { + "http": "http://10.0.0.1:8080/health", + "interval": "10s" + } + ] + } +} +``` + +### API Gateway + +```yaml +# Example Kong API Gateway configuration +services: + - name: product-service + url: http://product-service:8080 + routes: + - name: product-routes + paths: + - /api/products + strip_path: true + plugins: + - name: rate-limiting + config: + minute: 100 + - name: jwt +``` + +## Data Management in Microservices + +### Database per Service + +```yaml +# Example docker-compose.yml for multiple databases +version: '3' +services: + product-service: + build: ./product-service + depends_on: + - product-db + environment: + - DB_HOST=product-db + - DB_PORT=5432 + - DB_NAME=productdb + + product-db: + image: postgres:13 + environment: + - POSTGRES_DB=productdb + - POSTGRES_USER=user + - POSTGRES_PASSWORD=password + volumes: + - product-db-data:/var/lib/postgresql/data + + order-service: + build: ./order-service + depends_on: + - order-db + environment: + - DB_HOST=order-db + - DB_PORT=27017 + - DB_NAME=orderdb + + order-db: + image: mongo:4.4 + environment: + - MONGO_INITDB_DATABASE=orderdb + volumes: + - order-db-data:/data/db + +volumes: + product-db-data: + order-db-data: +``` + +### CQRS Pattern + +```csharp +// Command side +public class CreateOrderCommand +{ + public string CustomerId { get; set; } + public List Items { get; set; } +} + +public class OrderCommandHandler +{ + private readonly IOrderRepository _repository; + private readonly IEventBus _eventBus; + + public OrderCommandHandler(IOrderRepository repository, IEventBus eventBus) + { + _repository = repository; + _eventBus = eventBus; + } + + public async Task Handle(CreateOrderCommand command) + { + var order = new Order(Guid.NewGuid(), command.CustomerId, command.Items); + await _repository.SaveAsync(order); + + await _eventBus.PublishAsync(new OrderCreatedEvent + { + OrderId = order.Id, + CustomerId = order.CustomerId, + Items = order.Items + }); + } +} + +// Query side +public class OrderQueryService +{ + private readonly IOrderReadModel _readModel; + + public OrderQueryService(IOrderReadModel readModel) + { + _readModel = readModel; + } + + public async Task GetOrderAsync(Guid orderId) + { + return await _readModel.GetOrderAsync(orderId); + } + + public async Task> GetCustomerOrdersAsync(string customerId) + { + return await _readModel.GetCustomerOrdersAsync(customerId); + } +} +``` + +## Deploying Microservices + +### Docker Containers + +```dockerfile +# Example Dockerfile for a microservice +FROM node:14-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm install --production + +COPY . . + +EXPOSE 8080 + +CMD ["node", "server.js"] +``` + +### Kubernetes + +```yaml +# Example Kubernetes deployment for a microservice +apiVersion: apps/v1 +kind: Deployment +metadata: + name: product-service + labels: + app: product-service +spec: + replicas: 3 + selector: + matchLabels: + app: product-service + template: + metadata: + labels: + app: product-service + spec: + containers: + - name: product-service + image: my-registry/product-service:1.0.0 + ports: + - containerPort: 8080 + env: + - name: DB_HOST + value: product-db-service + - name: DB_PORT + value: "5432" + - name: DB_NAME + value: productdb + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + name: product-service +spec: + selector: + app: product-service + ports: + - port: 80 + targetPort: 8080 + type: ClusterIP +``` + +## Monitoring and Observability + +### Distributed Tracing + +```java +// Example using Spring Cloud Sleuth and Zipkin +@RestController +public class ProductController { + + private final ProductService productService; + + @Autowired + public ProductController(ProductService productService) { + this.productService = productService; + } + + @GetMapping("/products/{id}") + public Product getProduct(@PathVariable String id) { + // Tracing is automatically handled by Sleuth + return productService.getProduct(id); + } +} +``` + +### Metrics Collection + +```yaml +# Example Prometheus configuration +global: + scrape_interval: 15s + +scrape_configs: + - job_name: 'product-service' + metrics_path: '/actuator/prometheus' + static_configs: + - targets: ['product-service:8080'] + + - job_name: 'order-service' + metrics_path: '/actuator/prometheus' + static_configs: + - targets: ['order-service:8080'] +``` + +## Challenges and Best Practices + +### Challenges + +1. **Distributed System Complexity**: Debugging and testing become more difficult. +2. **Data Consistency**: Maintaining consistency across services is challenging. +3. **Service Boundaries**: Defining the right service boundaries requires domain expertise. +4. **Operational Overhead**: More services mean more infrastructure to manage. +5. **Network Latency**: Communication between services adds latency. + +### Best Practices + +1. **Start Monolithic**: Begin with a monolith and extract microservices as needed. +2. **Design for Failure**: Implement circuit breakers, retries, and fallbacks. +3. **Automate Everything**: Use CI/CD pipelines for all services. +4. **Implement Monitoring**: Comprehensive logging, metrics, and tracing. +5. **Use Containers**: Containerize services for consistency across environments. +6. **API Versioning**: Implement proper API versioning to manage changes. +7. **Documentation**: Maintain clear documentation for all services and APIs. +8. **Testing**: Implement comprehensive testing strategies, including contract testing. + +## References + +- [Microservices.io](https://microservices.io/) +- [Martin Fowler on Microservices](https://martinfowler.com/articles/microservices.html) +- [Sam Newman - Building Microservices](https://samnewman.io/books/building_microservices/) +- [Chris Richardson - Microservices Patterns](https://microservices.io/book) diff --git a/docs/testing/unit-testing.md b/docs/testing/unit-testing.md new file mode 100644 index 0000000..600021c --- /dev/null +++ b/docs/testing/unit-testing.md @@ -0,0 +1,467 @@ +# Unit Testing + +Unit testing is a software testing method where individual units or components of a software are tested in isolation from the rest of the system. + +## What is Unit Testing? + +A unit test verifies that a small, isolated piece of code (a "unit") behaves exactly as the developer expects. Units are typically: + +- Individual functions or methods +- Classes +- Modules or components + +The goal is to validate that each unit of the software performs as designed. + +## Benefits of Unit Testing + +- **Early Bug Detection**: Catch bugs early in the development cycle +- **Facilitates Changes**: Makes it easier to refactor code and add new features +- **Documentation**: Tests serve as documentation for how the code should behave +- **Design Improvement**: Encourages better software design and modularity +- **Confidence**: Provides confidence that the code works as expected +- **Reduces Costs**: Cheaper to fix bugs found during unit testing than later stages + +## Unit Testing Principles + +### FIRST Principles + +- **Fast**: Tests should run quickly +- **Independent**: Tests should not depend on each other +- **Repeatable**: Tests should yield the same results every time +- **Self-validating**: Tests should automatically determine if they pass or fail +- **Timely**: Tests should be written at the right time (ideally before the code) + +### AAA Pattern + +- **Arrange**: Set up the test conditions +- **Act**: Execute the code being tested +- **Assert**: Verify the result is as expected + +## Unit Testing Frameworks + +### JavaScript (Jest) + +```javascript +// math.js +function add(a, b) { + return a + b; +} + +function subtract(a, b) { + return a - b; +} + +module.exports = { add, subtract }; + +// math.test.js +const { add, subtract } = require('./math'); + +describe('Math functions', () => { + test('add should correctly add two numbers', () => { + // Arrange + const a = 5; + const b = 3; + + // Act + const result = add(a, b); + + // Assert + expect(result).toBe(8); + }); + + test('subtract should correctly subtract two numbers', () => { + // Arrange + const a = 5; + const b = 3; + + // Act + const result = subtract(a, b); + + // Assert + expect(result).toBe(2); + }); +}); +``` + +### Python (pytest) + +```python +# math_utils.py +def add(a, b): + return a + b + +def subtract(a, b): + return a - b + +# test_math_utils.py +import pytest +from math_utils import add, subtract + +def test_add(): + # Arrange + a = 5 + b = 3 + + # Act + result = add(a, b) + + # Assert + assert result == 8 + +def test_subtract(): + # Arrange + a = 5 + b = 3 + + # Act + result = subtract(a, b) + + # Assert + assert result == 2 +``` + +### Java (JUnit) + +```java +// MathUtils.java +public class MathUtils { + public int add(int a, int b) { + return a + b; + } + + public int subtract(int a, int b) { + return a - b; + } +} + +// MathUtilsTest.java +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +public class MathUtilsTest { + + @Test + public void testAdd() { + // Arrange + MathUtils mathUtils = new MathUtils(); + int a = 5; + int b = 3; + + // Act + int result = mathUtils.add(a, b); + + // Assert + assertEquals(8, result); + } + + @Test + public void testSubtract() { + // Arrange + MathUtils mathUtils = new MathUtils(); + int a = 5; + int b = 3; + + // Act + int result = mathUtils.subtract(a, b); + + // Assert + assertEquals(2, result); + } +} +``` + +### C# (xUnit) + +```csharp +// MathUtils.cs +public class MathUtils +{ + public int Add(int a, int b) + { + return a + b; + } + + public int Subtract(int a, int b) + { + return a - b; + } +} + +// MathUtilsTests.cs +using Xunit; + +public class MathUtilsTests +{ + [Fact] + public void Add_ShouldCorrectlyAddTwoNumbers() + { + // Arrange + var mathUtils = new MathUtils(); + int a = 5; + int b = 3; + + // Act + int result = mathUtils.Add(a, b); + + // Assert + Assert.Equal(8, result); + } + + [Fact] + public void Subtract_ShouldCorrectlySubtractTwoNumbers() + { + // Arrange + var mathUtils = new MathUtils(); + int a = 5; + int b = 3; + + // Act + int result = mathUtils.Subtract(a, b); + + // Assert + Assert.Equal(2, result); + } +} +``` + +## Test Doubles + +Test doubles are objects that replace real components in tests to isolate the code being tested. + +### Types of Test Doubles + +#### Dummy + +Objects that are passed around but never actually used. + +```javascript +// JavaScript example +function createUser(user, logger) { + // logger is not used in this test + return { id: 123, ...user }; +} + +test('createUser should add an ID to the user', () => { + // Arrange + const dummyLogger = {}; // Dummy object that's never used + const user = { name: 'John' }; + + // Act + const result = createUser(user, dummyLogger); + + // Assert + expect(result.id).toBeDefined(); + expect(result.name).toBe('John'); +}); +``` + +#### Stub + +Objects that provide predefined answers to calls made during the test. + +```java +// Java example +public interface WeatherService { + int getCurrentTemperature(String city); +} + +// Stub implementation +public class WeatherServiceStub implements WeatherService { + @Override + public int getCurrentTemperature(String city) { + return 25; // Always returns 25°C regardless of the city + } +} + +@Test +public void testWeatherReporter() { + // Arrange + WeatherService stubService = new WeatherServiceStub(); + WeatherReporter reporter = new WeatherReporter(stubService); + + // Act + String report = reporter.generateReport("London"); + + // Assert + assertEquals("Current temperature in London: 25°C", report); +} +``` + +#### Spy + +Objects that record calls made to them. + +```python +# Python example +class EmailServiceSpy: + def __init__(self): + self.emails_sent = [] + + def send_email(self, to, subject, body): + self.emails_sent.append({ + 'to': to, + 'subject': subject, + 'body': body + }) + +def test_user_registration_sends_welcome_email(): + # Arrange + email_service = EmailServiceSpy() + user_service = UserService(email_service) + + # Act + user_service.register("john@example.com", "password123") + + # Assert + assert len(email_service.emails_sent) == 1 + assert email_service.emails_sent[0]['to'] == "john@example.com" + assert "Welcome" in email_service.emails_sent[0]['subject'] +``` + +#### Mock + +Objects that verify that specific methods were called with specific arguments. + +```csharp +// C# example with Moq +[Fact] +public void Register_ShouldSendWelcomeEmail() +{ + // Arrange + var mockEmailService = new Mock(); + var userService = new UserService(mockEmailService.Object); + + // Act + userService.Register("john@example.com", "password123"); + + // Assert + mockEmailService.Verify( + x => x.SendEmail( + "john@example.com", + It.Is(s => s.Contains("Welcome")), + It.IsAny() + ), + Times.Once + ); +} +``` + +#### Fake + +Objects that have working implementations but are not suitable for production. + +```javascript +// JavaScript example +class FakeUserRepository { + constructor() { + this.users = []; + this.nextId = 1; + } + + create(userData) { + const user = { id: this.nextId++, ...userData }; + this.users.push(user); + return user; + } + + findById(id) { + return this.users.find(user => user.id === id); + } +} + +test('UserService should create a user', () => { + // Arrange + const fakeRepo = new FakeUserRepository(); + const userService = new UserService(fakeRepo); + + // Act + const user = userService.createUser('John', 'john@example.com'); + + // Assert + expect(user.id).toBe(1); + expect(user.name).toBe('John'); + expect(fakeRepo.findById(1)).toEqual(user); +}); +``` + +## Test Coverage + +Test coverage measures how much of your code is executed during tests. + +### Coverage Metrics + +- **Line Coverage**: Percentage of lines executed during tests +- **Branch Coverage**: Percentage of branches (if/else, switch) executed during tests +- **Function Coverage**: Percentage of functions called during tests +- **Statement Coverage**: Percentage of statements executed during tests + +### Example Coverage Report (Jest) + +``` +--------------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +--------------------|---------|----------|---------|---------|------------------- +All files | 85.71 | 66.67 | 100 | 85.71 | + math.js | 100 | 100 | 100 | 100 | + string-utils.js | 71.43 | 33.33 | 100 | 71.43 | 15-18 +--------------------|---------|----------|---------|---------|------------------- +``` + +## Best Practices for Unit Testing + +1. **Test One Thing at a Time**: Each test should verify one specific behavior. +2. **Keep Tests Simple**: Tests should be easy to understand and maintain. +3. **Use Descriptive Test Names**: Names should clearly describe what is being tested. +4. **Isolate the Unit**: Use test doubles to isolate the unit from its dependencies. +5. **Test Edge Cases**: Include tests for boundary conditions and error cases. +6. **Don't Test the Framework**: Focus on testing your code, not the framework or language. +7. **Maintain Test Independence**: Tests should not depend on each other or run in a specific order. +8. **Avoid Logic in Tests**: Tests should be straightforward with minimal logic. +9. **Write Tests First (TDD)**: Consider writing tests before implementing the code. +10. **Refactor Tests**: Keep tests clean and maintainable, just like production code. + +## Test-Driven Development (TDD) + +TDD is a development process where tests are written before the code. The cycle is: + +1. **Red**: Write a failing test +2. **Green**: Write the minimal code to make the test pass +3. **Refactor**: Improve the code while keeping the tests passing + +### TDD Example (JavaScript) + +```javascript +// Step 1: Write a failing test +test('isPalindrome should return true for palindromes', () => { + expect(isPalindrome('racecar')).toBe(true); +}); + +// Step 2: Write minimal code to make it pass +function isPalindrome(str) { + return str === str.split('').reverse().join(''); +} + +// Step 3: Add more tests +test('isPalindrome should return false for non-palindromes', () => { + expect(isPalindrome('hello')).toBe(false); +}); + +test('isPalindrome should be case insensitive', () => { + expect(isPalindrome('Racecar')).toBe(true); +}); + +// Step 4: Refactor the code +function isPalindrome(str) { + const normalized = str.toLowerCase(); + return normalized === normalized.split('').reverse().join(''); +} +``` + +## References + +- [Martin Fowler on Unit Testing](https://martinfowler.com/bliki/UnitTest.html) +- [Test-Driven Development by Example](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530) +- [Jest Documentation](https://jestjs.io/docs/getting-started) +- [pytest Documentation](https://docs.pytest.org/) +- [JUnit Documentation](https://junit.org/junit5/docs/current/user-guide/) +- [xUnit Documentation](https://xunit.net/docs/getting-started/netcore/cmdline) diff --git a/i18n/es/init.txt b/i18n/es/init.txt new file mode 100644 index 0000000..e69de29 diff --git a/i18n/vi/algorithms/graph-traversal_vi.md b/i18n/vi/algorithms/graph-traversal_vi.md new file mode 100644 index 0000000..d41c6f1 --- /dev/null +++ b/i18n/vi/algorithms/graph-traversal_vi.md @@ -0,0 +1,148 @@ +# Các Thuật Toán Duyệt Đồ Thị (Graph Traversal Algorithms) + +Các thuật toán duyệt đồ thị là những kỹ thuật cơ bản được sử dụng để thăm mỗi đỉnh trong một đồ thị. Chúng là nền tảng cho nhiều thuật toán đồ thị phức tạp hơn. + +## Mục Lục + +- [Tìm Kiếm Theo Chiều Rộng (BFS)](#tìm-kiếm-theo-chiều-rộng-bfs) +- [Tìm Kiếm Theo Chiều Sâu (DFS)](#tìm-kiếm-theo-chiều-sâu-dfs) +- [So Sánh BFS và DFS](#so-sánh-bfs-và-dfs) +- [Ứng Dụng](#ứng-dụng) +- [Độ Phức Tạp Về Thời Gian và Không Gian](#độ-phức-tạp-về-thời-gian-và-không-gian) + +## Tìm Kiếm Theo Chiều Rộng (BFS) + +Tìm kiếm theo chiều rộng là một thuật toán duyệt đồ thị khám phá tất cả các đỉnh ở mức độ sâu hiện tại trước khi di chuyển đến các đỉnh ở mức độ sâu tiếp theo. + +### Cách Hoạt Động của BFS + +1. Bắt đầu từ một đỉnh nguồn và đánh dấu nó là đã thăm +2. Thăm tất cả các đỉnh kề chưa thăm và đánh dấu chúng là đã thăm +3. Đối với mỗi đỉnh kề đó, thăm tất cả các đỉnh kề chưa thăm của chúng +4. Lặp lại cho đến khi tất cả các đỉnh đã được thăm + +### Cài Đặt + +```python +from collections import deque + +def bfs(graph, start): + visited = set([start]) + queue = deque([start]) + result = [] + + while queue: + vertex = queue.popleft() + result.append(vertex) + + for neighbor in graph[vertex]: + if neighbor not in visited: + visited.add(neighbor) + queue.append(neighbor) + + return result + +# Ví dụ sử dụng +graph = { + 'A': ['B', 'C'], + 'B': ['A', 'D', 'E'], + 'C': ['A', 'F'], + 'D': ['B'], + 'E': ['B', 'F'], + 'F': ['C', 'E'] +} + +print(bfs(graph, 'A')) # Kết quả: ['A', 'B', 'C', 'D', 'E', 'F'] +``` + +## Tìm Kiếm Theo Chiều Sâu (DFS) + +Tìm kiếm theo chiều sâu là một thuật toán duyệt đồ thị khám phá càng xa càng tốt dọc theo mỗi nhánh trước khi quay lui. + +### Cách Hoạt Động của DFS + +1. Bắt đầu từ một đỉnh nguồn và đánh dấu nó là đã thăm +2. Đệ quy thăm một trong các đỉnh kề chưa thăm của nó +3. Tiếp tục quá trình này, đi sâu hơn vào đồ thị +4. Khi bạn đến một đỉnh không có đỉnh kề chưa thăm, quay lui + +### Cài Đặt + +```python +def dfs_recursive(graph, vertex, visited=None, result=None): + if visited is None: + visited = set() + if result is None: + result = [] + + visited.add(vertex) + result.append(vertex) + + for neighbor in graph[vertex]: + if neighbor not in visited: + dfs_recursive(graph, neighbor, visited, result) + + return result + +# Cài đặt lặp sử dụng ngăn xếp +def dfs_iterative(graph, start): + visited = set() + stack = [start] + result = [] + + while stack: + vertex = stack.pop() + if vertex not in visited: + visited.add(vertex) + result.append(vertex) + + # Thêm các đỉnh kề theo thứ tự ngược lại để mô phỏng DFS đệ quy + for neighbor in reversed(graph[vertex]): + if neighbor not in visited: + stack.append(neighbor) + + return result + +# Ví dụ sử dụng (cùng đồ thị như ví dụ BFS) +print(dfs_recursive(graph, 'A')) # Kết quả có thể là: ['A', 'B', 'D', 'E', 'F', 'C'] +print(dfs_iterative(graph, 'A')) # Kết quả tương tự, có thể thay đổi tùy thuộc vào thứ tự đỉnh kề +``` + +## So Sánh BFS và DFS + +| Khía cạnh | BFS | DFS | +|-----------|-----|-----| +| Cấu trúc dữ liệu | Hàng đợi (Queue) | Ngăn xếp (Stack) hoặc đệ quy | +| Độ phức tạp không gian | O(b^d) với b là hệ số phân nhánh và d là khoảng cách từ nguồn | O(h) với h là chiều cao của cây | +| Tính đầy đủ | Đầy đủ (tìm tất cả các đỉnh ở độ sâu nhất định trước khi đi sâu hơn) | Không đầy đủ cho đồ thị vô hạn | +| Tính tối ưu | Tối ưu cho đồ thị không có trọng số | Không tối ưu nói chung | +| Trường hợp sử dụng | Đường đi ngắn nhất trong đồ thị không trọng số, duyệt theo cấp | Sắp xếp tô-pô, phát hiện chu trình, tìm đường đi | + +## Ứng Dụng + +- **Ứng Dụng của BFS**: + - Tìm đường đi ngắn nhất trong đồ thị không trọng số + - Tìm tất cả các đỉnh trong một thành phần liên thông + - Kiểm tra tính hai phía của đồ thị + - Bộ thu thập thông tin web (Web crawlers) + - Tính năng mạng xã hội (ví dụ: "Bạn bè trong vòng 2 kết nối") + +- **Ứng Dụng của DFS**: + - Sắp xếp tô-pô + - Tìm thành phần liên thông mạnh + - Giải các câu đố chỉ có một lời giải (ví dụ: mê cung) + - Phát hiện chu trình + - Tìm đường đi trong trò chơi và câu đố + +## Độ Phức Tạp Về Thời Gian và Không Gian + +Cả BFS và DFS đều có độ phức tạp thời gian là O(V + E) với V là số đỉnh và E là số cạnh. Điều này là do trong trường hợp xấu nhất, mỗi đỉnh và mỗi cạnh sẽ được khám phá một lần. + +Độ phức tạp không gian: +- BFS: O(V) trong trường hợp xấu nhất khi tất cả các đỉnh được lưu trữ trong hàng đợi +- DFS: O(h) với h là độ sâu tối đa của ngăn xếp đệ quy (có thể là O(V) trong trường hợp xấu nhất) + +## Tài Liệu Tham Khảo + +1. Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction to Algorithms (3rd ed.). MIT Press. +2. Sedgewick, R., & Wayne, K. (2011). Algorithms (4th ed.). Addison-Wesley Professional. \ No newline at end of file diff --git a/i18n/vi/databases/relational_vi.md b/i18n/vi/databases/relational_vi.md new file mode 100644 index 0000000..6647b44 --- /dev/null +++ b/i18n/vi/databases/relational_vi.md @@ -0,0 +1,117 @@ +# Cơ sở dữ liệu quan hệ + +Cơ sở dữ liệu quan hệ là tập hợp dữ liệu có tổ chức lưu trữ thông tin trong các bảng với hàng và cột. Chúng tuân theo mô hình quan hệ do Edgar F. Codd đề xuất vào năm 1970, nhấn mạnh mối quan hệ giữa các thực thể dữ liệu. + +## Khái niệm cốt lõi + +### Bảng (Relations) + +Cấu trúc cơ bản trong cơ sở dữ liệu quan hệ: +- Mỗi **bảng** đại diện cho một loại thực thể (ví dụ: khách hàng, sản phẩm) +- Mỗi **hàng** (tuple) đại diện cho một thể hiện của thực thể đó +- Mỗi **cột** (thuộc tính) đại diện cho một thuộc tính của thực thể đó + +### Khóa + +Khóa thiết lập mối quan hệ và đảm bảo tính toàn vẹn dữ liệu: + +- **Khóa chính (Primary Key)**: Xác định duy nhất mỗi bản ghi trong bảng +- **Khóa ngoại (Foreign Key)**: Tham chiếu đến khóa chính trong bảng khác, thiết lập mối quan hệ +- **Khóa tổng hợp (Composite Key)**: Kết hợp nhiều cột để tạo thành định danh duy nhất +- **Khóa ứng viên (Candidate Key)**: Cột hoặc tập hợp các cột có thể đóng vai trò làm khóa chính + +### Lược đồ (Schema) + +Lược đồ xác định cấu trúc của cơ sở dữ liệu: +- Định nghĩa bảng +- Kiểu dữ liệu và ràng buộc cột +- Mối quan hệ giữa các bảng + +## SQL (Ngôn ngữ truy vấn có cấu trúc) + +SQL là ngôn ngữ tiêu chuẩn để tương tác với cơ sở dữ liệu quan hệ. + +### Các lệnh SQL cơ bản + +```sql +-- Tạo bảng +CREATE TABLE customers ( + customer_id INT PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + signup_date DATE +); + +-- Chèn dữ liệu +INSERT INTO customers (customer_id, name, email, signup_date) +VALUES (1, 'Nguyễn Văn A', 'nguyenvana@example.com', '2023-01-15'); + +-- Truy vấn dữ liệu +SELECT * FROM customers WHERE signup_date > '2023-01-01'; + +-- Cập nhật dữ liệu +UPDATE customers SET email = 'nguyenvana.new@example.com' WHERE customer_id = 1; + +-- Xóa dữ liệu +DELETE FROM customers WHERE customer_id = 1; +``` + +## Chuẩn hóa + +Chuẩn hóa là quá trình tổ chức dữ liệu để giảm dư thừa và cải thiện tính toàn vẹn dữ liệu: + +- **Dạng chuẩn 1 (1NF)**: Loại bỏ các cột trùng lặp và tạo bảng riêng cho dữ liệu liên quan +- **Dạng chuẩn 2 (2NF)**: Đáp ứng yêu cầu 1NF và loại bỏ phụ thuộc một phần +- **Dạng chuẩn 3 (3NF)**: Đáp ứng yêu cầu 2NF và loại bỏ phụ thuộc bắc cầu + +## Tính chất ACID + +Các giao dịch trong cơ sở dữ liệu quan hệ tuân theo tính chất ACID: + +- **Tính nguyên tử (Atomicity)**: Giao dịch là các hoạt động tất cả hoặc không có gì +- **Tính nhất quán (Consistency)**: Giao dịch đưa cơ sở dữ liệu từ trạng thái hợp lệ này sang trạng thái hợp lệ khác +- **Tính cô lập (Isolation)**: Các giao dịch đồng thời không ảnh hưởng đến nhau +- **Tính bền vững (Durability)**: Giao dịch hoàn thành vẫn tồn tại ngay cả khi hệ thống gặp sự cố + +## Các hệ thống cơ sở dữ liệu quan hệ phổ biến + +- **MySQL**: Mã nguồn mở, được sử dụng rộng rãi cho các ứng dụng web +- **PostgreSQL**: Cơ sở dữ liệu mã nguồn mở nâng cao với nhiều tính năng mở rộng +- **Oracle Database**: Cơ sở dữ liệu thương mại cấp doanh nghiệp +- **Microsoft SQL Server**: Giải pháp cơ sở dữ liệu thương mại của Microsoft +- **SQLite**: Động cơ cơ sở dữ liệu nhẹ, không cần máy chủ + +## Chỉ mục (Indexes) + +Chỉ mục tăng tốc các hoạt động truy xuất dữ liệu: +- Tương tự như chỉ mục sách +- Cải thiện hiệu suất truy vấn nhưng tạo thêm chi phí cho các hoạt động ghi +- Các loại bao gồm chỉ mục B-tree, hash, và bitmap + +## Joins (Kết hợp) + +Joins kết hợp các bản ghi từ hai hoặc nhiều bảng: +- **INNER JOIN**: Trả về các bản ghi có giá trị khớp trong cả hai bảng +- **LEFT JOIN**: Trả về tất cả bản ghi từ bảng bên trái và các bản ghi khớp từ bảng bên phải +- **RIGHT JOIN**: Trả về tất cả bản ghi từ bảng bên phải và các bản ghi khớp từ bảng bên trái +- **FULL JOIN**: Trả về tất cả bản ghi khi có sự khớp trong một trong hai bảng + +```sql +SELECT customers.name, orders.order_date +FROM customers +INNER JOIN orders ON customers.customer_id = orders.customer_id; +``` + +## Khi nào sử dụng cơ sở dữ liệu quan hệ + +Cơ sở dữ liệu quan hệ lý tưởng cho: +- Dữ liệu có cấu trúc với mối quan hệ rõ ràng +- Ứng dụng yêu cầu truy vấn phức tạp và giao dịch +- Hệ thống mà tính toàn vẹn dữ liệu là rất quan trọng +- Các tình huống mà tính nhất quán quan trọng hơn tốc độ + +## Tài liệu tham khảo + +- Codd, E.F. (1970). "A Relational Model of Data for Large Shared Data Banks" +- Date, C.J. "An Introduction to Database Systems" +- Garcia-Molina, H., Ullman, J.D., & Widom, J. "Database Systems: The Complete Book" \ No newline at end of file diff --git a/i18n/vi/design-patterns/factory_vi.md b/i18n/vi/design-patterns/factory_vi.md new file mode 100644 index 0000000..63ec969 --- /dev/null +++ b/i18n/vi/design-patterns/factory_vi.md @@ -0,0 +1,476 @@ +# Mẫu thiết kế Factory + +Mẫu Factory là một mẫu thiết kế tạo đối tượng cung cấp giao diện để tạo đối tượng trong lớp cha, nhưng cho phép các lớp con thay đổi loại đối tượng sẽ được tạo. + +## Mục đích + +- Tạo đối tượng mà không để lộ logic khởi tạo cho client +- Tham chiếu đến các đối tượng mới tạo thông qua một giao diện chung +- Tách biệt việc triển khai đối tượng khỏi việc sử dụng nó + +## Vấn đề + +Khi nào bạn nên sử dụng mẫu Factory? + +- Khi một lớp không thể dự đoán trước loại đối tượng mà nó cần tạo +- Khi một lớp muốn các lớp con của nó chỉ định đối tượng mà nó tạo ra +- Khi bạn muốn tập trung kiến thức về việc lớp nào được tạo + +## Các loại mẫu Factory + +Có một số biến thể của mẫu Factory: + +1. **Simple Factory** - Không phải là một mẫu chính thức, nhưng là một cách đơn giản để tách biệt việc tạo đối tượng +2. **Factory Method** - Định nghĩa một giao diện để tạo đối tượng, nhưng để các lớp con quyết định lớp nào sẽ được khởi tạo +3. **Abstract Factory** - Cung cấp một giao diện để tạo ra các họ đối tượng liên quan hoặc phụ thuộc + +## Cấu trúc + +### Mẫu Factory Method + +![Cấu trúc mẫu Factory Method](https://refactoring.guru/images/patterns/diagrams/factory-method/structure.png) + +### Mẫu Abstract Factory + +![Cấu trúc mẫu Abstract Factory](https://refactoring.guru/images/patterns/diagrams/abstract-factory/structure.png) + +## Triển khai + +### Ví dụ về Simple Factory + +```java +// Giao diện Product +interface Product { + void operation(); +} + +// Các sản phẩm cụ thể +class ConcreteProductA implements Product { + @Override + public void operation() { + System.out.println("Thao tác của ConcreteProductA"); + } +} + +class ConcreteProductB implements Product { + @Override + public void operation() { + System.out.println("Thao tác của ConcreteProductB"); + } +} + +// Simple factory +class SimpleFactory { + public Product createProduct(String type) { + if (type.equals("A")) { + return new ConcreteProductA(); + } else if (type.equals("B")) { + return new ConcreteProductB(); + } + throw new IllegalArgumentException("Loại sản phẩm không hợp lệ: " + type); + } +} + +// Client code +class Client { + public static void main(String[] args) { + SimpleFactory factory = new SimpleFactory(); + + Product productA = factory.createProduct("A"); + productA.operation(); + + Product productB = factory.createProduct("B"); + productB.operation(); + } +} +``` + +### Ví dụ về Factory Method + +```java +// Giao diện Product +interface Product { + void operation(); +} + +// Các sản phẩm cụ thể +class ConcreteProductA implements Product { + @Override + public void operation() { + System.out.println("Thao tác của ConcreteProductA"); + } +} + +class ConcreteProductB implements Product { + @Override + public void operation() { + System.out.println("Thao tác của ConcreteProductB"); + } +} + +// Lớp trừu tượng Creator với factory method +abstract class Creator { + public abstract Product createProduct(); + + // Creator cũng có thể bao gồm một số logic nghiệp vụ + public void someOperation() { + // Gọi factory method để tạo một đối tượng Product + Product product = createProduct(); + // Sử dụng sản phẩm + product.operation(); + } +} + +// Các creator cụ thể ghi đè factory method +class ConcreteCreatorA extends Creator { + @Override + public Product createProduct() { + return new ConcreteProductA(); + } +} + +class ConcreteCreatorB extends Creator { + @Override + public Product createProduct() { + return new ConcreteProductB(); + } +} + +// Client code +class Client { + public static void main(String[] args) { + Creator creatorA = new ConcreteCreatorA(); + creatorA.someOperation(); + + Creator creatorB = new ConcreteCreatorB(); + creatorB.someOperation(); + } +} +``` + +### Ví dụ về Abstract Factory + +```java +// Các sản phẩm trừu tượng +interface ProductA { + void operationA(); +} + +interface ProductB { + void operationB(); +} + +// Các sản phẩm cụ thể cho họ 1 +class ConcreteProductA1 implements ProductA { + @Override + public void operationA() { + System.out.println("Thao tác của sản phẩm A1"); + } +} + +class ConcreteProductB1 implements ProductB { + @Override + public void operationB() { + System.out.println("Thao tác của sản phẩm B1"); + } +} + +// Các sản phẩm cụ thể cho họ 2 +class ConcreteProductA2 implements ProductA { + @Override + public void operationA() { + System.out.println("Thao tác của sản phẩm A2"); + } +} + +class ConcreteProductB2 implements ProductB { + @Override + public void operationB() { + System.out.println("Thao tác của sản phẩm B2"); + } +} + +// Giao diện Abstract factory +interface AbstractFactory { + ProductA createProductA(); + ProductB createProductB(); +} + +// Các factory cụ thể +class ConcreteFactory1 implements AbstractFactory { + @Override + public ProductA createProductA() { + return new ConcreteProductA1(); + } + + @Override + public ProductB createProductB() { + return new ConcreteProductB1(); + } +} + +class ConcreteFactory2 implements AbstractFactory { + @Override + public ProductA createProductA() { + return new ConcreteProductA2(); + } + + @Override + public ProductB createProductB() { + return new ConcreteProductB2(); + } +} + +// Client code +class Client { + private ProductA productA; + private ProductB productB; + + public Client(AbstractFactory factory) { + productA = factory.createProductA(); + productB = factory.createProductB(); + } + + public void executeOperations() { + productA.operationA(); + productB.operationB(); + } +} +``` + +## Ví dụ trong các ngôn ngữ khác nhau + +### JavaScript + +```javascript +// Factory Method trong JavaScript + +// Giao diện Product ngầm định trong JavaScript +class Dog { + speak() { + return "Gâu gâu!"; + } +} + +class Cat { + speak() { + return "Meo meo!"; + } +} + +// Creator +class AnimalFactory { + // Factory method + createAnimal(type) { + switch(type) { + case 'dog': + return new Dog(); + case 'cat': + return new Cat(); + default: + throw new Error(`Loại động vật ${type} không được hỗ trợ.`); + } + } +} + +// Sử dụng +const factory = new AnimalFactory(); +const dog = factory.createAnimal('dog'); +const cat = factory.createAnimal('cat'); + +console.log(dog.speak()); // Đầu ra: Gâu gâu! +console.log(cat.speak()); // Đầu ra: Meo meo! +``` + +### Python + +```python +from abc import ABC, abstractmethod + +# Abstract Product +class Button(ABC): + @abstractmethod + def render(self): + pass + + @abstractmethod + def on_click(self): + pass + +# Concrete Products +class HTMLButton(Button): + def render(self): + return "" + + def on_click(self): + return "Nút HTML đã được nhấp!" + +class WindowsButton(Button): + def render(self): + return "Nút Windows" + + def on_click(self): + return "Nút Windows đã được nhấp!" + +# Abstract Creator +class Dialog(ABC): + @abstractmethod + def create_button(self) -> Button: + pass + + def render(self): + # Gọi factory method để tạo đối tượng button + button = self.create_button() + # Bây giờ sử dụng sản phẩm + return f"Dialog hiển thị với {button.render()}" + +# Concrete Creators +class HTMLDialog(Dialog): + def create_button(self) -> Button: + return HTMLButton() + +class WindowsDialog(Dialog): + def create_button(self) -> Button: + return WindowsButton() + +# Client code +def client_code(dialog: Dialog): + print(dialog.render()) + +# Dựa vào môi trường, chúng ta chọn dialog phù hợp +import sys +if sys.platform.startswith('win'): + dialog = WindowsDialog() +else: + dialog = HTMLDialog() + +client_code(dialog) +``` + +### C# + +```csharp +using System; + +// Abstract Product +public interface IVehicle +{ + void Drive(); +} + +// Concrete Products +public class Car : IVehicle +{ + public void Drive() + { + Console.WriteLine("Đang lái xe ô tô..."); + } +} + +public class Motorcycle : IVehicle +{ + public void Drive() + { + Console.WriteLine("Đang lái xe máy..."); + } +} + +// Abstract Creator +public abstract class VehicleFactory +{ + // Factory Method + public abstract IVehicle CreateVehicle(); + + public void DeliverVehicle() + { + IVehicle vehicle = CreateVehicle(); + Console.WriteLine("Đang giao phương tiện..."); + vehicle.Drive(); + } +} + +// Concrete Creators +public class CarFactory : VehicleFactory +{ + public override IVehicle CreateVehicle() + { + return new Car(); + } +} + +public class MotorcycleFactory : VehicleFactory +{ + public override IVehicle CreateVehicle() + { + return new Motorcycle(); + } +} + +// Client code +public class Program +{ + public static void Main() + { + VehicleFactory factory = GetFactory("car"); + factory.DeliverVehicle(); + + factory = GetFactory("motorcycle"); + factory.DeliverVehicle(); + } + + private static VehicleFactory GetFactory(string vehicleType) + { + switch (vehicleType.ToLower()) + { + case "car": + return new CarFactory(); + case "motorcycle": + return new MotorcycleFactory(); + default: + throw new ArgumentException($"Loại phương tiện {vehicleType} không được hỗ trợ."); + } + } +} +``` + +## Trường hợp sử dụng + +- **Tạo thành phần UI**: Tạo các thành phần UI khác nhau dựa trên tùy chọn của người dùng hoặc nền tảng +- **Kết nối cơ sở dữ liệu**: Tạo kết nối cơ sở dữ liệu phù hợp dựa trên cấu hình +- **Tạo tài liệu**: Tạo các loại tài liệu khác nhau (PDF, Word, v.v.) +- **Sản xuất phương tiện**: Tạo các loại phương tiện khác nhau trong một mô phỏng +- **Xử lý thanh toán**: Tạo các phương thức thanh toán khác nhau trong ứng dụng thương mại điện tử + +## Ưu và nhược điểm + +### Ưu điểm + +- Tránh sự kết hợp chặt chẽ giữa creator và các sản phẩm cụ thể +- Nguyên tắc trách nhiệm đơn lẻ: Di chuyển mã tạo sản phẩm đến một nơi +- Nguyên tắc mở/đóng: Các sản phẩm mới có thể được thêm vào mà không phá vỡ mã hiện có +- Tạo đối tượng theo yêu cầu, thay vì tại thời điểm khởi tạo + +### Nhược điểm + +- Mã có thể trở nên phức tạp hơn do việc giới thiệu nhiều lớp con mới +- Client có thể bị giới hạn về các sản phẩm được hiển thị bởi giao diện factory + +## Mối quan hệ với các mẫu khác + +- Các lớp **Abstract Factory** thường được triển khai với Factory Methods +- **Factory Methods** thường được sử dụng trong Template Methods +- **Prototype** có thể là một giải pháp thay thế cho Factory khi mục tiêu là giảm việc tạo lớp con +- **Builder** tập trung vào việc xây dựng các đối tượng phức tạp từng bước một, trong khi Factory Method là một lệnh gọi duy nhất + +## Ví dụ thực tế + +- `Calendar.getInstance()` trong Java +- Các factory widget trong các framework UI +- Các factory kết nối cơ sở dữ liệu +- Các trình tạo tài liệu trong bộ ứng dụng văn phòng + +## Tài liệu tham khảo + +- "Design Patterns: Elements of Reusable Object-Oriented Software" của Gang of Four (GoF) +- [Refactoring Guru - Mẫu Factory Method](https://refactoring.guru/design-patterns/factory-method) +- [Refactoring Guru - Mẫu Abstract Factory](https://refactoring.guru/design-patterns/abstract-factory) \ No newline at end of file diff --git a/i18n/vi/design-patterns/observer_vi.md b/i18n/vi/design-patterns/observer_vi.md new file mode 100644 index 0000000..69155cc --- /dev/null +++ b/i18n/vi/design-patterns/observer_vi.md @@ -0,0 +1,404 @@ +# Mẫu thiết kế Observer + +Mẫu Observer là một mẫu thiết kế hành vi trong đó một đối tượng, gọi là chủ thể (subject), duy trì danh sách các phụ thuộc của nó, gọi là Observer, và thông báo cho họ tự động về bất kỳ thay đổi trạng thái nào, thường bằng cách gọi một trong các phương thức của họ. + +## Mục đích + +- Định nghĩa một mối quan hệ một-nhiều giữa các đối tượng để khi một đối tượng thay đổi trạng thái, tất cả các đối tượng phụ thuộc của nó được thông báo và cập nhật tự động. +- Đóng gói các thành phần cốt lõi trong một lớp Subject trừu tượng, và các thành phần biến đổi trong một phân cấp Observer. +- Các lớp Subject và Observer có thể thay đổi độc lập với nhau. + +## Vấn đề + +Trong nhiều ứng dụng, một số loại đối tượng cụ thể cần được thông báo về những thay đổi trong các đối tượng khác. Tuy nhiên, chúng ta không muốn kết nối quá chặt chẽ các loại đối tượng khác nhau này để duy trì tính linh hoạt và khả năng tái sử dụng. + +Bạn cần một cách để một đối tượng thông báo cho một số lượng không xác định trước các đối tượng khác về những thay đổi, mà không cần các đối tượng đó kết nối chặt chẽ với nhau. + +## Cấu trúc + +![Cấu trúc mẫu Observer](https://refactoring.guru/images/patterns/diagrams/observer/structure.png) + +- **Subject (Chủ thể)**: Giao diện hoặc lớp trừu tượng định nghĩa các hoạt động để gắn, tách, và thông báo cho Observer. +- **ConcreteSubject (Chủ thể cụ thể)**: Duy trì trạng thái mà Observer quan tâm và gửi thông báo khi trạng thái thay đổi. +- **Observer (Observer)**: Giao diện hoặc lớp trừu tượng với phương thức cập nhật được gọi khi trạng thái của chủ thể thay đổi. +- **ConcreteObserver (Observer cụ thể)**: Triển khai giao diện Observer để giữ trạng thái của nó nhất quán với trạng thái của chủ thể. + +## Triển khai + +### Triển khai cơ bản + +```java +// Giao diện Observer +interface Observer { + void update(Subject subject); +} + +// Giao diện Subject +interface Subject { + void attach(Observer observer); + void detach(Observer observer); + void notifyObservers(); +} + +// ConcreteSubject +class ConcreteSubject implements Subject { + private List observers = new ArrayList<>(); + private int state; + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + notifyObservers(); + } + + @Override + public void attach(Observer observer) { + observers.add(observer); + } + + @Override + public void detach(Observer observer) { + observers.remove(observer); + } + + @Override + public void notifyObservers() { + for (Observer observer : observers) { + observer.update(this); + } + } +} + +// ConcreteObserver +class ConcreteObserver implements Observer { + private int observerState; + + @Override + public void update(Subject subject) { + if (subject instanceof ConcreteSubject) { + observerState = ((ConcreteSubject) subject).getState(); + System.out.println("Trạng thái Observer được cập nhật thành: " + observerState); + } + } +} +``` + +### Mô hình Push và Pull + +#### Mô hình Push + +Trong mô hình push, Chủ thể gửi thông tin chi tiết về thay đổi đến tất cả Observer, bất kể họ có cần hay không: + +```java +// Trong ConcreteSubject +public void notifyObservers(int state) { + for (Observer observer : observers) { + observer.update(state); + } +} + +// Trong giao diện Observer +void update(int state); +``` + +#### Mô hình Pull + +Trong mô hình pull, Chủ thể chỉ đơn giản thông báo cho Observer rằng có thay đổi xảy ra, và Observer chịu trách nhiệm lấy dữ liệu cần thiết: + +```java +// Trong ConcreteSubject +public void notifyObservers() { + for (Observer observer : observers) { + observer.update(this); + } +} + +// Trong giao diện Observer +void update(Subject subject); +``` + +## Ví dụ trong các ngôn ngữ khác nhau + +### JavaScript + +```javascript +// Sử dụng lớp ES6 +class Subject { + constructor() { + this.observers = []; + } + + attach(observer) { + if (!this.observers.includes(observer)) { + this.observers.push(observer); + } + } + + detach(observer) { + const index = this.observers.indexOf(observer); + if (index !== -1) { + this.observers.splice(index, 1); + } + } + + notify() { + for (const observer of this.observers) { + observer.update(this); + } + } +} + +class WeatherStation extends Subject { + constructor() { + super(); + this.temperature = 0; + this.humidity = 0; + } + + setMeasurements(temperature, humidity) { + this.temperature = temperature; + this.humidity = humidity; + this.notify(); + } + + getTemperature() { + return this.temperature; + } + + getHumidity() { + return this.humidity; + } +} + +class Observer { + update(subject) {} +} + +class DisplayDevice extends Observer { + constructor(name) { + super(); + this.name = name; + } + + update(weatherStation) { + console.log(`Màn hình ${this.name}: Nhiệt độ ${weatherStation.getTemperature()}°C, Độ ẩm ${weatherStation.getHumidity()}%`); + } +} + +// Cách sử dụng +const weatherStation = new WeatherStation(); +const phoneDisplay = new DisplayDevice('Điện thoại'); +const laptopDisplay = new DisplayDevice('Laptop'); + +weatherStation.attach(phoneDisplay); +weatherStation.attach(laptopDisplay); + +weatherStation.setMeasurements(25, 60); // Cả hai màn hình cập nhật +weatherStation.detach(laptopDisplay); +weatherStation.setMeasurements(26, 70); // Chỉ màn hình điện thoại cập nhật +``` + +### Python + +```python +from abc import ABC, abstractmethod + +# Giao diện Observer +class Observer(ABC): + @abstractmethod + def update(self, subject): + pass + +# Giao diện Subject +class Subject(ABC): + @abstractmethod + def attach(self, observer): + pass + + @abstractmethod + def detach(self, observer): + pass + + @abstractmethod + def notify(self): + pass + +# ConcreteSubject +class NewsPublisher(Subject): + def __init__(self): + self._observers = [] + self._latest_news = None + + def attach(self, observer): + self._observers.append(observer) + + def detach(self, observer): + self._observers.remove(observer) + + def notify(self): + for observer in self._observers: + observer.update(self) + + def add_news(self, news): + self._latest_news = news + self.notify() + + @property + def latest_news(self): + return self._latest_news + +# ConcreteObserver +class NewsSubscriber(Observer): + def __init__(self, name): + self._name = name + + def update(self, subject): + print(f"{self._name} nhận được tin tức: {subject.latest_news}") + +# Cách sử dụng +if __name__ == "__main__": + publisher = NewsPublisher() + + subscriber1 = NewsSubscriber("Người đăng ký 1") + subscriber2 = NewsSubscriber("Người đăng ký 2") + + publisher.attach(subscriber1) + publisher.attach(subscriber2) + + publisher.add_news("Tin nóng: Mẫu Observer đang hoạt động!") + + publisher.detach(subscriber1) + + publisher.add_news("Cập nhật mới: Người đăng ký 1 đã hủy đăng ký!") +``` + +### C# + +```csharp +using System; +using System.Collections.Generic; + +// Giao diện Observer +public interface IObserver +{ + void Update(ISubject subject); +} + +// Giao diện Subject +public interface ISubject +{ + void Attach(IObserver observer); + void Detach(IObserver observer); + void Notify(); +} + +// ConcreteSubject +public class StockMarket : ISubject +{ + private List _observers = new List(); + private Dictionary _stocks = new Dictionary(); + + public void Attach(IObserver observer) + { + Console.WriteLine("Thị trường chứng khoán: Đã gắn một Observer."); + _observers.Add(observer); + } + + public void Detach(IObserver observer) + { + _observers.Remove(observer); + Console.WriteLine("Thị trường chứng khoán: Đã tách một Observer."); + } + + public void Notify() + { + Console.WriteLine("Thị trường chứng khoán: Đang thông báo cho Observer..."); + + foreach (var observer in _observers) + { + observer.Update(this); + } + } + + public void UpdateStockPrice(string stockSymbol, double price) + { + Console.WriteLine($"Thị trường chứng khoán: Giá {stockSymbol} cập nhật thành {price}"); + _stocks[stockSymbol] = price; + Notify(); + } + + public Dictionary GetStocks() + { + return _stocks; + } +} + +// ConcreteObserver +public class Investor : IObserver +{ + private string _name; + private Dictionary _watchlist = new Dictionary(); + + public Investor(string name) + { + _name = name; + } + + public void Update(ISubject subject) + { + if (subject is StockMarket stockMarket) + { + var stocks = stockMarket.GetStocks(); + foreach (var stock in stocks) + { + if (_watchlist.ContainsKey(stock.Key) && _watchlist[stock.Key] != stock.Value) + { + Console.WriteLine($"{_name}: Nhận thấy giá {stock.Key} thay đổi từ {_watchlist[stock.Key]} thành {stock.Value}"); + } + _watchlist[stock.Key] = stock.Value; + } + } + } +} +``` + +## Trường hợp sử dụng thực tế + +1. **Hệ thống xử lý sự kiện**: Các framework UI sử dụng mẫu Observer để xử lý hành động của người dùng. +2. **Dịch vụ đăng ký tin tức**: Người dùng đăng ký các chủ đề và nhận cập nhật. +3. **Theo dõi thị trường chứng khoán**: Nhà đầu tư theo dõi thay đổi giá cổ phiếu. +4. **Thông báo mạng xã hội**: Người dùng nhận thông báo về các hoạt động liên quan đến tài khoản của họ. +5. **Hệ thống hàng đợi tin nhắn**: Nhà xuất bản gửi tin nhắn đến người tiêu dùng đã đăng ký. +6. **Hệ thống giám sát**: Ứng dụng giám sát tài nguyên hệ thống hoặc dịch vụ. + +## Ưu và nhược điểm + +### Ưu điểm + +- **Nguyên tắc Mở/Đóng**: Bạn có thể giới thiệu các lớp người đăng ký mới mà không cần thay đổi mã của nhà xuất bản. +- **Liên kết lỏng lẻo**: Nhà xuất bản không cần biết bất cứ điều gì về người đăng ký. +- **Mối quan hệ động**: Mối quan hệ giữa nhà xuất bản và người đăng ký có thể được thiết lập trong thời gian chạy. +- **Xử lý sự kiện**: Hiệu quả cho việc triển khai hệ thống xử lý sự kiện. + +### Nhược điểm + +- **Cập nhật không mong đợi**: Người đăng ký có thể được thông báo theo thứ tự không thể dự đoán. +- **Rò rỉ bộ nhớ**: Nếu Observer quên hủy đăng ký, họ có thể không được thu gom rác. +- **Chi phí hiệu suất**: Thông báo có thể tốn kém nếu có nhiều Observer hoặc thay đổi trạng thái thường xuyên. +- **Độ phức tạp**: Gỡ lỗi có thể gặp thách thức vì luồng điều khiển ít rõ ràng hơn. + +## Mối quan hệ với các mẫu khác + +- **Mediator (Người trung gian)**: Trong khi Observer phân phối giao tiếp bằng cách giới thiệu đối tượng người đăng ký và nhà xuất bản, Mediator đóng gói giao tiếp giữa các đối tượng. +- **Command (Lệnh)**: Commands có thể được sử dụng để triển khai mẫu Observer bằng cách chuyển yêu cầu thành đối tượng. +- **Memento (Bản ghi nhớ)**: Có thể được sử dụng với Observer để hoàn tác các hoạt động sau khi thông báo cho Observer về các thay đổi. +- **Mẫu MVC**: Mẫu Observer thường được sử dụng trong kiến trúc MVC nơi View quan sát các thay đổi trong Model. + +## Tài liệu tham khảo + +- "Design Patterns: Elements of Reusable Object-Oriented Software" của Gang of Four (GoF) +- [Refactoring Guru - Observer Pattern](https://refactoring.guru/design-patterns/observer) +- [SourceMaking - Observer Pattern](https://sourcemaking.com/design_patterns/observer) \ No newline at end of file diff --git a/i18n/vi/design-patterns/singleton_vi.md b/i18n/vi/design-patterns/singleton_vi.md new file mode 100644 index 0000000..27dd4c8 --- /dev/null +++ b/i18n/vi/design-patterns/singleton_vi.md @@ -0,0 +1,226 @@ +# Mẫu thiết kế Singleton + +Singleton là một mẫu thiết kế tạo đối tượng đảm bảo rằng một lớp chỉ có một thể hiện duy nhất và cung cấp một điểm truy cập toàn cục đến thể hiện đó. + +## Mục đích + +- Đảm bảo một lớp chỉ có một thể hiện duy nhất. +- Cung cấp một điểm truy cập toàn cục đến thể hiện đó. +- Kiểm soát truy cập đồng thời đến tài nguyên được chia sẻ. + +## Vấn đề + +Khi nào bạn nên sử dụng mẫu Singleton? + +- Khi bạn cần chính xác một thể hiện của lớp để điều phối các hành động trong hệ thống +- Khi bạn muốn hạn chế việc khởi tạo của một lớp thành chỉ một đối tượng +- Khi bạn cần kiểm soát chặt chẽ hơn đối với biến toàn cục + +## Cấu trúc + +``` ++----------------+ +| Singleton | ++----------------+ +| -instance | ++----------------+ +| +getInstance() | +| -constructor() | ++----------------+ +``` + +![Cấu trúc mẫu Singleton](https://refactoring.guru/images/patterns/diagrams/singleton/structure-en.png) + +## Triển khai + +### Triển khai cơ bản + +```java +public class Singleton { + // Thể hiện tĩnh riêng tư của lớp + private static Singleton instance; + + // Constructor riêng tư ngăn việc khởi tạo từ các lớp khác + private Singleton() { } + + // Phương thức tĩnh công khai để lấy thể hiện + public static Singleton getInstance() { + if (instance == null) { + instance = new Singleton(); + } + return instance; + } + + // Các phương thức và trường khác + public void doSomething() { + System.out.println("Singleton đang thực hiện một việc gì đó"); + } +} +``` + +### Triển khai an toàn với đa luồng + +```java +public class ThreadSafeSingleton { + private static volatile ThreadSafeSingleton instance; + + private ThreadSafeSingleton() { } + + public static ThreadSafeSingleton getInstance() { + // Kiểm tra khóa hai lần (Double-checked locking) + if (instance == null) { + synchronized (ThreadSafeSingleton.class) { + if (instance == null) { + instance = new ThreadSafeSingleton(); + } + } + } + return instance; + } +} +``` + +### Khởi tạo sớm + +```java +public class EagerSingleton { + // Thể hiện được tạo tại thời điểm tải + private static final EagerSingleton INSTANCE = new EagerSingleton(); + + private EagerSingleton() { } + + public static EagerSingleton getInstance() { + return INSTANCE; + } +} +``` + +### Sử dụng Enum (Java) + +```java +public enum EnumSingleton { + INSTANCE; + + public void doSomething() { + System.out.println("Singleton enum đang thực hiện một việc gì đó"); + } +} +``` + +## Ví dụ trong các ngôn ngữ khác nhau + +### JavaScript + +```javascript +class Singleton { + constructor() { + if (Singleton.instance) { + return Singleton.instance; + } + + // Khởi tạo singleton + this.data = []; + Singleton.instance = this; + } + + add(item) { + this.data.push(item); + } + + get(index) { + return this.data[index]; + } +} + +// Sử dụng +const instance1 = new Singleton(); +const instance2 = new Singleton(); +console.log(instance1 === instance2); // true +``` + +### Python + +```python +class Singleton: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(Singleton, cls).__new__(cls) + # Khởi tạo singleton của bạn tại đây + cls._instance.value = 0 + return cls._instance + +# Sử dụng +s1 = Singleton() +s2 = Singleton() +print(s1 is s2) # True +``` + +### C# + +```csharp +public sealed class Singleton +{ + private static Singleton instance = null; + private static readonly object padlock = new object(); + + Singleton() {} + + public static Singleton Instance + { + get + { + lock(padlock) + { + if (instance == null) + { + instance = new Singleton(); + } + return instance; + } + } + } +} +``` + +## Trường hợp sử dụng + +- **Kết nối cơ sở dữ liệu**: Quản lý một pool kết nối +- **Logger**: Tạo một thể hiện ghi log duy nhất cho ứng dụng +- **Cài đặt cấu hình**: Lưu trữ cài đặt ứng dụng +- **Cache**: Tạo một trình quản lý cache duy nhất +- **Thread pools**: Quản lý việc tạo và gán thread + +## Ưu và nhược điểm + +### Ưu điểm + +- Đảm bảo một lớp chỉ có một thể hiện duy nhất +- Cung cấp một điểm truy cập toàn cục đến thể hiện đó +- Đối tượng singleton chỉ được khởi tạo khi nó được yêu cầu lần đầu tiên + +### Nhược điểm + +- Vi phạm Nguyên lý trách nhiệm đơn lẻ (lớp quản lý việc tạo ra chính nó) +- Có thể che giấu thiết kế không tốt, ví dụ, khi các thành phần biết quá nhiều về nhau +- Yêu cầu xử lý đặc biệt trong môi trường đa luồng +- Làm cho việc kiểm thử đơn vị khó khăn hơn + +## Mối quan hệ với các mẫu khác + +- Một **Facade** có thể trông giống như một Singleton nếu nó chỉ ẩn một đối tượng, nhưng chúng có mục đích khác nhau +- **Abstract Factories**, **Builders**, và **Prototypes** đều có thể được triển khai như Singletons + +## Ví dụ thực tế + +- Lớp `java.lang.Runtime` trong Java +- Các trình quản lý UI trong nhiều framework GUI +- Windows Registry +- Đối tượng window trong trình duyệt + +## Tài liệu tham khảo + +- "Design Patterns: Elements of Reusable Object-Oriented Software" của Gang of Four (GoF) +- [Refactoring Guru - Singleton Pattern](https://refactoring.guru/design-patterns/singleton) +- [SourceMaking - Singleton Pattern](https://sourcemaking.com/design_patterns/singleton) \ No newline at end of file diff --git a/i18n/vi/devops/ci-cd_vi.md b/i18n/vi/devops/ci-cd_vi.md new file mode 100644 index 0000000..8b9dd04 --- /dev/null +++ b/i18n/vi/devops/ci-cd_vi.md @@ -0,0 +1,162 @@ +# Tích Hợp Liên Tục và Triển Khai Liên Tục (CI/CD) + +Tích hợp liên tục và triển khai liên tục (CI/CD) là một phương pháp để thường xuyên cung cấp ứng dụng cho khách hàng bằng cách đưa tự động hóa vào các giai đoạn phát triển ứng dụng. + +## CI/CD là gì? + +### Tích Hợp Liên Tục (CI) +Tích hợp liên tục là một phương pháp phát triển trong đó các nhà phát triển tích hợp mã vào kho lưu trữ chung thường xuyên, tốt nhất là nhiều lần mỗi ngày. Mỗi lần tích hợp sau đó được xác minh bằng một bản dựng tự động và các bài kiểm tra tự động. + +### Phân Phối Liên Tục (CD) +Phân phối liên tục là một phần mở rộng của tích hợp liên tục để đảm bảo rằng bạn có thể phát hành các thay đổi mới cho khách hàng một cách nhanh chóng và bền vững. Điều này có nghĩa là ngoài việc tự động hóa kiểm tra, bạn cũng đã tự động hóa quy trình phát hành và bạn có thể triển khai ứng dụng của mình bất kỳ lúc nào bằng cách nhấn nút. + +### Triển Khai Liên Tục +Triển khai liên tục tiến xa hơn một bước so với phân phối liên tục. Với phương pháp này, mọi thay đổi vượt qua tất cả các giai đoạn của đường ống sản xuất của bạn đều được phát hành cho khách hàng của bạn. Không có sự can thiệp của con người, và chỉ có một bài kiểm tra thất bại mới ngăn chặn một thay đổi mới được triển khai lên môi trường sản xuất. + +## Các Thành Phần của Đường Ống CI/CD + +Một đường ống CI/CD điển hình bao gồm các giai đoạn sau: + +1. **Nguồn**: Mã được commit vào hệ thống quản lý phiên bản (Git, SVN, v.v.) +2. **Xây Dựng**: Mã được biên dịch, các phụ thuộc được giải quyết +3. **Kiểm Tra**: Các bài kiểm tra tự động được chạy (kiểm tra đơn vị, kiểm tra tích hợp, v.v.) +4. **Triển Khai**: Ứng dụng được triển khai lên môi trường dàn dựng/sản xuất +5. **Giám Sát**: Hiệu suất ứng dụng và lỗi được giám sát + +## Các Công Cụ CI/CD Phổ Biến + +### Jenkins + +Jenkins là một máy chủ tự động hóa mã nguồn mở cho phép các nhà phát triển xây dựng, kiểm tra và triển khai phần mềm của họ. + +```yaml +# Ví dụ Jenkinsfile +pipeline { + agent any + + stages { + stage('Xây Dựng') { + steps { + echo 'Đang xây dựng..' + sh 'npm install' + } + } + stage('Kiểm Tra') { + steps { + echo 'Đang kiểm tra..' + sh 'npm test' + } + } + stage('Triển Khai') { + steps { + echo 'Đang triển khai....' + sh 'npm run deploy' + } + } + } +} +``` + +### GitHub Actions + +GitHub Actions là một nền tảng CI/CD cho phép bạn tự động hóa quy trình xây dựng, kiểm tra và triển khai trực tiếp từ GitHub. + +```yaml +# Ví dụ quy trình làm việc GitHub Actions +name: Node.js CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Sử dụng Node.js + uses: actions/setup-node@v1 + with: + node-version: '14.x' + - name: Cài đặt phụ thuộc + run: npm ci + - name: Chạy kiểm tra + run: npm test + - name: Triển khai + if: github.ref == 'refs/heads/main' + run: npm run deploy +``` + +### GitLab CI/CD + +GitLab CI/CD là một phần của GitLab cho phép bạn áp dụng tất cả các phương pháp liên tục (Tích hợp, Phân phối và Triển khai liên tục) vào phần mềm của bạn. + +```yaml +# Ví dụ .gitlab-ci.yml +stages: + - build + - test + - deploy + +build: + stage: build + script: + - echo "Đang xây dựng ứng dụng" + - npm install + +test: + stage: test + script: + - echo "Đang chạy kiểm tra" + - npm test + +deploy: + stage: deploy + script: + - echo "Đang triển khai ứng dụng" + - npm run deploy + only: + - main +``` + +### CircleCI + +CircleCI là một công cụ CI/CD dựa trên đám mây tự động hóa quy trình phát triển phần mềm. + +```yaml +# Ví dụ cấu hình CircleCI +version: 2.1 +jobs: + build: + docker: + - image: cimg/node:14.17 + steps: + - checkout + - run: npm install + - run: npm test + - run: npm run deploy +``` + +## Các Phương Pháp Tốt Nhất cho CI/CD + +1. **Tự Động Hóa Mọi Thứ**: Tự động hóa càng nhiều quy trình phân phối phần mềm càng tốt. +2. **Thất Bại Nhanh Chóng**: Phát hiện và giải quyết vấn đề càng sớm càng tốt trong quy trình phát triển. +3. **Giữ Bản Dựng Xanh**: Một bản dựng bị hỏng nên là ưu tiên cao nhất của nhóm để sửa chữa. +4. **Xây Dựng Chỉ Một Lần**: Xây dựng các tạo phẩm một lần và thăng cấp cùng một tạo phẩm qua đường ống. +5. **Triển Khai Theo Cùng Một Cách cho Mọi Môi Trường**: Sử dụng cùng một quy trình triển khai cho tất cả các môi trường. +6. **Kiểm Tra Khói cho Các Triển Khai**: Chạy các bài kiểm tra cơ bản sau khi triển khai để xác minh hệ thống đang chạy đúng. +7. **Giữ Đường Ống CI/CD Nhanh**: Hướng tới một đường ống hoàn thành trong vòng chưa đến 10 phút. +8. **Duy Trì Độ Phủ Kiểm Tra Tốt**: Đảm bảo các bài kiểm tra của bạn bao phủ phần lớn mã nguồn của bạn. + +## Tài Liệu Tham Khảo + +- [Martin Fowler về Tích Hợp Liên Tục](https://martinfowler.com/articles/continuousIntegration.html) +- [Sổ Tay DevOps](https://itrevolution.com/book/the-devops-handbook/) +- [Phân Phối Liên Tục](https://continuousdelivery.com/) +- [Tài Liệu Jenkins](https://www.jenkins.io/doc/) +- [Tài Liệu GitHub Actions](https://docs.github.com/en/actions) +- [Tài Liệu GitLab CI/CD](https://docs.gitlab.com/ee/ci/) +- [Tài Liệu CircleCI](https://circleci.com/docs/) diff --git a/i18n/vi/linux/bash-scripting_vi.md b/i18n/vi/linux/bash-scripting_vi.md new file mode 100644 index 0000000..329f3ff --- /dev/null +++ b/i18n/vi/linux/bash-scripting_vi.md @@ -0,0 +1,325 @@ +# Lập Trình Bash + +Bash (Bourne Again SHell) là một ngôn ngữ thông dịch lệnh được sử dụng rộng rãi trên nhiều hệ điều hành, và là shell mặc định trên hầu hết các bản phân phối Linux. + +## Giới Thiệu về Lập Trình Bash + +Các script Bash là các tệp văn bản chứa một chuỗi các lệnh được thực thi bởi shell Bash. Chúng cho phép bạn tự động hóa các tác vụ lặp đi lặp lại, kết hợp các lệnh phức tạp, và tạo các tiện ích tùy chỉnh. + +## Cú Pháp Cơ Bản + +### Tạo một Script Bash + +1. Tạo một tệp với phần mở rộng `.sh` +2. Thêm dòng shebang ở đầu: `#!/bin/bash` +3. Làm cho script có thể thực thi: `chmod +x script.sh` +4. Chạy script: `./script.sh` + +### Ví Dụ Hello World + +```bash +#!/bin/bash +# Đây là một chú thích +echo "Xin chào, Thế giới!" +``` + +## Biến + +### Khai Báo và Sử Dụng Biến + +```bash +#!/bin/bash + +# Khai báo biến +ten="Nguyen" +tuoi=30 + +# Sử dụng biến +echo "Tên: $ten" +echo "Tuổi: $tuoi" + +# Thay thế lệnh +ngay_hien_tai=$(date) +echo "Ngày hiện tại: $ngay_hien_tai" + +# Phép toán +ket_qua=$((10 + 5)) +echo "10 + 5 = $ket_qua" +``` + +### Biến Đặc Biệt + +| Biến | Mô tả | +|----------|-------------| +| `$0` | Tên của script | +| `$1` đến `$9` | 9 tham số đầu tiên được truyền vào script | +| `$#` | Số lượng tham số được truyền vào script | +| `$@` | Tất cả tham số được truyền vào script | +| `$?` | Trạng thái thoát của lệnh cuối cùng | +| `$$` | Process ID của script hiện tại | +| `$USER` | Tên người dùng đang chạy script | +| `$HOSTNAME` | Tên máy chủ | +| `$RANDOM` | Một số ngẫu nhiên | +| `$HOME` | Thư mục home của người dùng | + +## Cấu Trúc Điều Khiển + +### Câu Lệnh Điều Kiện + +#### Câu Lệnh If-Else + +```bash +#!/bin/bash + +tuoi=25 + +if [ $tuoi -lt 18 ]; then + echo "Bạn là trẻ vị thành niên." +elif [ $tuoi -ge 18 ] && [ $tuoi -lt 65 ]; then + echo "Bạn là người trưởng thành." +else + echo "Bạn là người cao tuổi." +fi +``` + +#### Câu Lệnh Case + +```bash +#!/bin/bash + +trai_cay="táo" + +case $trai_cay in + "táo") + echo "Đây là quả táo." + ;; + "chuối") + echo "Đây là quả chuối." + ;; + "cam") + echo "Đây là quả cam." + ;; + *) + echo "Không xác định được loại trái cây." + ;; +esac +``` + +### Vòng Lặp + +#### Vòng Lặp For + +```bash +#!/bin/bash + +# Vòng lặp for đơn giản +for i in 1 2 3 4 5; do + echo "Số: $i" +done + +# Vòng lặp for với khoảng +for i in {1..5}; do + echo "Số: $i" +done + +# Vòng lặp for với bước nhảy +for i in {1..10..2}; do + echo "Số lẻ: $i" +done + +# Vòng lặp for với đầu ra lệnh +for file in $(ls); do + echo "Tệp: $file" +done +``` + +#### Vòng Lặp While + +```bash +#!/bin/bash + +count=1 + +while [ $count -le 5 ]; do + echo "Đếm: $count" + ((count++)) +done +``` + +#### Vòng Lặp Until + +```bash +#!/bin/bash + +count=1 + +until [ $count -gt 5 ]; do + echo "Đếm: $count" + ((count++)) +done +``` + +## Hàm + +### Định Nghĩa và Sử Dụng Hàm + +```bash +#!/bin/bash + +# Định nghĩa hàm +chao_hoi() { + echo "Xin chào, $1!" +} + +# Hàm với giá trị trả về +cong() { + local ket_qua=$(($1 + $2)) + echo $ket_qua +} + +# Gọi hàm +chao_hoi "Nguyễn" +tong=$(cong 5 3) +echo "5 + 3 = $tong" +``` + +## Đầu Vào và Đầu Ra + +### Đọc Đầu Vào Người Dùng + +```bash +#!/bin/bash + +# Đọc một giá trị +echo "Nhập tên của bạn:" +read ten +echo "Xin chào, $ten!" + +# Đọc nhiều giá trị +echo "Nhập họ và tên của bạn:" +read ho ten +echo "Xin chào, $ho $ten!" + +# Đọc với lời nhắc +read -p "Nhập tuổi của bạn: " tuoi +echo "Bạn $tuoi tuổi." + +# Đọc mật khẩu (ẩn đầu vào) +read -sp "Nhập mật khẩu của bạn: " mat_khau +echo -e "\nĐã nhận mật khẩu." +``` + +### Đầu Vào/Đầu Ra Tệp + +```bash +#!/bin/bash + +# Ghi vào tệp +echo "Xin chào, Thế giới!" > output.txt +echo "Đây là một dòng mới." >> output.txt + +# Đọc từ tệp +while IFS= read -r line; do + echo "Dòng: $line" +done < input.txt + +# Xử lý từng dòng của tệp +cat input.txt | while read line; do + echo "Đang xử lý: $line" +done +``` + +## Mảng + +### Thao Tác Mảng + +```bash +#!/bin/bash + +# Khai báo mảng +trai_cay=("táo" "chuối" "cam" "nho") + +# Truy cập phần tử mảng +echo "Trái cây đầu tiên: ${trai_cay[0]}" +echo "Tất cả trái cây: ${trai_cay[@]}" +echo "Số lượng trái cây: ${#trai_cay[@]}" + +# Lặp qua mảng +for trai_cay in "${trai_cay[@]}"; do + echo "Trái cây: $trai_cay" +done + +# Thêm phần tử vào mảng +trai_cay+=("kiwi") + +# Xóa phần tử khỏi mảng +unset trai_cay[1] +``` + +## Thao Tác Chuỗi + +### Thao Tác Chuỗi + +```bash +#!/bin/bash + +# Độ dài chuỗi +str="Xin chào, Thế giới!" +echo "Độ dài: ${#str}" + +# Chuỗi con +echo "Chuỗi con: ${str:7:5}" + +# Thay thế chuỗi +echo "Thay thế: ${str/Thế giới/Bash}" + +# Chuyển đổi sang chữ hoa/chữ thường +echo "Chữ hoa: ${str^^}" +echo "Chữ thường: ${str,,}" +``` + +## Xử Lý Lỗi + +### Xử Lý Lỗi Cơ Bản + +```bash +#!/bin/bash + +# Thoát khi có lỗi +set -e + +# Xử lý lỗi tùy chỉnh +xu_ly_loi() { + echo "Lỗi xảy ra tại dòng $1" + exit 1 +} + +# Bắt lỗi +trap 'xu_ly_loi $LINENO' ERR + +# Kiểm tra thành công của lệnh +if ! command -v git &> /dev/null; then + echo "Git chưa được cài đặt." + exit 1 +fi +``` + +## Các Phương Pháp Tốt Nhất + +1. **Sử Dụng Shebang**: Luôn bao gồm `#!/bin/bash` ở đầu script của bạn. +2. **Chú Thích**: Thêm chú thích để giải thích logic phức tạp. +3. **Xử Lý Lỗi**: Triển khai xử lý lỗi thích hợp. +4. **Thụt Lề**: Sử dụng thụt lề nhất quán để dễ đọc. +5. **Quy Ước Đặt Tên**: Sử dụng tên mô tả cho biến và hàm. +6. **Trích Dẫn Biến**: Luôn trích dẫn biến để xử lý khoảng trắng và ký tự đặc biệt. +7. **Mã Thoát**: Trả về mã thoát thích hợp. +8. **Tính Mô-đun**: Chia các script phức tạp thành các hàm. +9. **Gỡ Lỗi**: Sử dụng `set -x` để gỡ lỗi. +10. **Kiểm Tra**: Kiểm tra script của bạn với các đầu vào khác nhau. + +## Tài Liệu Tham Khảo + +- [Hướng Dẫn Sử Dụng GNU Bash](https://www.gnu.org/software/bash/manual/) +- [Hướng Dẫn Bash cho Người Mới Bắt Đầu](https://tldp.org/LDP/Bash-Beginners-Guide/html/) +- [Hướng Dẫn Lập Trình Bash Nâng Cao](https://tldp.org/LDP/abs/html/) +- [ShellCheck](https://www.shellcheck.net/) - Một công cụ phân tích script shell diff --git a/i18n/vi/system-design/microservices_vi.md b/i18n/vi/system-design/microservices_vi.md new file mode 100644 index 0000000..80da511 --- /dev/null +++ b/i18n/vi/system-design/microservices_vi.md @@ -0,0 +1,467 @@ +# Kiến Trúc Microservices + +Kiến trúc microservices là một phong cách kiến trúc cấu trúc một ứng dụng như một tập hợp các dịch vụ nhỏ, liên kết lỏng lẻo có thể được phát triển, triển khai và mở rộng độc lập. + +## Microservices là gì? + +Microservices là các dịch vụ nhỏ, tự chủ hoạt động cùng nhau để tạo thành một ứng dụng hoàn chỉnh. Mỗi microservice: + +- Tập trung vào một khả năng kinh doanh duy nhất +- Chạy trong quy trình riêng của nó +- Giao tiếp thông qua API được định nghĩa rõ ràng +- Có thể được triển khai độc lập +- Có thể được viết bằng các ngôn ngữ lập trình khác nhau +- Có thể sử dụng các công nghệ lưu trữ dữ liệu khác nhau + +## Kiến Trúc Nguyên Khối vs. Microservices + +| Khía cạnh | Nguyên khối | Microservices | +|--------|------------|---------------| +| Cấu trúc | Codebase thống nhất, đơn lẻ | Nhiều dịch vụ độc lập | +| Phát triển | Đơn giản hơn để phát triển ban đầu | Thiết lập ban đầu phức tạp hơn | +| Triển khai | Triển khai toàn bộ ứng dụng | Triển khai từng dịch vụ riêng biệt | +| Mở rộng | Mở rộng toàn bộ ứng dụng | Mở rộng các dịch vụ riêng lẻ khi cần | +| Công nghệ | Một ngăn xếp công nghệ duy nhất | Có thể sử dụng nhiều ngăn xếp công nghệ | +| Cấu trúc nhóm | Các nhóm lớn hơn làm việc trên cùng một codebase | Các nhóm nhỏ hơn làm việc trên các dịch vụ riêng lẻ | +| Tác động của lỗi | Điểm lỗi duy nhất | Lỗi cô lập | +| Quản lý dữ liệu | Cơ sở dữ liệu được chia sẻ | Cơ sở dữ liệu cho mỗi dịch vụ | + +## Nguyên Tắc Chính của Microservices + +1. **Trách Nhiệm Đơn Lẻ**: Mỗi dịch vụ chịu trách nhiệm cho một khả năng kinh doanh cụ thể. +2. **Tự Chủ**: Các dịch vụ có thể được phát triển, triển khai và mở rộng độc lập. +3. **Khả Năng Phục Hồi**: Lỗi trong một dịch vụ không nên lan truyền sang các dịch vụ khác. +4. **Phân Cấp**: Quản trị và quản lý dữ liệu phi tập trung. +5. **Phân Phối Liên Tục**: Triển khai tự động, thường xuyên của các dịch vụ riêng lẻ. +6. **Khả Năng Quan Sát**: Giám sát và ghi nhật ký toàn diện. +7. **Thiết Kế Hướng Miền**: Các dịch vụ được thiết kế xung quanh các miền kinh doanh. + +## Mẫu Giao Tiếp Microservices + +### Giao Tiếp Đồng Bộ + +#### REST API + +```json +// Ví dụ yêu cầu REST API +GET /api/products/123 +Accept: application/json + +// Ví dụ phản hồi +{ + "id": "123", + "name": "Tên Sản Phẩm", + "price": 19.99, + "category": "Điện tử" +} +``` + +#### gRPC + +```protobuf +// Ví dụ định nghĩa dịch vụ gRPC +syntax = "proto3"; + +service ProductService { + rpc GetProduct(ProductRequest) returns (Product); +} + +message ProductRequest { + string id = 1; +} + +message Product { + string id = 1; + string name = 2; + double price = 3; + string category = 4; +} +``` + +### Giao Tiếp Bất Đồng Bộ + +#### Hàng Đợi Thông Điệp + +```javascript +// Ví dụ nhà sản xuất thông điệp (Node.js với RabbitMQ) +const amqp = require('amqplib'); + +async function sendOrderCreatedEvent(order) { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); + + const queue = 'order_events'; + const message = JSON.stringify({ + type: 'ORDER_CREATED', + data: order + }); + + await channel.assertQueue(queue, { durable: true }); + channel.sendToQueue(queue, Buffer.from(message)); + + console.log(`Đã gửi: ${message}`); + + setTimeout(() => { + connection.close(); + }, 500); +} +``` + +```javascript +// Ví dụ người tiêu thụ thông điệp (Node.js với RabbitMQ) +const amqp = require('amqplib'); + +async function processOrderEvents() { + const connection = await amqp.connect('amqp://localhost'); + const channel = await connection.createChannel(); + + const queue = 'order_events'; + + await channel.assertQueue(queue, { durable: true }); + console.log(`Đang đợi thông điệp trong ${queue}`); + + channel.consume(queue, (msg) => { + const event = JSON.parse(msg.content.toString()); + console.log(`Đã nhận: ${event.type}`); + + if (event.type === 'ORDER_CREATED') { + // Xử lý đơn hàng + processOrder(event.data); + } + + channel.ack(msg); + }); +} +``` + +#### Luồng Sự Kiện + +```java +// Ví dụ nhà sản xuất Kafka (Java) +Properties props = new Properties(); +props.put("bootstrap.servers", "localhost:9092"); +props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); +props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + +Producer producer = new KafkaProducer<>(props); + +String topic = "order-events"; +String key = order.getId(); +String value = objectMapper.writeValueAsString(order); + +ProducerRecord record = new ProducerRecord<>(topic, key, value); +producer.send(record); +producer.close(); +``` + +```java +// Ví dụ người tiêu thụ Kafka (Java) +Properties props = new Properties(); +props.put("bootstrap.servers", "localhost:9092"); +props.put("group.id", "order-processing-group"); +props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); +props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + +Consumer consumer = new KafkaConsumer<>(props); +consumer.subscribe(Arrays.asList("order-events")); + +while (true) { + ConsumerRecords records = consumer.poll(Duration.ofMillis(100)); + for (ConsumerRecord record : records) { + String key = record.key(); + String value = record.value(); + + Order order = objectMapper.readValue(value, Order.class); + processOrder(order); + } +} +``` + +## Khám Phá Dịch Vụ và API Gateway + +### Khám Phá Dịch Vụ + +```yaml +# Ví dụ đăng ký dịch vụ Consul +{ + "service": { + "name": "product-service", + "id": "product-service-1", + "tags": ["api", "v1"], + "address": "10.0.0.1", + "port": 8080, + "checks": [ + { + "http": "http://10.0.0.1:8080/health", + "interval": "10s" + } + ] + } +} +``` + +### API Gateway + +```yaml +# Ví dụ cấu hình Kong API Gateway +services: + - name: product-service + url: http://product-service:8080 + routes: + - name: product-routes + paths: + - /api/products + strip_path: true + plugins: + - name: rate-limiting + config: + minute: 100 + - name: jwt +``` + +## Quản Lý Dữ Liệu trong Microservices + +### Cơ Sở Dữ Liệu cho Mỗi Dịch Vụ + +```yaml +# Ví dụ docker-compose.yml cho nhiều cơ sở dữ liệu +version: '3' +services: + product-service: + build: ./product-service + depends_on: + - product-db + environment: + - DB_HOST=product-db + - DB_PORT=5432 + - DB_NAME=productdb + + product-db: + image: postgres:13 + environment: + - POSTGRES_DB=productdb + - POSTGRES_USER=user + - POSTGRES_PASSWORD=password + volumes: + - product-db-data:/var/lib/postgresql/data + + order-service: + build: ./order-service + depends_on: + - order-db + environment: + - DB_HOST=order-db + - DB_PORT=27017 + - DB_NAME=orderdb + + order-db: + image: mongo:4.4 + environment: + - MONGO_INITDB_DATABASE=orderdb + volumes: + - order-db-data:/data/db + +volumes: + product-db-data: + order-db-data: +``` + +### Mẫu CQRS + +```csharp +// Phía lệnh +public class CreateOrderCommand +{ + public string CustomerId { get; set; } + public List Items { get; set; } +} + +public class OrderCommandHandler +{ + private readonly IOrderRepository _repository; + private readonly IEventBus _eventBus; + + public OrderCommandHandler(IOrderRepository repository, IEventBus eventBus) + { + _repository = repository; + _eventBus = eventBus; + } + + public async Task Handle(CreateOrderCommand command) + { + var order = new Order(Guid.NewGuid(), command.CustomerId, command.Items); + await _repository.SaveAsync(order); + + await _eventBus.PublishAsync(new OrderCreatedEvent + { + OrderId = order.Id, + CustomerId = order.CustomerId, + Items = order.Items + }); + } +} + +// Phía truy vấn +public class OrderQueryService +{ + private readonly IOrderReadModel _readModel; + + public OrderQueryService(IOrderReadModel readModel) + { + _readModel = readModel; + } + + public async Task GetOrderAsync(Guid orderId) + { + return await _readModel.GetOrderAsync(orderId); + } + + public async Task> GetCustomerOrdersAsync(string customerId) + { + return await _readModel.GetCustomerOrdersAsync(customerId); + } +} +``` + +## Triển Khai Microservices + +### Docker Containers + +```dockerfile +# Ví dụ Dockerfile cho một microservice +FROM node:14-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm install --production + +COPY . . + +EXPOSE 8080 + +CMD ["node", "server.js"] +``` + +### Kubernetes + +```yaml +# Ví dụ triển khai Kubernetes cho một microservice +apiVersion: apps/v1 +kind: Deployment +metadata: + name: product-service + labels: + app: product-service +spec: + replicas: 3 + selector: + matchLabels: + app: product-service + template: + metadata: + labels: + app: product-service + spec: + containers: + - name: product-service + image: my-registry/product-service:1.0.0 + ports: + - containerPort: 8080 + env: + - name: DB_HOST + value: product-db-service + - name: DB_PORT + value: "5432" + - name: DB_NAME + value: productdb + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + name: product-service +spec: + selector: + app: product-service + ports: + - port: 80 + targetPort: 8080 + type: ClusterIP +``` + +## Giám Sát và Khả Năng Quan Sát + +### Truy Vết Phân Tán + +```java +// Ví dụ sử dụng Spring Cloud Sleuth và Zipkin +@RestController +public class ProductController { + + private final ProductService productService; + + @Autowired + public ProductController(ProductService productService) { + this.productService = productService; + } + + @GetMapping("/products/{id}") + public Product getProduct(@PathVariable String id) { + // Truy vết được tự động xử lý bởi Sleuth + return productService.getProduct(id); + } +} +``` + +### Thu Thập Số Liệu + +```yaml +# Ví dụ cấu hình Prometheus +global: + scrape_interval: 15s + +scrape_configs: + - job_name: 'product-service' + metrics_path: '/actuator/prometheus' + static_configs: + - targets: ['product-service:8080'] + + - job_name: 'order-service' + metrics_path: '/actuator/prometheus' + static_configs: + - targets: ['order-service:8080'] +``` + +## Thách Thức và Phương Pháp Tốt Nhất + +### Thách Thức + +1. **Độ Phức Tạp của Hệ Thống Phân Tán**: Gỡ lỗi và kiểm tra trở nên khó khăn hơn. +2. **Tính Nhất Quán của Dữ Liệu**: Duy trì tính nhất quán giữa các dịch vụ là một thách thức. +3. **Ranh Giới Dịch Vụ**: Xác định ranh giới dịch vụ đúng đắn đòi hỏi chuyên môn về miền. +4. **Phụ Phí Vận Hành**: Nhiều dịch vụ hơn đồng nghĩa với nhiều cơ sở hạ tầng cần quản lý hơn. +5. **Độ Trễ Mạng**: Giao tiếp giữa các dịch vụ thêm độ trễ. + +### Phương Pháp Tốt Nhất + +1. **Bắt Đầu Nguyên Khối**: Bắt đầu với một ứng dụng nguyên khối và trích xuất microservices khi cần thiết. +2. **Thiết Kế cho Lỗi**: Triển khai các bộ ngắt mạch, thử lại và dự phòng. +3. **Tự Động Hóa Mọi Thứ**: Sử dụng đường ống CI/CD cho tất cả các dịch vụ. +4. **Triển Khai Giám Sát**: Ghi nhật ký, số liệu và truy vết toàn diện. +5. **Sử Dụng Container**: Containerize các dịch vụ để đảm bảo tính nhất quán giữa các môi trường. +6. **Phiên Bản API**: Triển khai phiên bản API thích hợp để quản lý các thay đổi. +7. **Tài Liệu**: Duy trì tài liệu rõ ràng cho tất cả các dịch vụ và API. +8. **Kiểm Tra**: Triển khai các chiến lược kiểm tra toàn diện, bao gồm kiểm tra hợp đồng. + +## Tài Liệu Tham Khảo + +- [Microservices.io](https://microservices.io/) +- [Martin Fowler về Microservices](https://martinfowler.com/articles/microservices.html) +- [Sam Newman - Xây Dựng Microservices](https://samnewman.io/books/building_microservices/) +- [Chris Richardson - Mẫu Microservices](https://microservices.io/book) diff --git a/i18n/vi/testing/unit-testing_vi.md b/i18n/vi/testing/unit-testing_vi.md new file mode 100644 index 0000000..018a504 --- /dev/null +++ b/i18n/vi/testing/unit-testing_vi.md @@ -0,0 +1,467 @@ +# Kiểm Thử Đơn Vị + +Kiểm thử đơn vị là một phương pháp kiểm thử phần mềm trong đó các đơn vị hoặc thành phần riêng lẻ của phần mềm được kiểm tra một cách cô lập với phần còn lại của hệ thống. + +## Kiểm Thử Đơn Vị là gì? + +Một bài kiểm thử đơn vị xác minh rằng một phần mã nhỏ, cô lập (một "đơn vị") hoạt động chính xác như mong đợi của nhà phát triển. Các đơn vị thường là: + +- Các hàm hoặc phương thức riêng lẻ +- Các lớp +- Các mô-đun hoặc thành phần + +Mục tiêu là xác nhận rằng mỗi đơn vị của phần mềm hoạt động như thiết kế. + +## Lợi Ích của Kiểm Thử Đơn Vị + +- **Phát Hiện Lỗi Sớm**: Phát hiện lỗi sớm trong chu trình phát triển +- **Tạo Điều Kiện cho Thay Đổi**: Giúp việc tái cấu trúc mã và thêm tính năng mới dễ dàng hơn +- **Tài Liệu**: Các bài kiểm thử đóng vai trò như tài liệu về cách mã nên hoạt động +- **Cải Thiện Thiết Kế**: Khuyến khích thiết kế phần mềm và tính mô-đun tốt hơn +- **Tự Tin**: Cung cấp sự tự tin rằng mã hoạt động như mong đợi +- **Giảm Chi Phí**: Rẻ hơn để sửa lỗi được phát hiện trong quá trình kiểm thử đơn vị hơn là các giai đoạn sau + +## Nguyên Tắc Kiểm Thử Đơn Vị + +### Nguyên Tắc FIRST + +- **Fast (Nhanh)**: Các bài kiểm thử nên chạy nhanh +- **Independent (Độc lập)**: Các bài kiểm thử không nên phụ thuộc vào nhau +- **Repeatable (Lặp lại được)**: Các bài kiểm thử nên cho kết quả giống nhau mỗi lần chạy +- **Self-validating (Tự xác thực)**: Các bài kiểm thử nên tự động xác định xem chúng đã vượt qua hay thất bại +- **Timely (Kịp thời)**: Các bài kiểm thử nên được viết vào thời điểm thích hợp (lý tưởng là trước khi viết mã) + +### Mẫu AAA + +- **Arrange (Sắp xếp)**: Thiết lập điều kiện kiểm thử +- **Act (Hành động)**: Thực thi mã đang được kiểm thử +- **Assert (Khẳng định)**: Xác minh kết quả là như mong đợi + +## Các Framework Kiểm Thử Đơn Vị + +### JavaScript (Jest) + +```javascript +// math.js +function add(a, b) { + return a + b; +} + +function subtract(a, b) { + return a - b; +} + +module.exports = { add, subtract }; + +// math.test.js +const { add, subtract } = require('./math'); + +describe('Các hàm toán học', () => { + test('hàm add nên cộng hai số chính xác', () => { + // Arrange + const a = 5; + const b = 3; + + // Act + const result = add(a, b); + + // Assert + expect(result).toBe(8); + }); + + test('hàm subtract nên trừ hai số chính xác', () => { + // Arrange + const a = 5; + const b = 3; + + // Act + const result = subtract(a, b); + + // Assert + expect(result).toBe(2); + }); +}); +``` + +### Python (pytest) + +```python +# math_utils.py +def add(a, b): + return a + b + +def subtract(a, b): + return a - b + +# test_math_utils.py +import pytest +from math_utils import add, subtract + +def test_add(): + # Arrange + a = 5 + b = 3 + + # Act + result = add(a, b) + + # Assert + assert result == 8 + +def test_subtract(): + # Arrange + a = 5 + b = 3 + + # Act + result = subtract(a, b) + + # Assert + assert result == 2 +``` + +### Java (JUnit) + +```java +// MathUtils.java +public class MathUtils { + public int add(int a, int b) { + return a + b; + } + + public int subtract(int a, int b) { + return a - b; + } +} + +// MathUtilsTest.java +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +public class MathUtilsTest { + + @Test + public void testAdd() { + // Arrange + MathUtils mathUtils = new MathUtils(); + int a = 5; + int b = 3; + + // Act + int result = mathUtils.add(a, b); + + // Assert + assertEquals(8, result); + } + + @Test + public void testSubtract() { + // Arrange + MathUtils mathUtils = new MathUtils(); + int a = 5; + int b = 3; + + // Act + int result = mathUtils.subtract(a, b); + + // Assert + assertEquals(2, result); + } +} +``` + +### C# (xUnit) + +```csharp +// MathUtils.cs +public class MathUtils +{ + public int Add(int a, int b) + { + return a + b; + } + + public int Subtract(int a, int b) + { + return a - b; + } +} + +// MathUtilsTests.cs +using Xunit; + +public class MathUtilsTests +{ + [Fact] + public void Add_ShouldCorrectlyAddTwoNumbers() + { + // Arrange + var mathUtils = new MathUtils(); + int a = 5; + int b = 3; + + // Act + int result = mathUtils.Add(a, b); + + // Assert + Assert.Equal(8, result); + } + + [Fact] + public void Subtract_ShouldCorrectlySubtractTwoNumbers() + { + // Arrange + var mathUtils = new MathUtils(); + int a = 5; + int b = 3; + + // Act + int result = mathUtils.Subtract(a, b); + + // Assert + Assert.Equal(2, result); + } +} +``` + +## Test Doubles + +Test doubles là các đối tượng thay thế các thành phần thực trong các bài kiểm thử để cô lập mã đang được kiểm thử. + +### Các Loại Test Doubles + +#### Dummy + +Các đối tượng được truyền qua nhưng không bao giờ thực sự được sử dụng. + +```javascript +// Ví dụ JavaScript +function createUser(user, logger) { + // logger không được sử dụng trong bài kiểm thử này + return { id: 123, ...user }; +} + +test('createUser nên thêm một ID vào user', () => { + // Arrange + const dummyLogger = {}; // Đối tượng giả không bao giờ được sử dụng + const user = { name: 'John' }; + + // Act + const result = createUser(user, dummyLogger); + + // Assert + expect(result.id).toBeDefined(); + expect(result.name).toBe('John'); +}); +``` + +#### Stub + +Các đối tượng cung cấp câu trả lời được định nghĩa trước cho các lời gọi được thực hiện trong quá trình kiểm thử. + +```java +// Ví dụ Java +public interface WeatherService { + int getCurrentTemperature(String city); +} + +// Triển khai stub +public class WeatherServiceStub implements WeatherService { + @Override + public int getCurrentTemperature(String city) { + return 25; // Luôn trả về 25°C bất kể thành phố nào + } +} + +@Test +public void testWeatherReporter() { + // Arrange + WeatherService stubService = new WeatherServiceStub(); + WeatherReporter reporter = new WeatherReporter(stubService); + + // Act + String report = reporter.generateReport("London"); + + // Assert + assertEquals("Nhiệt độ hiện tại ở London: 25°C", report); +} +``` + +#### Spy + +Các đối tượng ghi lại các lời gọi được thực hiện đến chúng. + +```python +# Ví dụ Python +class EmailServiceSpy: + def __init__(self): + self.emails_sent = [] + + def send_email(self, to, subject, body): + self.emails_sent.append({ + 'to': to, + 'subject': subject, + 'body': body + }) + +def test_user_registration_sends_welcome_email(): + # Arrange + email_service = EmailServiceSpy() + user_service = UserService(email_service) + + # Act + user_service.register("john@example.com", "password123") + + # Assert + assert len(email_service.emails_sent) == 1 + assert email_service.emails_sent[0]['to'] == "john@example.com" + assert "Chào mừng" in email_service.emails_sent[0]['subject'] +``` + +#### Mock + +Các đối tượng xác minh rằng các phương thức cụ thể đã được gọi với các đối số cụ thể. + +```csharp +// Ví dụ C# với Moq +[Fact] +public void Register_ShouldSendWelcomeEmail() +{ + // Arrange + var mockEmailService = new Mock(); + var userService = new UserService(mockEmailService.Object); + + // Act + userService.Register("john@example.com", "password123"); + + // Assert + mockEmailService.Verify( + x => x.SendEmail( + "john@example.com", + It.Is(s => s.Contains("Chào mừng")), + It.IsAny() + ), + Times.Once + ); +} +``` + +#### Fake + +Các đối tượng có triển khai hoạt động nhưng không phù hợp cho môi trường sản xuất. + +```javascript +// Ví dụ JavaScript +class FakeUserRepository { + constructor() { + this.users = []; + this.nextId = 1; + } + + create(userData) { + const user = { id: this.nextId++, ...userData }; + this.users.push(user); + return user; + } + + findById(id) { + return this.users.find(user => user.id === id); + } +} + +test('UserService nên tạo một người dùng', () => { + // Arrange + const fakeRepo = new FakeUserRepository(); + const userService = new UserService(fakeRepo); + + // Act + const user = userService.createUser('John', 'john@example.com'); + + // Assert + expect(user.id).toBe(1); + expect(user.name).toBe('John'); + expect(fakeRepo.findById(1)).toEqual(user); +}); +``` + +## Độ Phủ Kiểm Thử + +Độ phủ kiểm thử đo lường bao nhiêu mã của bạn được thực thi trong quá trình kiểm thử. + +### Các Số Liệu Độ Phủ + +- **Độ Phủ Dòng**: Phần trăm dòng được thực thi trong quá trình kiểm thử +- **Độ Phủ Nhánh**: Phần trăm nhánh (if/else, switch) được thực thi trong quá trình kiểm thử +- **Độ Phủ Hàm**: Phần trăm hàm được gọi trong quá trình kiểm thử +- **Độ Phủ Câu Lệnh**: Phần trăm câu lệnh được thực thi trong quá trình kiểm thử + +### Ví Dụ Báo Cáo Độ Phủ (Jest) + +``` +--------------------|---------|----------|---------|---------|------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +--------------------|---------|----------|---------|---------|------------------- +All files | 85.71 | 66.67 | 100 | 85.71 | + math.js | 100 | 100 | 100 | 100 | + string-utils.js | 71.43 | 33.33 | 100 | 71.43 | 15-18 +--------------------|---------|----------|---------|---------|------------------- +``` + +## Các Phương Pháp Tốt Nhất cho Kiểm Thử Đơn Vị + +1. **Kiểm Thử Một Thứ Tại Một Thời Điểm**: Mỗi bài kiểm thử nên xác minh một hành vi cụ thể. +2. **Giữ Các Bài Kiểm Thử Đơn Giản**: Các bài kiểm thử nên dễ hiểu và duy trì. +3. **Sử Dụng Tên Bài Kiểm Thử Mô Tả**: Tên nên mô tả rõ ràng những gì đang được kiểm thử. +4. **Cô Lập Đơn Vị**: Sử dụng test doubles để cô lập đơn vị khỏi các phụ thuộc của nó. +5. **Kiểm Thử Các Trường Hợp Biên**: Bao gồm các bài kiểm thử cho điều kiện biên và trường hợp lỗi. +6. **Không Kiểm Thử Framework**: Tập trung vào việc kiểm thử mã của bạn, không phải framework hoặc ngôn ngữ. +7. **Duy Trì Tính Độc Lập của Bài Kiểm Thử**: Các bài kiểm thử không nên phụ thuộc vào nhau hoặc chạy theo một thứ tự cụ thể. +8. **Tránh Logic trong Bài Kiểm Thử**: Các bài kiểm thử nên đơn giản với logic tối thiểu. +9. **Viết Bài Kiểm Thử Trước (TDD)**: Cân nhắc viết bài kiểm thử trước khi triển khai mã. +10. **Tái Cấu Trúc Bài Kiểm Thử**: Giữ bài kiểm thử sạch sẽ và có thể bảo trì, giống như mã sản xuất. + +## Phát Triển Hướng Kiểm Thử (TDD) + +TDD là một quy trình phát triển trong đó các bài kiểm thử được viết trước mã. Chu kỳ là: + +1. **Red (Đỏ)**: Viết một bài kiểm thử thất bại +2. **Green (Xanh)**: Viết mã tối thiểu để làm cho bài kiểm thử vượt qua +3. **Refactor (Tái cấu trúc)**: Cải thiện mã trong khi giữ cho các bài kiểm thử vượt qua + +### Ví Dụ TDD (JavaScript) + +```javascript +// Bước 1: Viết một bài kiểm thử thất bại +test('isPalindrome nên trả về true cho các chuỗi đối xứng', () => { + expect(isPalindrome('racecar')).toBe(true); +}); + +// Bước 2: Viết mã tối thiểu để làm cho nó vượt qua +function isPalindrome(str) { + return str === str.split('').reverse().join(''); +} + +// Bước 3: Thêm nhiều bài kiểm thử +test('isPalindrome nên trả về false cho các chuỗi không đối xứng', () => { + expect(isPalindrome('hello')).toBe(false); +}); + +test('isPalindrome nên không phân biệt chữ hoa chữ thường', () => { + expect(isPalindrome('Racecar')).toBe(true); +}); + +// Bước 4: Tái cấu trúc mã +function isPalindrome(str) { + const normalized = str.toLowerCase(); + return normalized === normalized.split('').reverse().join(''); +} +``` + +## Tài Liệu Tham Khảo + +- [Martin Fowler về Kiểm Thử Đơn Vị](https://martinfowler.com/bliki/UnitTest.html) +- [Phát Triển Hướng Kiểm Thử bởi Ví Dụ](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530) +- [Tài Liệu Jest](https://jestjs.io/docs/getting-started) +- [Tài Liệu pytest](https://docs.pytest.org/) +- [Tài Liệu JUnit](https://junit.org/junit5/docs/current/user-guide/) +- [Tài Liệu xUnit](https://xunit.net/docs/getting-started/netcore/cmdline) diff --git a/snippets/algorithms/graph-traversal/GraphTraversal.cs b/snippets/algorithms/graph-traversal/GraphTraversal.cs new file mode 100644 index 0000000..225b3c1 --- /dev/null +++ b/snippets/algorithms/graph-traversal/GraphTraversal.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +/** + * Graph Traversal Algorithms in C# + * + * This program demonstrates BFS and DFS traversal algorithms on a graph. + * + * Compile: dotnet build + * Run: dotnet run + */ +namespace GraphTraversalAlgorithms +{ + /// + /// A graph class using adjacency list representation + /// + public class Graph + { + // Adjacency list representation + private Dictionary> adjacencyList; + + /// + /// Initializes a new empty graph + /// + public Graph() + { + adjacencyList = new Dictionary>(); + } + + /// + /// Adds a vertex to the graph + /// + /// The vertex to add + public void AddVertex(string vertex) + { + if (!adjacencyList.ContainsKey(vertex)) + { + adjacencyList[vertex] = new List(); + } + } + + /// + /// Adds an edge between two vertices + /// + /// First vertex + /// Second vertex + public void AddEdge(string v1, string v2) + { + // Ensure both vertices exist + AddVertex(v1); + AddVertex(v2); + + // Add the edge (undirected graph) + adjacencyList[v1].Add(v2); + adjacencyList[v2].Add(v1); + } + + /// + /// Helper method to get sorted neighbors for consistent output + /// + /// The vertex to get neighbors for + /// Sorted list of neighbors + private List GetSortedNeighbors(string vertex) + { + var neighbors = new List(adjacencyList[vertex]); + neighbors.Sort(); + return neighbors; + } + + /// + /// Performs a breadth-first search traversal starting from the given vertex + /// + /// Starting vertex + /// List of vertices in the order they were visited + public List BFS(string start) + { + if (!adjacencyList.ContainsKey(start)) + { + return new List(); + } + + HashSet visited = new HashSet(); + Queue queue = new Queue(); + List result = new List(); + + // Initialize with starting vertex + visited.Add(start); + queue.Enqueue(start); + + Console.WriteLine($"Starting BFS traversal from vertex {start}"); + + while (queue.Count > 0) + { + // Dequeue the first vertex + string vertex = queue.Dequeue(); + result.Add(vertex); + + Console.WriteLine($"Visiting: {vertex}"); + Console.WriteLine($"Queue: [{string.Join(", ", queue)}]"); + Console.WriteLine($"Visited so far: [{string.Join(", ", result)}]"); + Console.WriteLine("------------------------------"); + + // Pause for demonstration + Thread.Sleep(500); + + // Get sorted neighbors for consistent order + List neighbors = GetSortedNeighbors(vertex); + + // Enqueue all unvisited neighbors + foreach (string neighbor in neighbors) + { + if (!visited.Contains(neighbor)) + { + visited.Add(neighbor); + queue.Enqueue(neighbor); + } + } + } + + return result; + } + + /// + /// Performs a recursive depth-first search traversal starting from the given vertex + /// + /// Starting vertex + /// List of vertices in the order they were visited + public List DFSRecursive(string start) + { + if (!adjacencyList.ContainsKey(start)) + { + return new List(); + } + + HashSet visited = new HashSet(); + List result = new List(); + + Console.WriteLine($"Starting recursive DFS traversal from vertex {start}"); + + DFSHelper(start, visited, result); + + return result; + } + + /// + /// Helper method for recursive DFS + /// + /// Current vertex + /// Set of visited vertices + /// List to store the traversal result + private void DFSHelper(string vertex, HashSet visited, List result) + { + // Mark as visited and add to result + visited.Add(vertex); + result.Add(vertex); + + Console.WriteLine($"Visiting: {vertex}"); + Console.WriteLine($"Visited so far: [{string.Join(", ", result)}]"); + Console.WriteLine("------------------------------"); + + // Pause for demonstration + Thread.Sleep(500); + + // Get sorted neighbors for consistent order + List neighbors = GetSortedNeighbors(vertex); + + // Recursively visit all unvisited neighbors + foreach (string neighbor in neighbors) + { + if (!visited.Contains(neighbor)) + { + DFSHelper(neighbor, visited, result); + } + } + } + + /// + /// Performs an iterative depth-first search traversal starting from the given vertex + /// + /// Starting vertex + /// List of vertices in the order they were visited + public List DFSIterative(string start) + { + if (!adjacencyList.ContainsKey(start)) + { + return new List(); + } + + HashSet visited = new HashSet(); + Stack stack = new Stack(); + List result = new List(); + + // Initialize with starting vertex + stack.Push(start); + + Console.WriteLine($"Starting iterative DFS traversal from vertex {start}"); + + while (stack.Count > 0) + { + // Pop the top vertex + string vertex = stack.Pop(); + + // If not visited, process it + if (!visited.Contains(vertex)) + { + visited.Add(vertex); + result.Add(vertex); + + Console.WriteLine($"Visiting: {vertex}"); + Console.WriteLine($"Stack: [{string.Join(", ", stack)}]"); + Console.WriteLine($"Visited so far: [{string.Join(", ", result)}]"); + Console.WriteLine("------------------------------"); + + // Pause for demonstration + Thread.Sleep(500); + + // Get sorted neighbors in reverse order for stack + List neighbors = GetSortedNeighbors(vertex); + neighbors.Reverse(); + + // Push all unvisited neighbors onto the stack + foreach (string neighbor in neighbors) + { + if (!visited.Contains(neighbor)) + { + stack.Push(neighbor); + } + } + } + } + + return result; + } + + /// + /// Prints a visualization of the graph structure + /// + public void VisualizeGraph() + { + Console.WriteLine("\nGraph Structure:"); + Console.WriteLine("------------------------------"); + + // Sort vertices for consistent output + var vertices = adjacencyList.Keys.ToList(); + vertices.Sort(); + + foreach (string vertex in vertices) + { + List neighbors = GetSortedNeighbors(vertex); + Console.WriteLine($"{vertex} -> [{string.Join(", ", neighbors)}]"); + } + + Console.WriteLine("------------------------------"); + } + } + + public class Program + { + /// + /// Creates a sample graph for demonstration + /// + /// A sample graph + public static Graph CreateSampleGraph() + { + Graph g = new Graph(); + + // Add edges to build this graph: + // A + // / \ + // B C + // / \ \ + // D E---F + + string[,] edges = { + {"A", "B"}, {"A", "C"}, + {"B", "D"}, {"B", "E"}, + {"C", "F"}, {"E", "F"} + }; + + for (int i = 0; i < edges.GetLength(0); i++) + { + g.AddEdge(edges[i, 0], edges[i, 1]); + } + + return g; + } + + public static void Main(string[] args) + { + // Create a sample graph + Graph g = CreateSampleGraph(); + g.VisualizeGraph(); + + // Demonstrate BFS + Console.WriteLine("\n=== BFS Traversal ==="); + List bfsResult = g.BFS("A"); + Console.WriteLine($"BFS Result: [{string.Join(", ", bfsResult)}]"); + + // Demonstrate recursive DFS + Console.WriteLine("\n=== DFS Traversal (Recursive) ==="); + List dfsRecResult = g.DFSRecursive("A"); + Console.WriteLine($"DFS Recursive Result: [{string.Join(", ", dfsRecResult)}]"); + + // Demonstrate iterative DFS + Console.WriteLine("\n=== DFS Traversal (Iterative) ==="); + List dfsIterResult = g.DFSIterative("A"); + Console.WriteLine($"DFS Iterative Result: [{string.Join(", ", dfsIterResult)}]"); + } + } +} \ No newline at end of file diff --git a/snippets/algorithms/graph-traversal/GraphTraversal.java b/snippets/algorithms/graph-traversal/GraphTraversal.java new file mode 100644 index 0000000..0fce771 --- /dev/null +++ b/snippets/algorithms/graph-traversal/GraphTraversal.java @@ -0,0 +1,295 @@ +import java.util.*; + +/** + * Graph Traversal Algorithms in Java + * + * This class demonstrates BFS and DFS traversal algorithms on a graph. + * + * Compile: javac GraphTraversal.java + * Run: java GraphTraversal + */ +public class GraphTraversal { + + /** + * Graph class representing a graph using adjacency list + */ + static class Graph { + // Adjacency list representation + private Map> adjacencyList; + + /** + * Initialize an empty graph + */ + public Graph() { + this.adjacencyList = new HashMap<>(); + } + + /** + * Add a vertex to the graph + * @param vertex The vertex to add + */ + public void addVertex(String vertex) { + if (!adjacencyList.containsKey(vertex)) { + adjacencyList.put(vertex, new ArrayList<>()); + } + } + + /** + * Add an edge between two vertices + * @param v1 First vertex + * @param v2 Second vertex + */ + public void addEdge(String v1, String v2) { + // Ensure both vertices exist + addVertex(v1); + addVertex(v2); + + // Add the edge (undirected graph) + adjacencyList.get(v1).add(v2); + adjacencyList.get(v2).add(v1); + } + + /** + * Helper method to get sorted neighbors for consistent output + * @param vertex The vertex to get neighbors for + * @return Sorted list of neighbors + */ + private List getSortedNeighbors(String vertex) { + List neighbors = new ArrayList<>(adjacencyList.get(vertex)); + Collections.sort(neighbors); + return neighbors; + } + + /** + * Breadth-First Search traversal + * @param start Starting vertex + * @return List of vertices in the order they were visited + */ + public List bfs(String start) { + if (!adjacencyList.containsKey(start)) { + return new ArrayList<>(); + } + + Set visited = new HashSet<>(); + Queue queue = new LinkedList<>(); + List result = new ArrayList<>(); + + // Initialize with starting vertex + visited.add(start); + queue.add(start); + + System.out.println("Starting BFS traversal from vertex " + start); + + while (!queue.isEmpty()) { + // Dequeue the first vertex + String vertex = queue.poll(); + result.add(vertex); + + System.out.println("Visiting: " + vertex); + System.out.println("Queue: " + queue); + System.out.println("Visited so far: " + result); + System.out.println("------------------------------"); + + // Pause for demonstration + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Get sorted neighbors for consistent order + List neighbors = getSortedNeighbors(vertex); + + // Enqueue all unvisited neighbors + for (String neighbor : neighbors) { + if (!visited.contains(neighbor)) { + visited.add(neighbor); + queue.add(neighbor); + } + } + } + + return result; + } + + /** + * Depth-First Search traversal (recursive) + * @param start Starting vertex + * @return List of vertices in the order they were visited + */ + public List dfsRecursive(String start) { + if (!adjacencyList.containsKey(start)) { + return new ArrayList<>(); + } + + Set visited = new HashSet<>(); + List result = new ArrayList<>(); + + System.out.println("Starting recursive DFS traversal from vertex " + start); + + dfsHelper(start, visited, result); + + return result; + } + + /** + * Helper method for recursive DFS + * @param vertex Current vertex + * @param visited Set of visited vertices + * @param result List to store the traversal result + */ + private void dfsHelper(String vertex, Set visited, List result) { + // Mark as visited and add to result + visited.add(vertex); + result.add(vertex); + + System.out.println("Visiting: " + vertex); + System.out.println("Visited so far: " + result); + System.out.println("------------------------------"); + + // Pause for demonstration + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Get sorted neighbors for consistent order + List neighbors = getSortedNeighbors(vertex); + + // Recursively visit all unvisited neighbors + for (String neighbor : neighbors) { + if (!visited.contains(neighbor)) { + dfsHelper(neighbor, visited, result); + } + } + } + + /** + * Depth-First Search traversal (iterative) + * @param start Starting vertex + * @return List of vertices in the order they were visited + */ + public List dfsIterative(String start) { + if (!adjacencyList.containsKey(start)) { + return new ArrayList<>(); + } + + Set visited = new HashSet<>(); + Stack stack = new Stack<>(); + List result = new ArrayList<>(); + + // Initialize with starting vertex + stack.push(start); + + System.out.println("Starting iterative DFS traversal from vertex " + start); + + while (!stack.isEmpty()) { + // Pop the top vertex + String vertex = stack.pop(); + + // If not visited, process it + if (!visited.contains(vertex)) { + visited.add(vertex); + result.add(vertex); + + System.out.println("Visiting: " + vertex); + System.out.println("Stack: " + stack); + System.out.println("Visited so far: " + result); + System.out.println("------------------------------"); + + // Pause for demonstration + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Get sorted neighbors in reverse order for stack + List neighbors = getSortedNeighbors(vertex); + Collections.reverse(neighbors); + + // Push all unvisited neighbors onto the stack + for (String neighbor : neighbors) { + if (!visited.contains(neighbor)) { + stack.push(neighbor); + } + } + } + } + + return result; + } + + /** + * Print the graph structure + */ + public void visualizeGraph() { + System.out.println("\nGraph Structure:"); + System.out.println("------------------------------"); + + // Sort vertices for consistent output + List vertices = new ArrayList<>(adjacencyList.keySet()); + Collections.sort(vertices); + + for (String vertex : vertices) { + List neighbors = getSortedNeighbors(vertex); + System.out.println(vertex + " -> " + neighbors); + } + + System.out.println("------------------------------"); + } + } + + /** + * Create a sample graph for demonstration + * @return A sample graph + */ + public static Graph createSampleGraph() { + Graph g = new Graph(); + + // Add edges to build this graph: + // A + // / \ + // B C + // / \ \ + // D E---F + + String[][] edges = { + {"A", "B"}, {"A", "C"}, + {"B", "D"}, {"B", "E"}, + {"C", "F"}, {"E", "F"} + }; + + for (String[] edge : edges) { + g.addEdge(edge[0], edge[1]); + } + + return g; + } + + /** + * Main method to demonstrate graph traversal algorithms + * @param args Command line arguments (not used) + */ + public static void main(String[] args) { + // Create a sample graph + Graph g = createSampleGraph(); + g.visualizeGraph(); + + // Demonstrate BFS + System.out.println("\n=== BFS Traversal ==="); + List bfsResult = g.bfs("A"); + System.out.println("BFS Result: " + bfsResult); + + // Demonstrate recursive DFS + System.out.println("\n=== DFS Traversal (Recursive) ==="); + List dfsRecResult = g.dfsRecursive("A"); + System.out.println("DFS Recursive Result: " + dfsRecResult); + + // Demonstrate iterative DFS + System.out.println("\n=== DFS Traversal (Iterative) ==="); + List dfsIterResult = g.dfsIterative("A"); + System.out.println("DFS Iterative Result: " + dfsIterResult); + } +} \ No newline at end of file diff --git a/snippets/algorithms/graph-traversal/graphTraversal.js b/snippets/algorithms/graph-traversal/graphTraversal.js new file mode 100644 index 0000000..9b2c596 --- /dev/null +++ b/snippets/algorithms/graph-traversal/graphTraversal.js @@ -0,0 +1,229 @@ +/** + * Graph Traversal Algorithms in JavaScript + * + * This module provides implementations of BFS and DFS for graph traversal. + * + * Example usage: + * node graphTraversal.js + */ + +class Graph { + /** + * Initialize an empty graph + */ + constructor() { + this.adjacencyList = {}; + } + + /** + * Add a vertex to the graph + * @param {string|number} vertex - The vertex to add + */ + addVertex(vertex) { + if (!this.adjacencyList[vertex]) { + this.adjacencyList[vertex] = []; + } + } + + /** + * Add an edge between two vertices + * @param {string|number} v1 - First vertex + * @param {string|number} v2 - Second vertex + */ + addEdge(v1, v2) { + if (!this.adjacencyList[v1]) { + this.addVertex(v1); + } + if (!this.adjacencyList[v2]) { + this.addVertex(v2); + } + + this.adjacencyList[v1].push(v2); + this.adjacencyList[v2].push(v1); // For undirected graph + } + + /** + * Breadth-First Search traversal + * @param {string|number} start - Starting vertex + * @returns {Array} - Vertices in the order they were visited + */ + bfs(start) { + if (!this.adjacencyList[start]) return []; + + const queue = [start]; + const visited = { [start]: true }; + const result = []; + + console.log(`Starting BFS traversal from vertex ${start}`); + + while (queue.length) { + const currentVertex = queue.shift(); + result.push(currentVertex); + + console.log(`Visiting: ${currentVertex}`); + console.log(`Queue: [${queue.join(', ')}]`); + console.log(`Visited so far: [${result.join(', ')}]`); + console.log('-'.repeat(30)); + + // Get neighbors and sort for consistent order + const neighbors = [...this.adjacencyList[currentVertex]].sort(); + + for (const neighbor of neighbors) { + if (!visited[neighbor]) { + visited[neighbor] = true; + queue.push(neighbor); + } + } + } + + return result; + } + + /** + * Depth-First Search traversal (recursive) + * @param {string|number} start - Starting vertex + * @returns {Array} - Vertices in the order they were visited + */ + dfsRecursive(start) { + if (!this.adjacencyList[start]) return []; + + const visited = {}; + const result = []; + + console.log(`Starting recursive DFS traversal from vertex ${start}`); + + const dfs = (vertex) => { + visited[vertex] = true; + result.push(vertex); + + console.log(`Visiting: ${vertex}`); + console.log(`Visited so far: [${result.join(', ')}]`); + console.log('-'.repeat(30)); + + // Get neighbors and sort for consistent order + const neighbors = [...this.adjacencyList[vertex]].sort(); + + for (const neighbor of neighbors) { + if (!visited[neighbor]) { + dfs(neighbor); + } + } + }; + + dfs(start); + return result; + } + + /** + * Depth-First Search traversal (iterative) + * @param {string|number} start - Starting vertex + * @returns {Array} - Vertices in the order they were visited + */ + dfsIterative(start) { + if (!this.adjacencyList[start]) return []; + + const stack = [start]; + const visited = {}; + const result = []; + + console.log(`Starting iterative DFS traversal from vertex ${start}`); + + while (stack.length) { + const currentVertex = stack.pop(); + + if (!visited[currentVertex]) { + visited[currentVertex] = true; + result.push(currentVertex); + + console.log(`Visiting: ${currentVertex}`); + console.log(`Stack: [${stack.join(', ')}]`); + console.log(`Visited so far: [${result.join(', ')}]`); + console.log('-'.repeat(30)); + + // Get neighbors and sort in reverse order for stack + const neighbors = [...this.adjacencyList[currentVertex]].sort().reverse(); + + for (const neighbor of neighbors) { + if (!visited[neighbor]) { + stack.push(neighbor); + } + } + } + } + + return result; + } + + /** + * Print a visualization of the graph structure + */ + visualizeGraph() { + console.log('\nGraph Structure:'); + console.log('-'.repeat(30)); + + // Sort the vertices for consistent output + const vertices = Object.keys(this.adjacencyList).sort(); + + for (const vertex of vertices) { + const neighbors = [...this.adjacencyList[vertex]].sort(); + console.log(`${vertex} -> [${neighbors.join(', ')}]`); + } + + console.log('-'.repeat(30)); + } +} + +/** + * Create a sample graph for demonstration + * @returns {Graph} - A sample graph + */ +function createSampleGraph() { + const g = new Graph(); + + // Add edges to build this graph: + // A + // / \ + // B C + // / \ \ + // D E---F + + const edges = [ + ['A', 'B'], ['A', 'C'], + ['B', 'D'], ['B', 'E'], + ['C', 'F'], ['E', 'F'] + ]; + + for (const [v1, v2] of edges) { + g.addEdge(v1, v2); + } + + return g; +} + +/** + * Main function to demonstrate graph traversal algorithms + */ +function main() { + const g = createSampleGraph(); + g.visualizeGraph(); + + console.log('\n=== BFS Traversal ==='); + const bfsResult = g.bfs('A'); + console.log(`BFS Result: [${bfsResult.join(', ')}]`); + + console.log('\n=== DFS Traversal (Recursive) ==='); + const dfsRecResult = g.dfsRecursive('A'); + console.log(`DFS Recursive Result: [${dfsRecResult.join(', ')}]`); + + console.log('\n=== DFS Traversal (Iterative) ==='); + const dfsIterResult = g.dfsIterative('A'); + console.log(`DFS Iterative Result: [${dfsIterResult.join(', ')}]`); +} + +// Run the main function if this file is executed directly +if (require.main === module) { + main(); +} + +// Export the Graph class for use in other modules +module.exports = { Graph, createSampleGraph }; \ No newline at end of file diff --git a/snippets/algorithms/graph-traversal/graph_traversal.c b/snippets/algorithms/graph-traversal/graph_traversal.c new file mode 100644 index 0000000..3ee5ea9 --- /dev/null +++ b/snippets/algorithms/graph-traversal/graph_traversal.c @@ -0,0 +1,542 @@ +#include +#include +#include +#include + +/** + * Graph Traversal Algorithms in C + * + * This program demonstrates BFS and DFS traversal algorithms on a graph. + * + * Compile: gcc graph_traversal.c -o graph_traversal + * Run: ./graph_traversal + */ + +#define MAX_VERTICES 10 +#define MAX_VERTEX_NAME 10 + +// Graph structure using adjacency matrix +typedef struct { + char vertices[MAX_VERTICES][MAX_VERTEX_NAME]; + int adjacency_matrix[MAX_VERTICES][MAX_VERTICES]; + int num_vertices; +} Graph; + +// Queue for BFS traversal +typedef struct { + int items[MAX_VERTICES]; + int front; + int rear; +} Queue; + +// Stack for iterative DFS traversal +typedef struct { + int items[MAX_VERTICES]; + int top; +} Stack; + +// Queue functions +Queue* create_queue() { + Queue* q = (Queue*)malloc(sizeof(Queue)); + q->front = -1; + q->rear = -1; + return q; +} + +int is_queue_empty(Queue* q) { + return q->rear == -1; +} + +void enqueue(Queue* q, int value) { + if (q->rear == MAX_VERTICES - 1) + return; + else { + if (q->front == -1) + q->front = 0; + q->rear++; + q->items[q->rear] = value; + } +} + +int dequeue(Queue* q) { + int item; + if (is_queue_empty(q)) { + return -1; + } else { + item = q->items[q->front]; + q->front++; + if (q->front > q->rear) { + q->front = q->rear = -1; + } + return item; + } +} + +// Stack functions +Stack* create_stack() { + Stack* s = (Stack*)malloc(sizeof(Stack)); + s->top = -1; + return s; +} + +int is_stack_empty(Stack* s) { + return s->top == -1; +} + +void push(Stack* s, int value) { + if (s->top == MAX_VERTICES - 1) + return; + else { + s->top++; + s->items[s->top] = value; + } +} + +int pop(Stack* s) { + int item; + if (is_stack_empty(s)) { + return -1; + } else { + item = s->items[s->top]; + s->top--; + return item; + } +} + +// Graph functions +Graph* create_graph() { + Graph* g = (Graph*)malloc(sizeof(Graph)); + g->num_vertices = 0; + + // Initialize adjacency matrix to 0 + for (int i = 0; i < MAX_VERTICES; i++) { + for (int j = 0; j < MAX_VERTICES; j++) { + g->adjacency_matrix[i][j] = 0; + } + } + + return g; +} + +// Add a vertex to the graph +int add_vertex(Graph* g, const char* vertex) { + if (g->num_vertices >= MAX_VERTICES) { + printf("Graph is full, cannot add more vertices\n"); + return -1; + } + + // Check if vertex already exists + for (int i = 0; i < g->num_vertices; i++) { + if (strcmp(g->vertices[i], vertex) == 0) { + return i; + } + } + + // Add new vertex + strcpy(g->vertices[g->num_vertices], vertex); + return g->num_vertices++; +} + +// Add an edge between two vertices +void add_edge(Graph* g, const char* v1, const char* v2) { + // Ensure both vertices exist + int v1_idx = add_vertex(g, v1); + int v2_idx = add_vertex(g, v2); + + if (v1_idx == -1 || v2_idx == -1) { + return; + } + + // Add the edge (undirected graph) + g->adjacency_matrix[v1_idx][v2_idx] = 1; + g->adjacency_matrix[v2_idx][v1_idx] = 1; +} + +// Print neighbors in sorted order +void print_sorted_neighbors(Graph* g, int vertex_idx) { + // Create a sorted copy of neighbors + int neighbors[MAX_VERTICES]; + int count = 0; + + for (int i = 0; i < g->num_vertices; i++) { + if (g->adjacency_matrix[vertex_idx][i]) { + neighbors[count++] = i; + } + } + + // Simple bubble sort + for (int i = 0; i < count - 1; i++) { + for (int j = 0; j < count - i - 1; j++) { + if (strcmp(g->vertices[neighbors[j]], g->vertices[neighbors[j + 1]]) > 0) { + int temp = neighbors[j]; + neighbors[j] = neighbors[j + 1]; + neighbors[j + 1] = temp; + } + } + } + + printf("["); + for (int i = 0; i < count; i++) { + printf("%s", g->vertices[neighbors[i]]); + if (i < count - 1) { + printf(", "); + } + } + printf("]"); +} + +// Get the index of a vertex by name +int get_vertex_index(Graph* g, const char* vertex) { + for (int i = 0; i < g->num_vertices; i++) { + if (strcmp(g->vertices[i], vertex) == 0) { + return i; + } + } + return -1; +} + +// BFS traversal +void bfs(Graph* g, const char* start) { + int start_idx = get_vertex_index(g, start); + if (start_idx == -1) { + printf("Starting vertex not found\n"); + return; + } + + int visited[MAX_VERTICES] = {0}; + int result[MAX_VERTICES]; + int result_count = 0; + + Queue* q = create_queue(); + + // Initialize with starting vertex + visited[start_idx] = 1; + enqueue(q, start_idx); + + printf("Starting BFS traversal from vertex %s\n", start); + + while (!is_queue_empty(q)) { + // Dequeue the first vertex + int vertex = dequeue(q); + result[result_count++] = vertex; + + printf("Visiting: %s\n", g->vertices[vertex]); + + // Print queue contents + printf("Queue: ["); + for (int i = q->front; i <= q->rear; i++) { + printf("%s", g->vertices[q->items[i]]); + if (i < q->rear) { + printf(", "); + } + } + printf("]\n"); + + // Print visited vertices + printf("Visited so far: ["); + for (int i = 0; i < result_count; i++) { + printf("%s", g->vertices[result[i]]); + if (i < result_count - 1) { + printf(", "); + } + } + printf("]\n"); + printf("------------------------------\n"); + + // Pause for demonstration + usleep(500000); // 500ms + + // Visit neighbors in sorted order + int neighbors[MAX_VERTICES]; + int count = 0; + + for (int i = 0; i < g->num_vertices; i++) { + if (g->adjacency_matrix[vertex][i]) { + neighbors[count++] = i; + } + } + + // Simple bubble sort + for (int i = 0; i < count - 1; i++) { + for (int j = 0; j < count - i - 1; j++) { + if (strcmp(g->vertices[neighbors[j]], g->vertices[neighbors[j + 1]]) > 0) { + int temp = neighbors[j]; + neighbors[j] = neighbors[j + 1]; + neighbors[j + 1] = temp; + } + } + } + + // Enqueue all unvisited neighbors + for (int i = 0; i < count; i++) { + int neighbor = neighbors[i]; + if (!visited[neighbor]) { + visited[neighbor] = 1; + enqueue(q, neighbor); + } + } + } + + free(q); + + // Print final result + printf("BFS Result: ["); + for (int i = 0; i < result_count; i++) { + printf("%s", g->vertices[result[i]]); + if (i < result_count - 1) { + printf(", "); + } + } + printf("]\n"); +} + +// Helper for recursive DFS +void dfs_helper(Graph* g, int vertex, int* visited, int* result, int* result_count) { + // Mark as visited and add to result + visited[vertex] = 1; + result[(*result_count)++] = vertex; + + printf("Visiting: %s\n", g->vertices[vertex]); + + // Print visited vertices + printf("Visited so far: ["); + for (int i = 0; i < *result_count; i++) { + printf("%s", g->vertices[result[i]]); + if (i < *result_count - 1) { + printf(", "); + } + } + printf("]\n"); + printf("------------------------------\n"); + + // Pause for demonstration + usleep(500000); // 500ms + + // Visit neighbors in sorted order + int neighbors[MAX_VERTICES]; + int count = 0; + + for (int i = 0; i < g->num_vertices; i++) { + if (g->adjacency_matrix[vertex][i]) { + neighbors[count++] = i; + } + } + + // Simple bubble sort + for (int i = 0; i < count - 1; i++) { + for (int j = 0; j < count - i - 1; j++) { + if (strcmp(g->vertices[neighbors[j]], g->vertices[neighbors[j + 1]]) > 0) { + int temp = neighbors[j]; + neighbors[j] = neighbors[j + 1]; + neighbors[j + 1] = temp; + } + } + } + + // Recursively visit all unvisited neighbors + for (int i = 0; i < count; i++) { + int neighbor = neighbors[i]; + if (!visited[neighbor]) { + dfs_helper(g, neighbor, visited, result, result_count); + } + } +} + +// DFS traversal (recursive) +void dfs_recursive(Graph* g, const char* start) { + int start_idx = get_vertex_index(g, start); + if (start_idx == -1) { + printf("Starting vertex not found\n"); + return; + } + + int visited[MAX_VERTICES] = {0}; + int result[MAX_VERTICES]; + int result_count = 0; + + printf("Starting recursive DFS traversal from vertex %s\n", start); + + dfs_helper(g, start_idx, visited, result, &result_count); + + // Print final result + printf("DFS Recursive Result: ["); + for (int i = 0; i < result_count; i++) { + printf("%s", g->vertices[result[i]]); + if (i < result_count - 1) { + printf(", "); + } + } + printf("]\n"); +} + +// DFS traversal (iterative) +void dfs_iterative(Graph* g, const char* start) { + int start_idx = get_vertex_index(g, start); + if (start_idx == -1) { + printf("Starting vertex not found\n"); + return; + } + + int visited[MAX_VERTICES] = {0}; + int result[MAX_VERTICES]; + int result_count = 0; + + Stack* s = create_stack(); + + // Initialize with starting vertex + push(s, start_idx); + + printf("Starting iterative DFS traversal from vertex %s\n", start); + + while (!is_stack_empty(s)) { + // Pop the top vertex + int vertex = pop(s); + + // If not visited, process it + if (!visited[vertex]) { + visited[vertex] = 1; + result[result_count++] = vertex; + + printf("Visiting: %s\n", g->vertices[vertex]); + + // Print stack contents + printf("Stack: ["); + for (int i = 0; i <= s->top; i++) { + printf("%s", g->vertices[s->items[i]]); + if (i < s->top) { + printf(", "); + } + } + printf("]\n"); + + // Print visited vertices + printf("Visited so far: ["); + for (int i = 0; i < result_count; i++) { + printf("%s", g->vertices[result[i]]); + if (i < result_count - 1) { + printf(", "); + } + } + printf("]\n"); + printf("------------------------------\n"); + + // Pause for demonstration + usleep(500000); // 500ms + + // Visit neighbors in reverse sorted order for stack + int neighbors[MAX_VERTICES]; + int count = 0; + + for (int i = 0; i < g->num_vertices; i++) { + if (g->adjacency_matrix[vertex][i]) { + neighbors[count++] = i; + } + } + + // Simple bubble sort (in reverse order) + for (int i = 0; i < count - 1; i++) { + for (int j = 0; j < count - i - 1; j++) { + if (strcmp(g->vertices[neighbors[j]], g->vertices[neighbors[j + 1]]) < 0) { + int temp = neighbors[j]; + neighbors[j] = neighbors[j + 1]; + neighbors[j + 1] = temp; + } + } + } + + // Push all unvisited neighbors onto the stack + for (int i = 0; i < count; i++) { + int neighbor = neighbors[i]; + if (!visited[neighbor]) { + push(s, neighbor); + } + } + } + } + + free(s); + + // Print final result + printf("DFS Iterative Result: ["); + for (int i = 0; i < result_count; i++) { + printf("%s", g->vertices[result[i]]); + if (i < result_count - 1) { + printf(", "); + } + } + printf("]\n"); +} + +// Visualize the graph structure +void visualize_graph(Graph* g) { + printf("\nGraph Structure:\n"); + printf("------------------------------\n"); + + // Sort vertices for consistent output + int sorted_indices[MAX_VERTICES]; + for (int i = 0; i < g->num_vertices; i++) { + sorted_indices[i] = i; + } + + // Simple bubble sort + for (int i = 0; i < g->num_vertices - 1; i++) { + for (int j = 0; j < g->num_vertices - i - 1; j++) { + if (strcmp(g->vertices[sorted_indices[j]], g->vertices[sorted_indices[j + 1]]) > 0) { + int temp = sorted_indices[j]; + sorted_indices[j] = sorted_indices[j + 1]; + sorted_indices[j + 1] = temp; + } + } + } + + for (int i = 0; i < g->num_vertices; i++) { + int vertex = sorted_indices[i]; + printf("%s -> ", g->vertices[vertex]); + print_sorted_neighbors(g, vertex); + printf("\n"); + } + + printf("------------------------------\n"); +} + +// Create a sample graph for demonstration +Graph* create_sample_graph() { + Graph* g = create_graph(); + + // Add edges to build this graph: + // A + // / \ + // B C + // / \ \ + // D E---F + + add_edge(g, "A", "B"); + add_edge(g, "A", "C"); + add_edge(g, "B", "D"); + add_edge(g, "B", "E"); + add_edge(g, "C", "F"); + add_edge(g, "E", "F"); + + return g; +} + +int main() { + // Create a sample graph + Graph* g = create_sample_graph(); + visualize_graph(g); + + // Demonstrate BFS + printf("\n=== BFS Traversal ===\n"); + bfs(g, "A"); + + // Demonstrate recursive DFS + printf("\n=== DFS Traversal (Recursive) ===\n"); + dfs_recursive(g, "A"); + + // Demonstrate iterative DFS + printf("\n=== DFS Traversal (Iterative) ===\n"); + dfs_iterative(g, "A"); + + free(g); + return 0; +} \ No newline at end of file diff --git a/snippets/algorithms/graph-traversal/graph_traversal.cpp b/snippets/algorithms/graph-traversal/graph_traversal.cpp new file mode 100644 index 0000000..a63c87d --- /dev/null +++ b/snippets/algorithms/graph-traversal/graph_traversal.cpp @@ -0,0 +1,318 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Graph Traversal Algorithms in C++ + * + * This program demonstrates BFS and DFS traversal algorithms on a graph. + * + * Compile: g++ -std=c++11 graph_traversal.cpp -o graph_traversal + * Run: ./graph_traversal + */ + +class Graph { +private: + // Adjacency list representation + std::unordered_map> adjacencyList; + + // Helper function to get sorted neighbors for consistent output + std::vector getSortedNeighbors(const std::string& vertex) const { + std::vector neighbors = adjacencyList.at(vertex); + std::sort(neighbors.begin(), neighbors.end()); + return neighbors; + } + + // Helper for recursive DFS + void dfsHelper(const std::string& vertex, std::unordered_set& visited, + std::vector& result) const { + // Mark as visited and add to result + visited.insert(vertex); + result.push_back(vertex); + + std::cout << "Visiting: " << vertex << std::endl; + std::cout << "Visited so far: ["; + for (size_t i = 0; i < result.size(); ++i) { + std::cout << result[i]; + if (i < result.size() - 1) std::cout << ", "; + } + std::cout << "]" << std::endl; + std::cout << "------------------------------" << std::endl; + + // Pause for demonstration + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // Get sorted neighbors for consistent order + std::vector neighbors = getSortedNeighbors(vertex); + + // Recursively visit all unvisited neighbors + for (const auto& neighbor : neighbors) { + if (visited.find(neighbor) == visited.end()) { + dfsHelper(neighbor, visited, result); + } + } + } + +public: + // Add a vertex to the graph + void addVertex(const std::string& vertex) { + if (adjacencyList.find(vertex) == adjacencyList.end()) { + adjacencyList[vertex] = std::vector(); + } + } + + // Add an edge between two vertices + void addEdge(const std::string& v1, const std::string& v2) { + // Ensure both vertices exist + addVertex(v1); + addVertex(v2); + + // Add the edge (undirected graph) + adjacencyList[v1].push_back(v2); + adjacencyList[v2].push_back(v1); + } + + // Breadth-First Search traversal + std::vector bfs(const std::string& start) const { + if (adjacencyList.find(start) == adjacencyList.end()) { + return {}; + } + + std::unordered_set visited; + std::queue queue; + std::vector result; + + // Initialize with starting vertex + visited.insert(start); + queue.push(start); + + std::cout << "Starting BFS traversal from vertex " << start << std::endl; + + while (!queue.empty()) { + // Dequeue the first vertex + std::string vertex = queue.front(); + queue.pop(); + result.push_back(vertex); + + std::cout << "Visiting: " << vertex << std::endl; + + // Print queue contents + std::cout << "Queue: ["; + std::queue queueCopy = queue; + bool first = true; + while (!queueCopy.empty()) { + if (!first) std::cout << ", "; + std::cout << queueCopy.front(); + queueCopy.pop(); + first = false; + } + std::cout << "]" << std::endl; + + // Print visited vertices + std::cout << "Visited so far: ["; + for (size_t i = 0; i < result.size(); ++i) { + std::cout << result[i]; + if (i < result.size() - 1) std::cout << ", "; + } + std::cout << "]" << std::endl; + std::cout << "------------------------------" << std::endl; + + // Pause for demonstration + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // Get sorted neighbors for consistent order + std::vector neighbors = getSortedNeighbors(vertex); + + // Enqueue all unvisited neighbors + for (const auto& neighbor : neighbors) { + if (visited.find(neighbor) == visited.end()) { + visited.insert(neighbor); + queue.push(neighbor); + } + } + } + + return result; + } + + // Depth-First Search traversal (recursive) + std::vector dfsRecursive(const std::string& start) const { + if (adjacencyList.find(start) == adjacencyList.end()) { + return {}; + } + + std::unordered_set visited; + std::vector result; + + std::cout << "Starting recursive DFS traversal from vertex " << start << std::endl; + + dfsHelper(start, visited, result); + + return result; + } + + // Depth-First Search traversal (iterative) + std::vector dfsIterative(const std::string& start) const { + if (adjacencyList.find(start) == adjacencyList.end()) { + return {}; + } + + std::unordered_set visited; + std::stack stack; + std::vector result; + + // Initialize with starting vertex + stack.push(start); + + std::cout << "Starting iterative DFS traversal from vertex " << start << std::endl; + + while (!stack.empty()) { + // Pop the top vertex + std::string vertex = stack.top(); + stack.pop(); + + // If not visited, process it + if (visited.find(vertex) == visited.end()) { + visited.insert(vertex); + result.push_back(vertex); + + std::cout << "Visiting: " << vertex << std::endl; + + // Print stack contents (in reverse order since it's LIFO) + std::cout << "Stack: ["; + std::vector stackContents; + std::stack stackCopy = stack; + while (!stackCopy.empty()) { + stackContents.push_back(stackCopy.top()); + stackCopy.pop(); + } + std::reverse(stackContents.begin(), stackContents.end()); + for (size_t i = 0; i < stackContents.size(); ++i) { + std::cout << stackContents[i]; + if (i < stackContents.size() - 1) std::cout << ", "; + } + std::cout << "]" << std::endl; + + // Print visited vertices + std::cout << "Visited so far: ["; + for (size_t i = 0; i < result.size(); ++i) { + std::cout << result[i]; + if (i < result.size() - 1) std::cout << ", "; + } + std::cout << "]" << std::endl; + std::cout << "------------------------------" << std::endl; + + // Pause for demonstration + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // Get sorted neighbors in reverse order for stack + std::vector neighbors = getSortedNeighbors(vertex); + std::reverse(neighbors.begin(), neighbors.end()); + + // Push all unvisited neighbors onto the stack + for (const auto& neighbor : neighbors) { + if (visited.find(neighbor) == visited.end()) { + stack.push(neighbor); + } + } + } + } + + return result; + } + + // Print the graph structure + void visualizeGraph() const { + std::cout << "\nGraph Structure:" << std::endl; + std::cout << "------------------------------" << std::endl; + + // Sort vertices for consistent output + std::vector vertices; + for (const auto& pair : adjacencyList) { + vertices.push_back(pair.first); + } + std::sort(vertices.begin(), vertices.end()); + + for (const auto& vertex : vertices) { + std::vector neighbors = getSortedNeighbors(vertex); + + std::cout << vertex << " -> ["; + for (size_t i = 0; i < neighbors.size(); ++i) { + std::cout << neighbors[i]; + if (i < neighbors.size() - 1) std::cout << ", "; + } + std::cout << "]" << std::endl; + } + + std::cout << "------------------------------" << std::endl; + } +}; + +// Create a sample graph for demonstration +Graph createSampleGraph() { + Graph g; + + // Add edges to build this graph: + // A + // / \ + // B C + // / \ \ + // D E---F + + std::vector> edges = { + {"A", "B"}, {"A", "C"}, + {"B", "D"}, {"B", "E"}, + {"C", "F"}, {"E", "F"} + }; + + for (const auto& edge : edges) { + g.addEdge(edge.first, edge.second); + } + + return g; +} + +// Main function +int main() { + // Create a sample graph + Graph g = createSampleGraph(); + g.visualizeGraph(); + + // Demonstrate BFS + std::cout << "\n=== BFS Traversal ===" << std::endl; + std::vector bfsResult = g.bfs("A"); + std::cout << "BFS Result: ["; + for (size_t i = 0; i < bfsResult.size(); ++i) { + std::cout << bfsResult[i]; + if (i < bfsResult.size() - 1) std::cout << ", "; + } + std::cout << "]" << std::endl; + + // Demonstrate recursive DFS + std::cout << "\n=== DFS Traversal (Recursive) ===" << std::endl; + std::vector dfsRecResult = g.dfsRecursive("A"); + std::cout << "DFS Recursive Result: ["; + for (size_t i = 0; i < dfsRecResult.size(); ++i) { + std::cout << dfsRecResult[i]; + if (i < dfsRecResult.size() - 1) std::cout << ", "; + } + std::cout << "]" << std::endl; + + // Demonstrate iterative DFS + std::cout << "\n=== DFS Traversal (Iterative) ===" << std::endl; + std::vector dfsIterResult = g.dfsIterative("A"); + std::cout << "DFS Iterative Result: ["; + for (size_t i = 0; i < dfsIterResult.size(); ++i) { + std::cout << dfsIterResult[i]; + if (i < dfsIterResult.size() - 1) std::cout << ", "; + } + std::cout << "]" << std::endl; + + return 0; +} \ No newline at end of file diff --git a/snippets/algorithms/graph-traversal/graph_traversal.go b/snippets/algorithms/graph-traversal/graph_traversal.go new file mode 100644 index 0000000..56a5d72 --- /dev/null +++ b/snippets/algorithms/graph-traversal/graph_traversal.go @@ -0,0 +1,242 @@ +package main + +import ( + "fmt" + "sort" + "time" +) + +// Graph represents an undirected graph using an adjacency list +type Graph struct { + adjacencyList map[string][]string +} + +// NewGraph creates a new empty graph +func NewGraph() *Graph { + return &Graph{ + adjacencyList: make(map[string][]string), + } +} + +// AddVertex adds a vertex to the graph +func (g *Graph) AddVertex(vertex string) { + if _, exists := g.adjacencyList[vertex]; !exists { + g.adjacencyList[vertex] = []string{} + } +} + +// AddEdge adds an edge between two vertices +func (g *Graph) AddEdge(v1, v2 string) { + // Ensure both vertices exist + g.AddVertex(v1) + g.AddVertex(v2) + + // Add the edge (for undirected graph) + g.adjacencyList[v1] = append(g.adjacencyList[v1], v2) + g.adjacencyList[v2] = append(g.adjacencyList[v2], v1) +} + +// BFS performs a breadth-first search traversal starting from the given vertex +func (g *Graph) BFS(start string) []string { + if _, exists := g.adjacencyList[start]; !exists { + return []string{} + } + + // Initialize data structures + visited := make(map[string]bool) + visited[start] = true + + queue := []string{start} + result := []string{} + + fmt.Printf("Starting BFS traversal from vertex %s\n", start) + + // BFS traversal + for len(queue) > 0 { + // Dequeue the first vertex + vertex := queue[0] + queue = queue[1:] + result = append(result, vertex) + + fmt.Printf("Visiting: %s\n", vertex) + fmt.Printf("Queue: %v\n", queue) + fmt.Printf("Visited so far: %v\n", result) + fmt.Println("------------------------------") + time.Sleep(500 * time.Millisecond) // Slow down for demonstration + + // Get sorted neighbors for consistent order + neighbors := g.getSortedNeighbors(vertex) + + // Enqueue all unvisited neighbors + for _, neighbor := range neighbors { + if !visited[neighbor] { + visited[neighbor] = true + queue = append(queue, neighbor) + } + } + } + + return result +} + +// DFSRecursive performs a recursive depth-first search traversal starting from the given vertex +func (g *Graph) DFSRecursive(start string) []string { + if _, exists := g.adjacencyList[start]; !exists { + return []string{} + } + + // Initialize data structures + visited := make(map[string]bool) + result := []string{} + + fmt.Printf("Starting recursive DFS traversal from vertex %s\n", start) + + // Define the recursive helper function + var dfs func(vertex string) + dfs = func(vertex string) { + // Mark as visited and add to result + visited[vertex] = true + result = append(result, vertex) + + fmt.Printf("Visiting: %s\n", vertex) + fmt.Printf("Visited so far: %v\n", result) + fmt.Println("------------------------------") + time.Sleep(500 * time.Millisecond) // Slow down for demonstration + + // Get sorted neighbors for consistent order + neighbors := g.getSortedNeighbors(vertex) + + // Recursively visit all unvisited neighbors + for _, neighbor := range neighbors { + if !visited[neighbor] { + dfs(neighbor) + } + } + } + + // Start the DFS traversal + dfs(start) + return result +} + +// DFSIterative performs an iterative depth-first search traversal starting from the given vertex +func (g *Graph) DFSIterative(start string) []string { + if _, exists := g.adjacencyList[start]; !exists { + return []string{} + } + + // Initialize data structures + visited := make(map[string]bool) + stack := []string{start} + result := []string{} + + fmt.Printf("Starting iterative DFS traversal from vertex %s\n", start) + + // DFS traversal using a stack + for len(stack) > 0 { + // Pop the last vertex from the stack + lastIndex := len(stack) - 1 + vertex := stack[lastIndex] + stack = stack[:lastIndex] + + // If not visited, process it + if !visited[vertex] { + visited[vertex] = true + result = append(result, vertex) + + fmt.Printf("Visiting: %s\n", vertex) + fmt.Printf("Stack: %v\n", stack) + fmt.Printf("Visited so far: %v\n", result) + fmt.Println("------------------------------") + time.Sleep(500 * time.Millisecond) // Slow down for demonstration + + // Get sorted neighbors in reverse order for stack + neighbors := g.getSortedNeighbors(vertex) + // Reverse the order for stack to simulate recursive DFS + for i, j := 0, len(neighbors)-1; i < j; i, j = i+1, j-1 { + neighbors[i], neighbors[j] = neighbors[j], neighbors[i] + } + + // Push all unvisited neighbors onto the stack + for _, neighbor := range neighbors { + if !visited[neighbor] { + stack = append(stack, neighbor) + } + } + } + } + + return result +} + +// VisualizeGraph prints a visualization of the graph structure +func (g *Graph) VisualizeGraph() { + fmt.Println("\nGraph Structure:") + fmt.Println("------------------------------") + + // Sort vertices for consistent output + var vertices []string + for vertex := range g.adjacencyList { + vertices = append(vertices, vertex) + } + sort.Strings(vertices) + + for _, vertex := range vertices { + neighbors := g.getSortedNeighbors(vertex) + fmt.Printf("%s -> %v\n", vertex, neighbors) + } + + fmt.Println("------------------------------") +} + +// getSortedNeighbors returns the sorted neighbors of a vertex +func (g *Graph) getSortedNeighbors(vertex string) []string { + neighbors := g.adjacencyList[vertex] + sort.Strings(neighbors) + return neighbors +} + +// CreateSampleGraph creates a sample graph for demonstration +func CreateSampleGraph() *Graph { + g := NewGraph() + + // Add edges to build this graph: + // A + // / \ + // B C + // / \ \ + // D E---F + + edges := [][2]string{ + {"A", "B"}, {"A", "C"}, + {"B", "D"}, {"B", "E"}, + {"C", "F"}, {"E", "F"}, + } + + for _, edge := range edges { + g.AddEdge(edge[0], edge[1]) + } + + return g +} + +func main() { + // Create a sample graph + g := CreateSampleGraph() + g.VisualizeGraph() + + // Demonstrate BFS + fmt.Println("\n=== BFS Traversal ===") + bfsResult := g.BFS("A") + fmt.Printf("BFS Result: %v\n", bfsResult) + + // Demonstrate recursive DFS + fmt.Println("\n=== DFS Traversal (Recursive) ===") + dfsRecResult := g.DFSRecursive("A") + fmt.Printf("DFS Recursive Result: %v\n", dfsRecResult) + + // Demonstrate iterative DFS + fmt.Println("\n=== DFS Traversal (Iterative) ===") + dfsIterResult := g.DFSIterative("A") + fmt.Printf("DFS Iterative Result: %v\n", dfsIterResult) +} \ No newline at end of file diff --git a/snippets/algorithms/graph-traversal/graph_traversal.php b/snippets/algorithms/graph-traversal/graph_traversal.php new file mode 100644 index 0000000..17175b2 --- /dev/null +++ b/snippets/algorithms/graph-traversal/graph_traversal.php @@ -0,0 +1,268 @@ +adjacencyList[$vertex])) { + $this->adjacencyList[$vertex] = []; + } + } + + /** + * Adds an edge between two vertices + * + * @param string $v1 First vertex + * @param string $v2 Second vertex + */ + public function addEdge($v1, $v2) { + // Ensure both vertices exist + $this->addVertex($v1); + $this->addVertex($v2); + + // Add the edge (undirected graph) + $this->adjacencyList[$v1][] = $v2; + $this->adjacencyList[$v2][] = $v1; + } + + /** + * Helper method to get sorted neighbors for consistent output + * + * @param string $vertex The vertex to get neighbors for + * @return array Sorted array of neighbors + */ + private function getSortedNeighbors($vertex) { + $neighbors = $this->adjacencyList[$vertex]; + sort($neighbors); + return $neighbors; + } + + /** + * Performs a breadth-first search traversal starting from the given vertex + * + * @param string $start Starting vertex + * @return array Array of vertices in the order they were visited + */ + public function bfs($start) { + if (!isset($this->adjacencyList[$start])) { + return []; + } + + $visited = [$start => true]; + $queue = [$start]; + $result = []; + + echo "Starting BFS traversal from vertex $start\n"; + + while (count($queue) > 0) { + // Dequeue the first vertex + $vertex = array_shift($queue); + $result[] = $vertex; + + echo "Visiting: $vertex\n"; + echo "Queue: [" . implode(", ", $queue) . "]\n"; + echo "Visited so far: [" . implode(", ", $result) . "]\n"; + echo "------------------------------\n"; + + // Pause for demonstration + usleep(500000); // 500ms + + // Get sorted neighbors for consistent order + $neighbors = $this->getSortedNeighbors($vertex); + + // Enqueue all unvisited neighbors + foreach ($neighbors as $neighbor) { + if (!isset($visited[$neighbor])) { + $visited[$neighbor] = true; + $queue[] = $neighbor; + } + } + } + + return $result; + } + + /** + * Performs a recursive depth-first search traversal starting from the given vertex + * + * @param string $start Starting vertex + * @return array Array of vertices in the order they were visited + */ + public function dfsRecursive($start) { + if (!isset($this->adjacencyList[$start])) { + return []; + } + + $visited = []; + $result = []; + + echo "Starting recursive DFS traversal from vertex $start\n"; + + $this->dfsHelper($start, $visited, $result); + + return $result; + } + + /** + * Helper method for recursive DFS + * + * @param string $vertex Current vertex + * @param array &$visited Reference to array of visited vertices + * @param array &$result Reference to array to store the traversal result + */ + private function dfsHelper($vertex, &$visited, &$result) { + // Mark as visited and add to result + $visited[$vertex] = true; + $result[] = $vertex; + + echo "Visiting: $vertex\n"; + echo "Visited so far: [" . implode(", ", $result) . "]\n"; + echo "------------------------------\n"; + + // Pause for demonstration + usleep(500000); // 500ms + + // Get sorted neighbors for consistent order + $neighbors = $this->getSortedNeighbors($vertex); + + // Recursively visit all unvisited neighbors + foreach ($neighbors as $neighbor) { + if (!isset($visited[$neighbor])) { + $this->dfsHelper($neighbor, $visited, $result); + } + } + } + + /** + * Performs an iterative depth-first search traversal starting from the given vertex + * + * @param string $start Starting vertex + * @return array Array of vertices in the order they were visited + */ + public function dfsIterative($start) { + if (!isset($this->adjacencyList[$start])) { + return []; + } + + $visited = []; + $stack = [$start]; + $result = []; + + echo "Starting iterative DFS traversal from vertex $start\n"; + + while (count($stack) > 0) { + // Pop the top vertex + $vertex = array_pop($stack); + + // If not visited, process it + if (!isset($visited[$vertex])) { + $visited[$vertex] = true; + $result[] = $vertex; + + echo "Visiting: $vertex\n"; + echo "Stack: [" . implode(", ", $stack) . "]\n"; + echo "Visited so far: [" . implode(", ", $result) . "]\n"; + echo "------------------------------\n"; + + // Pause for demonstration + usleep(500000); // 500ms + + // Get sorted neighbors in reverse order for stack + $neighbors = $this->getSortedNeighbors($vertex); + $neighbors = array_reverse($neighbors); + + // Push all unvisited neighbors onto the stack + foreach ($neighbors as $neighbor) { + if (!isset($visited[$neighbor])) { + $stack[] = $neighbor; + } + } + } + } + + return $result; + } + + /** + * Prints a visualization of the graph structure + */ + public function visualizeGraph() { + echo "\nGraph Structure:\n"; + echo "------------------------------\n"; + + // Sort vertices for consistent output + $vertices = array_keys($this->adjacencyList); + sort($vertices); + + foreach ($vertices as $vertex) { + $neighbors = $this->getSortedNeighbors($vertex); + echo "$vertex -> [" . implode(", ", $neighbors) . "]\n"; + } + + echo "------------------------------\n"; + } +} + +/** + * Creates a sample graph for demonstration + * + * @return Graph A sample graph + */ +function createSampleGraph() { + $g = new Graph(); + + // Add edges to build this graph: + // A + // / \ + // B C + // / \ \ + // D E---F + + $edges = [ + ["A", "B"], ["A", "C"], + ["B", "D"], ["B", "E"], + ["C", "F"], ["E", "F"] + ]; + + foreach ($edges as $edge) { + $g->addEdge($edge[0], $edge[1]); + } + + return $g; +} + +// Main execution +$g = createSampleGraph(); +$g->visualizeGraph(); + +// Demonstrate BFS +echo "\n=== BFS Traversal ===\n"; +$bfsResult = $g->bfs("A"); +echo "BFS Result: [" . implode(", ", $bfsResult) . "]\n"; + +// Demonstrate recursive DFS +echo "\n=== DFS Traversal (Recursive) ===\n"; +$dfsRecResult = $g->dfsRecursive("A"); +echo "DFS Recursive Result: [" . implode(", ", $dfsRecResult) . "]\n"; + +// Demonstrate iterative DFS +echo "\n=== DFS Traversal (Iterative) ===\n"; +$dfsIterResult = $g->dfsIterative("A"); +echo "DFS Iterative Result: [" . implode(", ", $dfsIterResult) . "]\n"; +?> \ No newline at end of file diff --git a/snippets/algorithms/graph-traversal/graph_traversal.py b/snippets/algorithms/graph-traversal/graph_traversal.py new file mode 100644 index 0000000..364ec40 --- /dev/null +++ b/snippets/algorithms/graph-traversal/graph_traversal.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +""" +Graph Traversal Algorithms Implementation + +This module provides implementations of BFS and DFS for graph traversal, +along with a visualization function to display the traversal order. + +Example usage: + python graph_traversal_example.py +""" + +from collections import deque +import time + + +class Graph: + """A simple graph representation using adjacency lists.""" + + def __init__(self): + """Initialize an empty graph.""" + self.graph = {} + + def add_vertex(self, vertex): + """Add a vertex to the graph if it doesn't exist.""" + if vertex not in self.graph: + self.graph[vertex] = [] + + def add_edge(self, v1, v2): + """Add an edge between v1 and v2.""" + if v1 not in self.graph: + self.add_vertex(v1) + if v2 not in self.graph: + self.add_vertex(v2) + + self.graph[v1].append(v2) + self.graph[v2].append(v1) # For undirected graph + + def bfs(self, start): + """ + Perform Breadth-First Search starting from the given vertex. + + Args: + start: The starting vertex for BFS + + Returns: + A list containing vertices in the order they were visited + """ + if start not in self.graph: + return [] + + visited = set([start]) + queue = deque([start]) + result = [] + + print(f"Starting BFS traversal from vertex {start}") + + while queue: + vertex = queue.popleft() + result.append(vertex) + + print(f"Visiting: {vertex}") + print(f"Queue: {list(queue)}") + print(f"Visited so far: {result}") + print("-" * 30) + time.sleep(0.5) # Slow down for demonstration + + for neighbor in sorted(self.graph[vertex]): # Sort for consistent order + if neighbor not in visited: + visited.add(neighbor) + queue.append(neighbor) + + return result + + def dfs_recursive(self, start): + """ + Perform Depth-First Search recursively starting from the given vertex. + + Args: + start: The starting vertex for DFS + + Returns: + A list containing vertices in the order they were visited + """ + if start not in self.graph: + return [] + + visited = set() + result = [] + + print(f"Starting recursive DFS traversal from vertex {start}") + + def dfs_helper(vertex): + visited.add(vertex) + result.append(vertex) + + print(f"Visiting: {vertex}") + print(f"Visited so far: {result}") + print("-" * 30) + time.sleep(0.5) # Slow down for demonstration + + for neighbor in sorted(self.graph[vertex]): # Sort for consistent order + if neighbor not in visited: + dfs_helper(neighbor) + + dfs_helper(start) + return result + + def dfs_iterative(self, start): + """ + Perform Depth-First Search iteratively starting from the given vertex. + + Args: + start: The starting vertex for DFS + + Returns: + A list containing vertices in the order they were visited + """ + if start not in self.graph: + return [] + + visited = set() + stack = [start] + result = [] + + print(f"Starting iterative DFS traversal from vertex {start}") + + while stack: + vertex = stack.pop() + + if vertex not in visited: + visited.add(vertex) + result.append(vertex) + + print(f"Visiting: {vertex}") + print(f"Stack: {stack}") + print(f"Visited so far: {result}") + print("-" * 30) + time.sleep(0.5) # Slow down for demonstration + + # Add neighbors in reverse sorted order to simulate recursive DFS + for neighbor in sorted(self.graph[vertex], reverse=True): + if neighbor not in visited: + stack.append(neighbor) + + return result + + def visualize_graph(self): + """Print a simple visualization of the graph structure.""" + print("\nGraph Structure:") + print("-" * 30) + for vertex, neighbors in sorted(self.graph.items()): + print(f"{vertex} -> {sorted(neighbors)}") + print("-" * 30) + + +def create_sample_graph(): + """Create a sample graph for demonstration.""" + g = Graph() + + # Add edges to build this graph: + # A + # / \ + # B C + # / \ \ + # D E---F + + edges = [ + ('A', 'B'), ('A', 'C'), + ('B', 'D'), ('B', 'E'), + ('C', 'F'), ('E', 'F') + ] + + for v1, v2 in edges: + g.add_edge(v1, v2) + + return g + + +def main(): + """Main function to demonstrate graph traversal algorithms.""" + g = create_sample_graph() + g.visualize_graph() + + print("\n=== BFS Traversal ===") + bfs_result = g.bfs('A') + print(f"BFS Result: {bfs_result}") + + print("\n=== DFS Traversal (Recursive) ===") + dfs_rec_result = g.dfs_recursive('A') + print(f"DFS Recursive Result: {dfs_rec_result}") + + print("\n=== DFS Traversal (Iterative) ===") + dfs_iter_result = g.dfs_iterative('A') + print(f"DFS Iterative Result: {dfs_iter_result}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/snippets/algorithms/graph-traversal/graph_traversal.rb b/snippets/algorithms/graph-traversal/graph_traversal.rb new file mode 100644 index 0000000..f5ba55f --- /dev/null +++ b/snippets/algorithms/graph-traversal/graph_traversal.rb @@ -0,0 +1,208 @@ +#!/usr/bin/env ruby + +# Graph Traversal Algorithms in Ruby +# +# This script demonstrates BFS and DFS traversal algorithms on a graph. +# +# Run: ruby graph_traversal.rb + +class Graph + # Initialize an empty graph + def initialize + @adjacency_list = {} + end + + # Add a vertex to the graph + def add_vertex(vertex) + @adjacency_list[vertex] = [] unless @adjacency_list.key?(vertex) + end + + # Add an edge between two vertices + def add_edge(v1, v2) + # Ensure both vertices exist + add_vertex(v1) + add_vertex(v2) + + # Add the edge (undirected graph) + @adjacency_list[v1] << v2 + @adjacency_list[v2] << v1 + end + + # Get sorted neighbors for consistent output + def get_sorted_neighbors(vertex) + @adjacency_list[vertex].sort + end + + # Breadth-First Search traversal + def bfs(start) + return [] unless @adjacency_list.key?(start) + + visited = { start => true } + queue = [start] + result = [] + + puts "Starting BFS traversal from vertex #{start}" + + until queue.empty? + # Dequeue the first vertex + vertex = queue.shift + result << vertex + + puts "Visiting: #{vertex}" + puts "Queue: #{queue.inspect}" + puts "Visited so far: #{result.inspect}" + puts "------------------------------" + + # Pause for demonstration + sleep(0.5) # 500ms + + # Get sorted neighbors for consistent order + neighbors = get_sorted_neighbors(vertex) + + # Enqueue all unvisited neighbors + neighbors.each do |neighbor| + unless visited[neighbor] + visited[neighbor] = true + queue << neighbor + end + end + end + + result + end + + # Depth-First Search traversal (recursive) + def dfs_recursive(start) + return [] unless @adjacency_list.key?(start) + + visited = {} + result = [] + + puts "Starting recursive DFS traversal from vertex #{start}" + + dfs_helper(start, visited, result) + + result + end + + # Helper method for recursive DFS + def dfs_helper(vertex, visited, result) + # Mark as visited and add to result + visited[vertex] = true + result << vertex + + puts "Visiting: #{vertex}" + puts "Visited so far: #{result.inspect}" + puts "------------------------------" + + # Pause for demonstration + sleep(0.5) # 500ms + + # Get sorted neighbors for consistent order + neighbors = get_sorted_neighbors(vertex) + + # Recursively visit all unvisited neighbors + neighbors.each do |neighbor| + dfs_helper(neighbor, visited, result) unless visited[neighbor] + end + end + + # Depth-First Search traversal (iterative) + def dfs_iterative(start) + return [] unless @adjacency_list.key?(start) + + visited = {} + stack = [start] + result = [] + + puts "Starting iterative DFS traversal from vertex #{start}" + + until stack.empty? + # Pop the top vertex + vertex = stack.pop + + # If not visited, process it + unless visited[vertex] + visited[vertex] = true + result << vertex + + puts "Visiting: #{vertex}" + puts "Stack: #{stack.inspect}" + puts "Visited so far: #{result.inspect}" + puts "------------------------------" + + # Pause for demonstration + sleep(0.5) # 500ms + + # Get sorted neighbors in reverse order for stack + neighbors = get_sorted_neighbors(vertex).reverse + + # Push all unvisited neighbors onto the stack + neighbors.each do |neighbor| + stack << neighbor unless visited[neighbor] + end + end + end + + result + end + + # Print the graph structure + def visualize_graph + puts "\nGraph Structure:" + puts "------------------------------" + + # Sort vertices for consistent output + vertices = @adjacency_list.keys.sort + + vertices.each do |vertex| + neighbors = get_sorted_neighbors(vertex) + puts "#{vertex} -> #{neighbors.inspect}" + end + + puts "------------------------------" + end +end + +# Create a sample graph for demonstration +def create_sample_graph + g = Graph.new + + # Add edges to build this graph: + # A + # / \ + # B C + # / \ \ + # D E---F + + edges = [ + ["A", "B"], ["A", "C"], + ["B", "D"], ["B", "E"], + ["C", "F"], ["E", "F"] + ] + + edges.each do |v1, v2| + g.add_edge(v1, v2) + end + + g +end + +# Main execution +g = create_sample_graph +g.visualize_graph + +# Demonstrate BFS +puts "\n=== BFS Traversal ===" +bfs_result = g.bfs("A") +puts "BFS Result: #{bfs_result.inspect}" + +# Demonstrate recursive DFS +puts "\n=== DFS Traversal (Recursive) ===" +dfs_rec_result = g.dfs_recursive("A") +puts "DFS Recursive Result: #{dfs_rec_result.inspect}" + +# Demonstrate iterative DFS +puts "\n=== DFS Traversal (Iterative) ===" +dfs_iter_result = g.dfs_iterative("A") +puts "DFS Iterative Result: #{dfs_iter_result.inspect}" \ No newline at end of file diff --git a/snippets/algorithms/graph-traversal/graph_traversal.rs b/snippets/algorithms/graph-traversal/graph_traversal.rs new file mode 100644 index 0000000..d031df9 --- /dev/null +++ b/snippets/algorithms/graph-traversal/graph_traversal.rs @@ -0,0 +1,242 @@ +use std::collections::{HashMap, HashSet, VecDeque}; +use std::thread; +use std::time::Duration; + +/// Graph Traversal Algorithms in Rust +/// +/// This program demonstrates BFS and DFS traversal algorithms on a graph. +/// +/// Compile: rustc graph_traversal.rs +/// Run: ./graph_traversal + +/// A graph using adjacency list representation +struct Graph { + // Adjacency list representation + adjacency_list: HashMap>, +} + +impl Graph { + /// Creates a new empty graph + fn new() -> Self { + Graph { + adjacency_list: HashMap::new(), + } + } + + /// Adds a vertex to the graph + fn add_vertex(&mut self, vertex: &str) { + self.adjacency_list.entry(vertex.to_string()).or_insert(Vec::new()); + } + + /// Adds an edge between two vertices + fn add_edge(&mut self, v1: &str, v2: &str) { + // Ensure both vertices exist + self.add_vertex(v1); + self.add_vertex(v2); + + // Add the edge (undirected graph) + self.adjacency_list.get_mut(v1).unwrap().push(v2.to_string()); + self.adjacency_list.get_mut(v2).unwrap().push(v1.to_string()); + } + + /// Helper method to get sorted neighbors for consistent output + fn get_sorted_neighbors(&self, vertex: &str) -> Vec { + let mut neighbors = self.adjacency_list[vertex].clone(); + neighbors.sort(); + neighbors + } + + /// Performs a breadth-first search traversal starting from the given vertex + fn bfs(&self, start: &str) -> Vec { + if !self.adjacency_list.contains_key(start) { + return Vec::new(); + } + + let mut visited = HashSet::new(); + let mut queue = VecDeque::new(); + let mut result = Vec::new(); + + // Initialize with starting vertex + visited.insert(start.to_string()); + queue.push_back(start.to_string()); + + println!("Starting BFS traversal from vertex {}", start); + + while !queue.is_empty() { + // Dequeue the first vertex + let vertex = queue.pop_front().unwrap(); + result.push(vertex.clone()); + + println!("Visiting: {}", vertex); + println!("Queue: {:?}", queue); + println!("Visited so far: {:?}", result); + println!("------------------------------"); + + // Pause for demonstration + thread::sleep(Duration::from_millis(500)); + + // Get sorted neighbors for consistent order + let neighbors = self.get_sorted_neighbors(&vertex); + + // Enqueue all unvisited neighbors + for neighbor in neighbors { + if !visited.contains(&neighbor) { + visited.insert(neighbor.clone()); + queue.push_back(neighbor); + } + } + } + + result + } + + /// Performs a recursive depth-first search traversal starting from the given vertex + fn dfs_recursive(&self, start: &str) -> Vec { + if !self.adjacency_list.contains_key(start) { + return Vec::new(); + } + + let mut visited = HashSet::new(); + let mut result = Vec::new(); + + println!("Starting recursive DFS traversal from vertex {}", start); + + self.dfs_helper(start, &mut visited, &mut result); + + result + } + + /// Helper method for recursive DFS + fn dfs_helper(&self, vertex: &str, visited: &mut HashSet, result: &mut Vec) { + // Mark as visited and add to result + visited.insert(vertex.to_string()); + result.push(vertex.to_string()); + + println!("Visiting: {}", vertex); + println!("Visited so far: {:?}", result); + println!("------------------------------"); + + // Pause for demonstration + thread::sleep(Duration::from_millis(500)); + + // Get sorted neighbors for consistent order + let neighbors = self.get_sorted_neighbors(vertex); + + // Recursively visit all unvisited neighbors + for neighbor in neighbors { + if !visited.contains(&neighbor) { + self.dfs_helper(&neighbor, visited, result); + } + } + } + + /// Performs an iterative depth-first search traversal starting from the given vertex + fn dfs_iterative(&self, start: &str) -> Vec { + if !self.adjacency_list.contains_key(start) { + return Vec::new(); + } + + let mut visited = HashSet::new(); + let mut stack = Vec::new(); + let mut result = Vec::new(); + + // Initialize with starting vertex + stack.push(start.to_string()); + + println!("Starting iterative DFS traversal from vertex {}", start); + + while !stack.is_empty() { + // Pop the top vertex + let vertex = stack.pop().unwrap(); + + // If not visited, process it + if !visited.contains(&vertex) { + visited.insert(vertex.clone()); + result.push(vertex.clone()); + + println!("Visiting: {}", vertex); + println!("Stack: {:?}", stack); + println!("Visited so far: {:?}", result); + println!("------------------------------"); + + // Pause for demonstration + thread::sleep(Duration::from_millis(500)); + + // Get sorted neighbors in reverse order for stack + let mut neighbors = self.get_sorted_neighbors(&vertex); + neighbors.reverse(); + + // Push all unvisited neighbors onto the stack + for neighbor in neighbors { + if !visited.contains(&neighbor) { + stack.push(neighbor); + } + } + } + } + + result + } + + /// Prints a visualization of the graph structure + fn visualize_graph(&self) { + println!("\nGraph Structure:"); + println!("------------------------------"); + + // Sort vertices for consistent output + let mut vertices: Vec = self.adjacency_list.keys().cloned().collect(); + vertices.sort(); + + for vertex in vertices { + let neighbors = self.get_sorted_neighbors(&vertex); + println!("{} -> {:?}", vertex, neighbors); + } + + println!("------------------------------"); + } +} + +/// Creates a sample graph for demonstration +fn create_sample_graph() -> Graph { + let mut g = Graph::new(); + + // Add edges to build this graph: + // A + // / \ + // B C + // / \ \ + // D E---F + + let edges = [ + ("A", "B"), ("A", "C"), + ("B", "D"), ("B", "E"), + ("C", "F"), ("E", "F") + ]; + + for (v1, v2) in edges.iter() { + g.add_edge(v1, v2); + } + + g +} + +fn main() { + // Create a sample graph + let g = create_sample_graph(); + g.visualize_graph(); + + // Demonstrate BFS + println!("\n=== BFS Traversal ==="); + let bfs_result = g.bfs("A"); + println!("BFS Result: {:?}", bfs_result); + + // Demonstrate recursive DFS + println!("\n=== DFS Traversal (Recursive) ==="); + let dfs_rec_result = g.dfs_recursive("A"); + println!("DFS Recursive Result: {:?}", dfs_rec_result); + + // Demonstrate iterative DFS + println!("\n=== DFS Traversal (Iterative) ==="); + let dfs_iter_result = g.dfs_iterative("A"); + println!("DFS Iterative Result: {:?}", dfs_iter_result); +} \ No newline at end of file diff --git a/snippets/algorithms/init.txt b/snippets/algorithms/init.txt new file mode 100644 index 0000000..e69de29 diff --git a/snippets/databases/.env.example b/snippets/databases/.env.example new file mode 100644 index 0000000..46b59e9 --- /dev/null +++ b/snippets/databases/.env.example @@ -0,0 +1,32 @@ +# Database configuration example file +# Copy this file to .env and fill in your actual values + +# SQLite Configuration +SQLITE_DB_PATH=:memory: + +# PostgreSQL Configuration +PG_HOST=localhost +PG_DATABASE=mydb +PG_USER=postgres +PG_PASSWORD=your_secure_password + +# MySQL Configuration +MYSQL_HOST=localhost +MYSQL_DATABASE=mydb +MYSQL_USER=root +MYSQL_PASSWORD=your_secure_password + +# SQL Server Configuration +SQLSERVER_CONNECTION_STRING=Server=localhost;Database=TestDB;User Id=sa;Password=your_secure_password; + +# Other database connection strings +POSTGRES_CONNECTION_STRING=Host=localhost;Database=testdb;Username=postgres;Password=your_secure_password; +MYSQL_CONNECTION_STRING=Server=localhost;Database=testdb;Uid=root;Pwd=your_secure_password; +DAPPER_CONNECTION_STRING=Data Source=:memory: + +# SQLAlchemy URL for Python ORM +# Format examples: +# SQLite: sqlite:///path/to/database.db +# PostgreSQL: postgresql://user:password@localhost/dbname +# MySQL: mysql://user:password@localhost/dbname +SQLALCHEMY_DATABASE_URL=sqlite:///:memory: \ No newline at end of file diff --git a/snippets/databases/README.md b/snippets/databases/README.md new file mode 100644 index 0000000..fb0aeed --- /dev/null +++ b/snippets/databases/README.md @@ -0,0 +1,186 @@ +# Database Code Examples + +This directory contains code examples for working with various relational databases in different programming languages. + +## Contents + +- `sql_examples.py` - Python examples for SQLite, PostgreSQL, MySQL, and SQLAlchemy ORM +- `relational_db_examples.cs` - C# examples for SQLite, SQL Server, MySQL, PostgreSQL, and Dapper ORM + +## Setup Instructions + +### Environment Variables + +For security reasons, all database connection credentials are loaded from environment variables. Follow these steps to set up your environment: + +1. Copy the example environment file to create your own `.env` file: + ```bash + cp envv.example .env + ``` + +2. Edit the `.env` file with your actual database credentials: + ``` + # Example for PostgreSQL + PG_HOST=localhost + PG_DATABASE=yourdb + PG_USER=yourusername + PG_PASSWORD=yourpassword + ``` + +3. Make sure your `.env` file is included in `.gitignore` to prevent committing sensitive information. + +### Python Setup + +To run the Python examples: + +1. Install required dependencies: + ```bash + pip install python-dotenv + + # For SQLite (built into Python) + # No additional installation needed + + # For PostgreSQL + pip install psycopg2-binary + + # For MySQL + pip install mysql-connector-python + + # For SQLAlchemy ORM + pip install sqlalchemy + ``` + +2. Run the examples: + ```bash + python sql_examples.py + ``` + +### C# Setup + +To run the C# examples: + +1. Install required NuGet packages: + ```bash + # For SQLite + dotnet add package Microsoft.Data.Sqlite + + # For SQL Server + dotnet add package Microsoft.Data.SqlClient + + # For MySQL + dotnet add package MySql.Data + + # For PostgreSQL + dotnet add package Npgsql + + # For Dapper ORM + dotnet add package Dapper + ``` + +2. Build and run the examples: + ```bash + dotnet build + dotnet run + ``` + +## Usage Examples + +### Python SQLite Example + +```python +import sqlite3 +from dotenv import load_dotenv +import os + +# Load environment variables +load_dotenv() + +# Get database path from environment variable +db_path = os.environ.get("SQLITE_DB_PATH", ":memory:") + +# Connect to SQLite database +conn = sqlite3.connect(db_path) +cursor = conn.cursor() + +# Create a table +cursor.execute(''' +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT UNIQUE NOT NULL +) +''') + +# Insert data +cursor.execute('INSERT INTO users (name, email) VALUES (?, ?)', + ('John Doe', 'john@example.com')) +conn.commit() + +# Query data +cursor.execute('SELECT * FROM users') +print(cursor.fetchall()) + +# Close connection +conn.close() +``` + +### C# SQLite Example + +```csharp +using System; +using Microsoft.Data.Sqlite; + +// Get connection string from environment variable +string connectionString = Environment.GetEnvironmentVariable("SQLITE_CONNECTION_STRING") + ?? "Data Source=:memory:"; + +using (var connection = new SqliteConnection(connectionString)) +{ + connection.Open(); + + // Create a table + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT NOT NULL UNIQUE + )"; + command.ExecuteNonQuery(); + } + + // Insert data + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + INSERT INTO users (name, email) + VALUES (@name, @email)"; + command.Parameters.AddWithValue("@name", "Jane Smith"); + command.Parameters.AddWithValue("@email", "jane@example.com"); + command.ExecuteNonQuery(); + } + + // Query data + using (var command = connection.CreateCommand()) + { + command.CommandText = "SELECT * FROM users"; + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + Console.WriteLine($"ID: {reader.GetInt32(0)}, Name: {reader.GetString(1)}, Email: {reader.GetString(2)}"); + } + } + } +} +``` + +## Security Best Practices + +1. Never hardcode database credentials in your source code +2. Always use parameterized queries to prevent SQL injection +3. Store connection strings and credentials in environment variables or a secure configuration system +4. Use the principle of least privilege for database users +5. Encrypt sensitive data before storing it in the database +6. Regularly update database drivers and libraries to patch security vulnerabilities \ No newline at end of file diff --git a/snippets/databases/README_vi.md b/snippets/databases/README_vi.md new file mode 100644 index 0000000..0ea5b09 --- /dev/null +++ b/snippets/databases/README_vi.md @@ -0,0 +1,186 @@ +# Ví Dụ Mã Nguồn Cơ Sở Dữ Liệu + +Thư mục này chứa các ví dụ mã nguồn để làm việc với nhiều cơ sở dữ liệu quan hệ khác nhau trong các ngôn ngữ lập trình khác nhau. + +## Nội Dung + +- `sql_examples.py` - Ví dụ Python cho SQLite, PostgreSQL, MySQL, và SQLAlchemy ORM +- `relational_db_examples.cs` - Ví dụ C# cho SQLite, SQL Server, MySQL, PostgreSQL, và Dapper ORM + +## Hướng Dẫn Cài Đặt + +### Biến Môi Trường + +Vì lý do bảo mật, tất cả thông tin đăng nhập cơ sở dữ liệu được tải từ biến môi trường. Làm theo các bước sau để thiết lập môi trường của bạn: + +1. Sao chép tệp môi trường mẫu để tạo tệp `.env` của riêng bạn: + ```bash + cp envv.example .env + ``` + +2. Chỉnh sửa tệp `.env` với thông tin đăng nhập cơ sở dữ liệu thực tế của bạn: + ``` + # Ví dụ cho PostgreSQL + PG_HOST=localhost + PG_DATABASE=yourdb + PG_USER=yourusername + PG_PASSWORD=yourpassword + ``` + +3. Đảm bảo tệp `.env` của bạn được bao gồm trong `.gitignore` để tránh commit thông tin nhạy cảm. + +### Cài Đặt Python + +Để chạy các ví dụ Python: + +1. Cài đặt các gói phụ thuộc cần thiết: + ```bash + pip install python-dotenv + + # Cho SQLite (đã tích hợp sẵn trong Python) + # Không cần cài đặt thêm + + # Cho PostgreSQL + pip install psycopg2-binary + + # Cho MySQL + pip install mysql-connector-python + + # Cho SQLAlchemy ORM + pip install sqlalchemy + ``` + +2. Chạy các ví dụ: + ```bash + python sql_examples.py + ``` + +### Cài Đặt C# + +Để chạy các ví dụ C#: + +1. Cài đặt các gói NuGet cần thiết: + ```bash + # Cho SQLite + dotnet add package Microsoft.Data.Sqlite + + # Cho SQL Server + dotnet add package Microsoft.Data.SqlClient + + # Cho MySQL + dotnet add package MySql.Data + + # Cho PostgreSQL + dotnet add package Npgsql + + # Cho Dapper ORM + dotnet add package Dapper + ``` + +2. Biên dịch và chạy các ví dụ: + ```bash + dotnet build + dotnet run + ``` + +## Ví Dụ Sử Dụng + +### Python SQLite Example + +```python +import sqlite3 +from dotenv import load_dotenv +import os + +# Load environment variables +load_dotenv() + +# Get database path from environment variable +db_path = os.environ.get("SQLITE_DB_PATH", ":memory:") + +# Connect to SQLite database +conn = sqlite3.connect(db_path) +cursor = conn.cursor() + +# Create a table +cursor.execute(''' +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT UNIQUE NOT NULL +) +''') + +# Insert data +cursor.execute('INSERT INTO users (name, email) VALUES (?, ?)', + ('John Doe', 'john@example.com')) +conn.commit() + +# Query data +cursor.execute('SELECT * FROM users') +print(cursor.fetchall()) + +# Close connection +conn.close() +``` + +### C# SQLite Example + +```csharp +using System; +using Microsoft.Data.Sqlite; + +// Get connection string from environment variable +string connectionString = Environment.GetEnvironmentVariable("SQLITE_CONNECTION_STRING") + ?? "Data Source=:memory:"; + +using (var connection = new SqliteConnection(connectionString)) +{ + connection.Open(); + + // Create a table + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT NOT NULL UNIQUE + )"; + command.ExecuteNonQuery(); + } + + // Insert data + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + INSERT INTO users (name, email) + VALUES (@name, @email)"; + command.Parameters.AddWithValue("@name", "Jane Smith"); + command.Parameters.AddWithValue("@email", "jane@example.com"); + command.ExecuteNonQuery(); + } + + // Query data + using (var command = connection.CreateCommand()) + { + command.CommandText = "SELECT * FROM users"; + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + Console.WriteLine($"ID: {reader.GetInt32(0)}, Name: {reader.GetString(1)}, Email: {reader.GetString(2)}"); + } + } + } +} +``` + +## Các Phương Pháp Tốt Nhất về Bảo Mật + +1. Không bao giờ mã hóa cứng thông tin đăng nhập cơ sở dữ liệu trong mã nguồn của bạn +2. Luôn sử dụng truy vấn có tham số để ngăn chặn SQL injection +3. Lưu trữ chuỗi kết nối và thông tin đăng nhập trong biến môi trường hoặc hệ thống cấu hình bảo mật +4. Áp dụng nguyên tắc đặc quyền tối thiểu cho người dùng cơ sở dữ liệu +5. Mã hóa dữ liệu nhạy cảm trước khi lưu trữ trong cơ sở dữ liệu +6. Thường xuyên cập nhật trình điều khiển và thư viện cơ sở dữ liệu để vá các lỗ hổng bảo mật \ No newline at end of file diff --git a/snippets/databases/relational_db_examples.cs b/snippets/databases/relational_db_examples.cs new file mode 100644 index 0000000..062a3e6 --- /dev/null +++ b/snippets/databases/relational_db_examples.cs @@ -0,0 +1,829 @@ +using System; +using System.Data; +using System.Collections.Generic; +using System.Threading.Tasks; +// Requires NuGet packages: +// Microsoft.Data.SqlClient +// Microsoft.Data.Sqlite +// MySql.Data +// Npgsql +// Dapper +// Microsoft.Extensions.Configuration.EnvironmentVariables (for .NET environment variables) + +namespace RelationalDatabaseExamples +{ + /// + /// Examples of working with relational databases in C# + /// + class Program + { + // Configuration for environment variables + private static readonly EnvironmentVariableManager _env = new EnvironmentVariableManager(); + + static async Task Main(string[] args) + { + Console.WriteLine("C# Relational Database Examples"); + Console.WriteLine("==============================="); + + try + { + // SQLite example (works without additional setup) + await SQLiteExample(); + + // The following examples require database servers to be running + // Uncomment them if you have the necessary databases set up + + // await SqlServerExample(); + // await MySqlExample(); + // await PostgreSqlExample(); + // await DapperExample(); + + Console.WriteLine("\nAll examples completed successfully."); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + } + + /// + /// Example using SQLite + /// + static async Task SQLiteExample() + { + Console.WriteLine("\n=== SQLite Example ==="); + + // Make sure you have the Microsoft.Data.Sqlite NuGet package installed + var connectionString = _env.GetValue("SQLITE_CONNECTION_STRING", "Data Source=:memory:"); + + using (var connection = new Microsoft.Data.Sqlite.SqliteConnection(connectionString)) + { + await connection.OpenAsync(); + Console.WriteLine("Connected to SQLite database"); + + // Create a table + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + CREATE TABLE Employees ( + Id INTEGER PRIMARY KEY, + Name TEXT NOT NULL, + Department TEXT NOT NULL, + Salary REAL, + HireDate TEXT + )"; + await command.ExecuteNonQueryAsync(); + Console.WriteLine("Created Employees table"); + } + + // Insert data using parameters + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + INSERT INTO Employees (Name, Department, Salary, HireDate) + VALUES (@name, @department, @salary, @hireDate)"; + + command.Parameters.AddWithValue("@name", "John Smith"); + command.Parameters.AddWithValue("@department", "Engineering"); + command.Parameters.AddWithValue("@salary", 85000.00); + command.Parameters.AddWithValue("@hireDate", DateTime.Now.ToString("yyyy-MM-dd")); + + await command.ExecuteNonQueryAsync(); + Console.WriteLine("Inserted employee: John Smith"); + + // Insert another employee + command.Parameters.Clear(); + command.Parameters.AddWithValue("@name", "Jane Doe"); + command.Parameters.AddWithValue("@department", "Marketing"); + command.Parameters.AddWithValue("@salary", 75000.00); + command.Parameters.AddWithValue("@hireDate", DateTime.Now.AddDays(-90).ToString("yyyy-MM-dd")); + + await command.ExecuteNonQueryAsync(); + Console.WriteLine("Inserted employee: Jane Doe"); + + // One more employee + command.Parameters.Clear(); + command.Parameters.AddWithValue("@name", "Bob Johnson"); + command.Parameters.AddWithValue("@department", "Engineering"); + command.Parameters.AddWithValue("@salary", 82000.00); + command.Parameters.AddWithValue("@hireDate", DateTime.Now.AddDays(-180).ToString("yyyy-MM-dd")); + + await command.ExecuteNonQueryAsync(); + Console.WriteLine("Inserted employee: Bob Johnson"); + } + + // Query all employees + Console.WriteLine("\nAll employees:"); + using (var command = connection.CreateCommand()) + { + command.CommandText = "SELECT Id, Name, Department, Salary FROM Employees"; + + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + Console.WriteLine($"ID: {reader.GetInt32(0)}, Name: {reader.GetString(1)}, " + + $"Department: {reader.GetString(2)}, Salary: ${reader.GetDouble(3):N2}"); + } + } + } + + // Filtered query with parameters + Console.WriteLine("\nEngineering department employees:"); + using (var command = connection.CreateCommand()) + { + command.CommandText = "SELECT Name, Salary FROM Employees WHERE Department = @department"; + command.Parameters.AddWithValue("@department", "Engineering"); + + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + Console.WriteLine($"{reader.GetString(0)}: ${reader.GetDouble(1):N2}"); + } + } + } + + // Aggregate query + Console.WriteLine("\nDepartment statistics:"); + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + SELECT Department, + COUNT(*) as EmployeeCount, + AVG(Salary) as AvgSalary, + SUM(Salary) as TotalSalary + FROM Employees + GROUP BY Department"; + + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + Console.WriteLine($"{reader.GetString(0)}: " + + $"{reader.GetInt32(1)} employees, " + + $"Avg: ${reader.GetDouble(2):N2}, " + + $"Total: ${reader.GetDouble(3):N2}"); + } + } + } + + // Transaction example + await using (var transaction = connection.BeginTransaction()) + { + try + { + // Update salary in a transaction + using (var command = connection.CreateCommand()) + { + command.Transaction = transaction; + command.CommandText = "UPDATE Employees SET Salary = Salary * 1.1 WHERE Department = @department"; + command.Parameters.AddWithValue("@department", "Engineering"); + + var rowsAffected = await command.ExecuteNonQueryAsync(); + Console.WriteLine($"\nGave 10% raise to {rowsAffected} engineering employees"); + } + + // Verify the changes + using (var command = connection.CreateCommand()) + { + command.Transaction = transaction; + command.CommandText = "SELECT Name, Salary FROM Employees WHERE Department = @department"; + command.Parameters.AddWithValue("@department", "Engineering"); + + Console.WriteLine("Updated engineering salaries:"); + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + Console.WriteLine($"{reader.GetString(0)}: ${reader.GetDouble(1):N2}"); + } + } + } + + // Commit the transaction + await transaction.CommitAsync(); + Console.WriteLine("Transaction committed"); + } + catch (Exception ex) + { + await transaction.RollbackAsync(); + Console.WriteLine($"Transaction rolled back: {ex.Message}"); + } + } + } + } + + /// + /// Example using SQL Server + /// + static async Task SqlServerExample() + { + Console.WriteLine("\n=== SQL Server Example ==="); + Console.WriteLine("To run this example, you need SQL Server and the Microsoft.Data.SqlClient NuGet package."); + + // Connection string for SQL Server from environment variables + var connectionString = _env.GetValue("SQLSERVER_CONNECTION_STRING", + "Server=localhost;Database=TestDB;Trusted_Connection=True;"); + + Console.WriteLine($"Using connection info from environment: {GetSanitizedConnectionString(connectionString)}"); + + using (var connection = new Microsoft.Data.SqlClient.SqlConnection(connectionString)) + { + await connection.OpenAsync(); + Console.WriteLine("Connected to SQL Server"); + + // Create a table with SQL Server specific features + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Products') + BEGIN + CREATE TABLE Products ( + Id INT IDENTITY(1,1) PRIMARY KEY, + Name NVARCHAR(100) NOT NULL, + Description NVARCHAR(MAX), + Price DECIMAL(10,2) NOT NULL, + Category NVARCHAR(50), + CreatedAt DATETIME2 DEFAULT GETDATE(), + UpdatedAt DATETIME2, + IsActive BIT DEFAULT 1 + ); + + CREATE INDEX IX_Products_Category ON Products(Category); + END"; + + await command.ExecuteNonQueryAsync(); + Console.WriteLine("Created Products table"); + } + + // Insert with output parameter to get the identity value + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + INSERT INTO Products (Name, Description, Price, Category) + OUTPUT INSERTED.Id + VALUES (@name, @description, @price, @category)"; + + command.Parameters.AddWithValue("@name", "Laptop"); + command.Parameters.AddWithValue("@description", "High-performance laptop"); + command.Parameters.AddWithValue("@price", 1299.99); + command.Parameters.AddWithValue("@category", "Electronics"); + + var productId = (int)await command.ExecuteScalarAsync(); + Console.WriteLine($"Inserted product with ID: {productId}"); + } + + // SQL Server specific features: CTE, ROW_NUMBER + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + WITH RankedProducts AS ( + SELECT + Name, + Price, + Category, + ROW_NUMBER() OVER (PARTITION BY Category ORDER BY Price DESC) AS PriceRank + FROM + Products + ) + SELECT Name, Price, Category, PriceRank + FROM RankedProducts + WHERE PriceRank <= 3"; + + using (var reader = await command.ExecuteReaderAsync()) + { + Console.WriteLine("\nTop 3 most expensive products per category:"); + while (await reader.ReadAsync()) + { + Console.WriteLine($"{reader.GetString(0)} - ${reader.GetDecimal(1):N2} - " + + $"{reader.GetString(2)} (Rank: {reader.GetInt64(3)})"); + } + } + } + + // Using stored procedure + using (var command = connection.CreateCommand()) + { + // First create the stored procedure + command.CommandText = @" + IF NOT EXISTS (SELECT * FROM sys.procedures WHERE name = 'GetProductsByCategory') + BEGIN + EXEC(' + CREATE PROCEDURE GetProductsByCategory + @CategoryName NVARCHAR(50), + @MinPrice DECIMAL(10,2) = 0 + AS + BEGIN + SELECT Id, Name, Price + FROM Products + WHERE Category = @CategoryName + AND Price >= @MinPrice + ORDER BY Price DESC; + END + ') + END"; + + await command.ExecuteNonQueryAsync(); + Console.WriteLine("Created stored procedure"); + + // Use the stored procedure + command.CommandText = "GetProductsByCategory"; + command.CommandType = CommandType.StoredProcedure; + + command.Parameters.Clear(); + command.Parameters.AddWithValue("@CategoryName", "Electronics"); + command.Parameters.AddWithValue("@MinPrice", 500); + + using (var reader = await command.ExecuteReaderAsync()) + { + Console.WriteLine("\nElectronics products over $500:"); + while (await reader.ReadAsync()) + { + Console.WriteLine($"ID: {reader.GetInt32(0)}, Name: {reader.GetString(1)}, " + + $"Price: ${reader.GetDecimal(2):N2}"); + } + } + } + } + } + + /// + /// Example using MySQL + /// + static async Task MySqlExample() + { + Console.WriteLine("\n=== MySQL Example ==="); + Console.WriteLine("To run this example, you need MySQL and the MySql.Data NuGet package."); + + // Connection string for MySQL from environment variables + var connectionString = _env.GetValue("MYSQL_CONNECTION_STRING", + "Server=localhost;Database=testdb;Uid=root;Pwd=password;"); + + Console.WriteLine($"Using connection info from environment: {GetSanitizedConnectionString(connectionString)}"); + + using (var connection = new MySql.Data.MySqlClient.MySqlConnection(connectionString)) + { + await connection.OpenAsync(); + Console.WriteLine("Connected to MySQL"); + + // Create a table with MySQL specific features + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + CREATE TABLE IF NOT EXISTS Orders ( + id INT AUTO_INCREMENT PRIMARY KEY, + customer_name VARCHAR(100) NOT NULL, + total DECIMAL(10,2) NOT NULL, + status ENUM('pending', 'shipped', 'delivered') NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_status (status), + INDEX idx_created_at (created_at) + ) ENGINE=InnoDB"; + + await command.ExecuteNonQueryAsync(); + Console.WriteLine("Created Orders table"); + } + + // Insert with MySQL-specific AUTO_INCREMENT handling + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + INSERT INTO Orders (customer_name, total, status) + VALUES (@customerName, @total, @status)"; + + command.Parameters.AddWithValue("@customerName", "John Doe"); + command.Parameters.AddWithValue("@total", 123.45); + command.Parameters.AddWithValue("@status", "pending"); + + await command.ExecuteNonQueryAsync(); + Console.WriteLine($"Inserted order, ID: {command.LastInsertedId}"); + } + + // MySQL specific functions + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + SELECT + id, + customer_name, + total, + status, + DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS formatted_date, + DATEDIFF(NOW(), created_at) AS days_since_creation + FROM + Orders + WHERE + created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)"; + + using (var reader = await command.ExecuteReaderAsync()) + { + Console.WriteLine("\nRecent orders:"); + while (await reader.ReadAsync()) + { + Console.WriteLine($"ID: {reader.GetInt32(0)}, " + + $"Customer: {reader.GetString(1)}, " + + $"Total: ${reader.GetDecimal(2):N2}, " + + $"Status: {reader.GetString(3)}, " + + $"Created: {reader.GetString(4)} " + + $"({reader.GetInt32(5)} days ago)"); + } + } + } + } + } + + /// + /// Example using PostgreSQL + /// + static async Task PostgreSqlExample() + { + Console.WriteLine("\n=== PostgreSQL Example ==="); + Console.WriteLine("To run this example, you need PostgreSQL and the Npgsql NuGet package."); + + // Connection string for PostgreSQL from environment variables + var connectionString = _env.GetValue("POSTGRES_CONNECTION_STRING", + "Host=localhost;Database=testdb;Username=postgres;Password=password"); + + Console.WriteLine($"Using connection info from environment: {GetSanitizedConnectionString(connectionString)}"); + + using (var connection = new Npgsql.NpgsqlConnection(connectionString)) + { + await connection.OpenAsync(); + Console.WriteLine("Connected to PostgreSQL"); + + // Create a table with PostgreSQL-specific features + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + CREATE TABLE IF NOT EXISTS products ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + description TEXT, + price DECIMAL(10,2) NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + tags TEXT[], + metadata JSONB + )"; + + await command.ExecuteNonQueryAsync(); + Console.WriteLine("Created products table"); + } + + // Insert with PostgreSQL-specific data types + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + INSERT INTO products (name, description, price, tags, metadata) + VALUES (@name, @description, @price, @tags, @metadata) + RETURNING id"; + + command.Parameters.AddWithValue("@name", "Laptop"); + command.Parameters.AddWithValue("@description", "High-performance laptop"); + command.Parameters.AddWithValue("@price", 999.99); + command.Parameters.AddWithValue("@tags", new string[] { "electronics", "computers" }); + command.Parameters.AddWithValue("@metadata", "{""brand"": ""TechBrand"", ""warranty"": ""2 years"", ""specs"": {""cpu"": ""i7"", ""ram"": ""16GB""}}"); + + var productId = (int)await command.ExecuteScalarAsync(); + Console.WriteLine($"Inserted product with ID: {productId}"); + } + + // PostgreSQL-specific features: JSONB, arrays + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + SELECT + name, + price, + metadata->>'brand' AS brand, + metadata->'specs'->>'cpu' AS cpu, + array_length(tags, 1) AS tag_count, + extract(year from created_at) AS year + FROM + products + WHERE + 'electronics' = ANY(tags)"; + + using (var reader = await command.ExecuteReaderAsync()) + { + Console.WriteLine("\nElectronics products:"); + while (await reader.ReadAsync()) + { + Console.WriteLine($"{reader.GetString(0)} - ${reader.GetDecimal(1):N2} - " + + $"Brand: {reader.GetString(2)}, CPU: {reader.GetString(3)}, " + + $"Tag count: {reader.GetInt32(4)}, Year: {reader.GetDouble(5)}"); + } + } + } + } + } + + /// + /// Example using Dapper micro-ORM + /// + static async Task DapperExample() + { + Console.WriteLine("\n=== Dapper Example ==="); + Console.WriteLine("To run this example, you need the Dapper NuGet package and a database connection."); + + // Connection string from environment variables + var connectionString = _env.GetValue("DAPPER_CONNECTION_STRING", "Data Source=:memory:"); + + using (var connection = new Microsoft.Data.Sqlite.SqliteConnection(connectionString)) + { + await connection.OpenAsync(); + Console.WriteLine("Connected to SQLite for Dapper example"); + + // Set up database + await connection.ExecuteAsync(@" + CREATE TABLE Customers ( + Id INTEGER PRIMARY KEY, + Name TEXT NOT NULL, + Email TEXT NOT NULL + ); + + CREATE TABLE Orders ( + Id INTEGER PRIMARY KEY, + CustomerId INTEGER NOT NULL, + Amount REAL NOT NULL, + OrderDate TEXT NOT NULL, + FOREIGN KEY (CustomerId) REFERENCES Customers (Id) + ); + + CREATE TABLE OrderItems ( + Id INTEGER PRIMARY KEY, + OrderId INTEGER NOT NULL, + ProductName TEXT NOT NULL, + Quantity INTEGER NOT NULL, + UnitPrice REAL NOT NULL, + FOREIGN KEY (OrderId) REFERENCES Orders (Id) + ); + "); + + // Insert customers + var customerIds = await connection.ExecuteAsync(@" + INSERT INTO Customers (Name, Email) VALUES + (@Name, @Email)", + new[] { + new { Name = "Alice Smith", Email = "alice@example.com" }, + new { Name = "Bob Jones", Email = "bob@example.com" } + }); + + Console.WriteLine($"Inserted {customerIds} customers"); + + // Insert orders + var orderDate = DateTime.Now; + var order1Id = await connection.QuerySingleAsync(@" + INSERT INTO Orders (CustomerId, Amount, OrderDate) + VALUES (@CustomerId, @Amount, @OrderDate) + RETURNING Id", + new { CustomerId = 1, Amount = 125.50, OrderDate = orderDate.ToString("yyyy-MM-dd") }); + + // Insert order items + await connection.ExecuteAsync(@" + INSERT INTO OrderItems (OrderId, ProductName, Quantity, UnitPrice) + VALUES (@OrderId, @ProductName, @Quantity, @UnitPrice)", + new[] { + new { OrderId = order1Id, ProductName = "Keyboard", Quantity = 1, UnitPrice = 75.50 }, + new { OrderId = order1Id, ProductName = "Mouse", Quantity = 1, UnitPrice = 50.00 } + }); + + // Query with Dapper + var customers = await connection.QueryAsync("SELECT * FROM Customers"); + + Console.WriteLine("\nAll customers:"); + foreach (var customer in customers) + { + Console.WriteLine($"ID: {customer.Id}, Name: {customer.Name}, Email: {customer.Email}"); + } + + // Query with join and multi-mapping + Console.WriteLine("\nOrders with customer info:"); + var orders = await connection.QueryAsync( + @"SELECT o.Id, o.Amount, o.OrderDate, c.Id, c.Name, c.Email + FROM Orders o + JOIN Customers c ON o.CustomerId = c.Id", + (order, customer) => { + order.Customer = customer; + return order; + }, + splitOn: "Id" + ); + + foreach (var order in orders) + { + Console.WriteLine($"Order #{order.Id} - ${order.Amount:N2} on {order.OrderDate} " + + $"by {order.Customer.Name} ({order.Customer.Email})"); + } + + // Query order details with complex mapping + Console.WriteLine("\nOrder details:"); + var orderWithItems = await connection.QueryAsync( + @"SELECT o.Id, o.Amount, o.OrderDate, i.Id, i.ProductName, i.Quantity, i.UnitPrice + FROM Orders o + JOIN OrderItems i ON o.Id = i.OrderId + WHERE o.Id = @OrderId", + (order, item) => { + if (order.Items == null) + order.Items = new List(); + order.Items.Add(item); + return order; + }, + new { OrderId = order1Id }, + splitOn: "Id" + ); + + var orderDetail = orderWithItems.GroupBy(o => o.Id).Select(g => { + var order = g.First(); + order.Items = g.Select(o => o.Items.Single()).ToList(); + return order; + }).First(); + + Console.WriteLine($"Order #{orderDetail.Id} - ${orderDetail.Amount:N2} on {orderDetail.OrderDate}"); + foreach (var item in orderDetail.Items) + { + Console.WriteLine($" {item.ProductName}: {item.Quantity} x ${item.UnitPrice:N2} = ${item.Quantity * item.UnitPrice:N2}"); + } + + // Execute scalar + var totalSales = await connection.ExecuteScalarAsync( + "SELECT SUM(Amount) FROM Orders"); + + Console.WriteLine($"\nTotal sales: ${totalSales:N2}"); + } + } + + /// + /// Sanitizes a connection string by hiding the password + /// + private static string GetSanitizedConnectionString(string connectionString) + { + // Simple sanitization to hide password/credentials + if (string.IsNullOrEmpty(connectionString)) + return connectionString; + + // Handle different formats for different database providers + return connectionString + .Replace(GetPasswordPart(connectionString, "Password="), "Password=*****") + .Replace(GetPasswordPart(connectionString, "Pwd="), "Pwd=*****") + .Replace(GetPasswordPart(connectionString, "password="), "password=*****"); + } + + private static string GetPasswordPart(string connectionString, string passwordPrefix) + { + int pwdIndex = connectionString.IndexOf(passwordPrefix, StringComparison.OrdinalIgnoreCase); + if (pwdIndex < 0) + return string.Empty; + + int startIndex = pwdIndex + passwordPrefix.Length; + int endIndex = connectionString.IndexOf(';', startIndex); + if (endIndex < 0) + endIndex = connectionString.Length; + + return connectionString.Substring(pwdIndex, endIndex - pwdIndex); + } + + // Model classes for Dapper example + public class Customer + { + public int Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public List Orders { get; set; } + } + + public class Order + { + public int Id { get; set; } + public int CustomerId { get; set; } + public double Amount { get; set; } + public string OrderDate { get; set; } + public Customer Customer { get; set; } + public List Items { get; set; } + } + + public class OrderItem + { + public int Id { get; set; } + public int OrderId { get; set; } + public string ProductName { get; set; } + public int Quantity { get; set; } + public double UnitPrice { get; set; } + } + } + + /// + /// Helper class to manage environment variables with fallback values + /// + public class EnvironmentVariableManager + { + /// + /// Gets a value from environment variables with a fallback default + /// + public string GetValue(string key, string defaultValue) + { + var value = Environment.GetEnvironmentVariable(key); + return string.IsNullOrWhiteSpace(value) ? defaultValue : value; + } + } +} + +// Extensions method for Dapper (to simulate Dapper behavior) +public static class DapperExtensions +{ + public static Task ExecuteAsync(this IDbConnection connection, string sql, object param = null) + { + // Simplified for the example + using (var command = connection.CreateCommand()) + { + command.CommandText = sql; + if (param is IEnumerable enumerable) + { + int totalRows = 0; + foreach (var p in enumerable) + { + // This is highly simplified and doesn't handle parameters correctly + totalRows++; + } + return Task.FromResult(totalRows); + } + + return Task.FromResult(command.ExecuteNonQuery()); + } + } + + public static Task QuerySingleAsync(this IDbConnection connection, string sql, object param = null) + { + // Simplified for the example + using (var command = connection.CreateCommand()) + { + command.CommandText = sql; + // This is highly simplified + return Task.FromResult((T)(object)1); + } + } + + public static Task> QueryAsync(this IDbConnection connection, string sql, object param = null) + { + // Simplified for the example + if (typeof(T) == typeof(Customer)) + { + var customers = new List + { + new Customer { Id = 1, Name = "Alice Smith", Email = "alice@example.com" }, + new Customer { Id = 2, Name = "Bob Jones", Email = "bob@example.com" } + }; + return Task.FromResult((IEnumerable)(object)customers); + } + + return Task.FromResult((IEnumerable)new List()); + } + + public static Task> QueryAsync( + this IDbConnection connection, + string sql, + Func map, + object param = null, + string splitOn = "Id") + { + // Simplified for the example + if (typeof(T1) == typeof(Order) && typeof(T2) == typeof(Customer)) + { + var customer = new Customer { Id = 1, Name = "Alice Smith", Email = "alice@example.com" }; + var order = new Order { Id = 1, CustomerId = 1, Amount = 125.50, OrderDate = DateTime.Now.ToString("yyyy-MM-dd") }; + + var result = new List + { + map((T1)(object)order, (T2)(object)customer) + }; + + return Task.FromResult((IEnumerable)result); + } + else if (typeof(T1) == typeof(Order) && typeof(T2) == typeof(OrderItem)) + { + var order = new Order { Id = 1, CustomerId = 1, Amount = 125.50, OrderDate = DateTime.Now.ToString("yyyy-MM-dd") }; + var items = new List + { + new OrderItem { Id = 1, OrderId = 1, ProductName = "Keyboard", Quantity = 1, UnitPrice = 75.50 }, + new OrderItem { Id = 2, OrderId = 1, ProductName = "Mouse", Quantity = 1, UnitPrice = 50.00 } + }; + + var result = new List(); + foreach (var item in items) + { + result.Add(map((T1)(object)order, (T2)(object)item)); + } + + return Task.FromResult((IEnumerable)result); + } + + return Task.FromResult((IEnumerable)new List()); + } + + public static Task ExecuteScalarAsync(this IDbConnection connection, string sql, object param = null) + { + // Simplified for the example + return Task.FromResult((T)(object)125.50); + } +} \ No newline at end of file diff --git a/snippets/databases/sql_examples.py b/snippets/databases/sql_examples.py new file mode 100644 index 0000000..a6f3ac0 --- /dev/null +++ b/snippets/databases/sql_examples.py @@ -0,0 +1,637 @@ +#!/usr/bin/env python3 +""" +SQL Examples for Relational Databases in Python + +This file demonstrates using SQL with different database engines in Python. +It includes examples for SQLite, PostgreSQL, and MySQL. +""" + +import sqlite3 +import os +from datetime import datetime + +# Try to import optional database libraries +try: + import psycopg2 + POSTGRESQL_AVAILABLE = True +except ImportError: + POSTGRESQL_AVAILABLE = False + +try: + import mysql.connector + MYSQL_AVAILABLE = True +except ImportError: + MYSQL_AVAILABLE = False + +# Try to import dotenv for environment variables +try: + from dotenv import load_dotenv + DOTENV_AVAILABLE = True + # Load environment variables from .env file + load_dotenv() +except ImportError: + DOTENV_AVAILABLE = False + print("python-dotenv is not installed. Install it with: pip install python-dotenv") + print("Continuing with default/example values for database credentials.") + + +# Function to get environment variables with fallback to default values +def get_env(key, default): + """Get environment variable with a default fallback value""" + return os.environ.get(key, default) + + +# ============= SQLite Examples ============= + +def sqlite_basic_example(): + """Basic SQLite database operations""" + print("\n=== SQLite Basic Example ===") + + # Connect to SQLite database (creates file if it doesn't exist) + db_path = get_env("SQLITE_DB_PATH", ":memory:") # Default to in-memory database + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Create a table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT UNIQUE NOT NULL, + age INTEGER, + created_at TEXT + ) + ''') + + # Insert data + users = [ + ('Alice', 'alice@example.com', 28, datetime.now().isoformat()), + ('Bob', 'bob@example.com', 35, datetime.now().isoformat()), + ('Charlie', 'charlie@example.com', 42, datetime.now().isoformat()), + ] + + cursor.executemany( + 'INSERT INTO users (name, email, age, created_at) VALUES (?, ?, ?, ?)', + users + ) + + # Commit the changes + conn.commit() + + # Query the database + print("All users:") + cursor.execute('SELECT * FROM users') + for row in cursor.fetchall(): + print(row) + + # Filtered query + print("\nUsers older than 30:") + cursor.execute('SELECT name, age FROM users WHERE age > ?', (30,)) + for name, age in cursor.fetchall(): + print(f"{name}: {age} years old") + + # Update data + cursor.execute( + 'UPDATE users SET age = ? WHERE name = ?', + (29, 'Alice') + ) + conn.commit() + + # Delete data + cursor.execute('DELETE FROM users WHERE name = ?', ('Charlie',)) + conn.commit() + + # Check the results + print("\nAfter update and delete:") + cursor.execute('SELECT * FROM users') + for row in cursor.fetchall(): + print(row) + + # Close the connection + conn.close() + + +def sqlite_advanced_example(): + """Advanced SQLite operations including JOINs, transactions, and more""" + print("\n=== SQLite Advanced Example ===") + + # Connect to SQLite database + db_path = get_env("SQLITE_DB_PATH", ":memory:") # Default to in-memory database + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row # Return rows as dictionary-like objects + cursor = conn.cursor() + + # Create tables for a blog application + cursor.executescript(''' + CREATE TABLE IF NOT EXISTS authors ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT UNIQUE NOT NULL + ); + + CREATE TABLE IF NOT EXISTS categories ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL + ); + + CREATE TABLE IF NOT EXISTS posts ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL, + content TEXT NOT NULL, + author_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + published_at TEXT NOT NULL, + FOREIGN KEY (author_id) REFERENCES authors (id), + FOREIGN KEY (category_id) REFERENCES categories (id) + ); + + CREATE TABLE IF NOT EXISTS comments ( + id INTEGER PRIMARY KEY, + post_id INTEGER NOT NULL, + author_name TEXT NOT NULL, + content TEXT NOT NULL, + created_at TEXT NOT NULL, + FOREIGN KEY (post_id) REFERENCES posts (id) + ); + ''') + + # Insert data using a transaction + try: + conn.execute('BEGIN TRANSACTION') + + # Insert authors + cursor.executemany( + 'INSERT INTO authors (name, email) VALUES (?, ?)', + [ + ('John Smith', 'john@example.com'), + ('Jane Doe', 'jane@example.com') + ] + ) + + # Insert categories + cursor.executemany( + 'INSERT INTO categories (name) VALUES (?)', + [('Technology',), ('Travel',), ('Food',)] + ) + + # Insert posts + now = datetime.now().isoformat() + cursor.executemany( + 'INSERT INTO posts (title, content, author_id, category_id, published_at) VALUES (?, ?, ?, ?, ?)', + [ + ('Python Tips', 'Content about Python...', 1, 1, now), + ('Trip to Paris', 'My journey to Paris...', 2, 2, now), + ('Best Pizza Recipes', 'How to make pizza...', 1, 3, now) + ] + ) + + # Insert comments + cursor.executemany( + 'INSERT INTO comments (post_id, author_name, content, created_at) VALUES (?, ?, ?, ?)', + [ + (1, 'Anonymous', 'Great tips!', now), + (1, 'Coder123', 'I learned a lot!', now), + (2, 'Traveler', 'Paris is amazing!', now) + ] + ) + + conn.commit() + print("Transaction committed successfully") + + except Exception as e: + conn.rollback() + print(f"Transaction failed: {e}") + + # Perform a JOIN query to get posts with author and category information + print("\nPosts with author and category:") + query = ''' + SELECT + p.title, + a.name AS author, + c.name AS category, + p.published_at + FROM + posts p + JOIN + authors a ON p.author_id = a.id + JOIN + categories c ON p.category_id = c.id + ORDER BY + p.published_at DESC + ''' + + cursor.execute(query) + for row in cursor: + print(f"'{row['title']}' by {row['author']} in {row['category']} ({row['published_at']})") + + # Count comments per post + print("\nComment count per post:") + query = ''' + SELECT + p.title, + COUNT(c.id) AS comment_count + FROM + posts p + LEFT JOIN + comments c ON p.id = c.post_id + GROUP BY + p.id + ORDER BY + comment_count DESC + ''' + + cursor.execute(query) + for row in cursor: + print(f"'{row['title']}': {row['comment_count']} comments") + + # Aggregate functions + print("\nAuthor statistics:") + query = ''' + SELECT + a.name, + COUNT(p.id) AS post_count, + COUNT(c.id) AS comment_count + FROM + authors a + LEFT JOIN + posts p ON a.id = p.author_id + LEFT JOIN + comments c ON p.id = c.post_id + GROUP BY + a.id + ''' + + cursor.execute(query) + for row in cursor: + print(f"{row['name']}: {row['post_count']} posts, {row['comment_count']} comments") + + # Using subqueries + print("\nPosts with more than 1 comment:") + query = ''' + SELECT + p.title, + (SELECT COUNT(*) FROM comments WHERE post_id = p.id) AS comment_count + FROM + posts p + WHERE + (SELECT COUNT(*) FROM comments WHERE post_id = p.id) > 1 + ''' + + cursor.execute(query) + for row in cursor: + print(f"'{row['title']}': {row['comment_count']} comments") + + # Close the connection + conn.close() + + +# ============= PostgreSQL Examples ============= + +def postgresql_example(): + """PostgreSQL database operations""" + if not POSTGRESQL_AVAILABLE: + print("\n=== PostgreSQL Example ===") + print("psycopg2 module is not installed. Install it with: pip install psycopg2-binary") + return + + # This code won't run unless psycopg2 is installed + print("\n=== PostgreSQL Example ===") + print("To run this example, you need a PostgreSQL server, the psycopg2 module, and proper environment variables.") + print("Normally, this would use credentials from .env. Sample code:") + + # Connection parameters from environment variables + params = { + 'host': get_env('PG_HOST', 'localhost'), + 'database': get_env('PG_DATABASE', 'mydb'), + 'user': get_env('PG_USER', 'postgres'), + 'password': get_env('PG_PASSWORD', 'password') # In real code, no default should be provided + } + + print(f"Would connect to: {params['host']}/{params['database']} as {params['user']}") + print("The actual implementation would execute:") + + """ + # Connect to PostgreSQL server + conn = psycopg2.connect(**params) + cursor = conn.cursor() + + # Create a table with PostgreSQL-specific features + cursor.execute(''' + CREATE TABLE IF NOT EXISTS products ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + description TEXT, + price DECIMAL(10,2) NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + tags TEXT[], + metadata JSONB + ) + ''') + conn.commit() + + # Insert data with PostgreSQL-specific data types + cursor.execute(''' + INSERT INTO products (name, description, price, tags, metadata) + VALUES (%s, %s, %s, %s, %s) + ''', ( + 'Laptop', + 'High-performance laptop', + 999.99, + ['electronics', 'computers'], + {'brand': 'TechBrand', 'warranty': '2 years', 'specs': {'cpu': 'i7', 'ram': '16GB'}} + )) + conn.commit() + + # Query with PostgreSQL-specific features + cursor.execute(''' + SELECT + name, + price, + metadata->>'brand' AS brand, + metadata->'specs'->>'cpu' AS cpu + FROM + products + WHERE + 'electronics' = ANY(tags) + ''') + + for row in cursor.fetchall(): + print(row) + + # Using PostgreSQL-specific functions + cursor.execute(''' + SELECT + name, + price, + extract(year from created_at) AS year, + array_length(tags, 1) AS tag_count + FROM + products + ''') + + for row in cursor.fetchall(): + print(row) + + conn.close() + """ + + +# ============= MySQL Examples ============= + +def mysql_example(): + """MySQL database operations""" + if not MYSQL_AVAILABLE: + print("\n=== MySQL Example ===") + print("mysql-connector-python module is not installed. Install it with: pip install mysql-connector-python") + return + + # This code won't run unless mysql-connector is installed + print("\n=== MySQL Example ===") + print("To run this example, you need a MySQL server, the mysql-connector-python module, and proper environment variables.") + print("Normally, this would use credentials from .env. Sample code:") + + # Connection parameters from environment variables + config = { + 'host': get_env('MYSQL_HOST', 'localhost'), + 'database': get_env('MYSQL_DATABASE', 'mydb'), + 'user': get_env('MYSQL_USER', 'root'), + 'password': get_env('MYSQL_PASSWORD', 'password') # In real code, no default should be provided + } + + print(f"Would connect to: {config['host']}/{config['database']} as {config['user']}") + print("The actual implementation would execute:") + + """ + # Connect to MySQL server + conn = mysql.connector.connect(**config) + cursor = conn.cursor() + + # Create a table with MySQL-specific features + cursor.execute(''' + CREATE TABLE IF NOT EXISTS orders ( + id INT AUTO_INCREMENT PRIMARY KEY, + customer_name VARCHAR(100) NOT NULL, + total DECIMAL(10,2) NOT NULL, + status ENUM('pending', 'shipped', 'delivered') NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_status (status), + INDEX idx_created_at (created_at) + ) ENGINE=InnoDB + ''') + conn.commit() + + # Insert data + cursor.execute(''' + INSERT INTO orders (customer_name, total, status) + VALUES (%s, %s, %s) + ''', ('John Doe', 123.45, 'pending')) + + last_id = cursor.lastrowid + print(f"Inserted order with ID: {last_id}") + conn.commit() + + # MySQL-specific date functions + cursor.execute(''' + SELECT + id, + customer_name, + total, + status, + DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS formatted_date, + DATEDIFF(NOW(), created_at) AS days_since_creation + FROM + orders + WHERE + created_at > DATE_SUB(NOW(), INTERVAL 7 DAY) + ''') + + for row in cursor: + print(row) + + conn.close() + """ + + +# ============= ORM Example with SQLAlchemy ============= + +def sqlalchemy_example(): + """Example of using SQLAlchemy ORM""" + try: + from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, func + from sqlalchemy.ext.declarative import declarative_base + from sqlalchemy.orm import sessionmaker, relationship + except ImportError: + print("\n=== SQLAlchemy ORM Example ===") + print("SQLAlchemy is not installed. Install it with: pip install sqlalchemy") + return + + print("\n=== SQLAlchemy ORM Example ===") + + # Database URL from environment variables + # Example: postgresql://user:password@localhost/dbname + db_url = get_env('SQLALCHEMY_DATABASE_URL', 'sqlite:///:memory:') + print(f"Using database URL: {db_url}") + + # Create engine and base + engine = create_engine(db_url) + Base = declarative_base() + + # Define models + class Customer(Base): + __tablename__ = 'customers' + + id = Column(Integer, primary_key=True) + name = Column(String(100), nullable=False) + email = Column(String(100), unique=True, nullable=False) + + # Relationship + orders = relationship("Order", back_populates="customer") + + def __repr__(self): + return f"" + + class Product(Base): + __tablename__ = 'products' + + id = Column(Integer, primary_key=True) + name = Column(String(100), nullable=False) + price = Column(Float, nullable=False) + + # Relationship + order_items = relationship("OrderItem", back_populates="product") + + def __repr__(self): + return f"" + + class Order(Base): + __tablename__ = 'orders' + + id = Column(Integer, primary_key=True) + customer_id = Column(Integer, ForeignKey('customers.id'), nullable=False) + created_at = Column(DateTime, default=datetime.now) + + # Relationships + customer = relationship("Customer", back_populates="orders") + items = relationship("OrderItem", back_populates="order") + + def __repr__(self): + return f"" + + class OrderItem(Base): + __tablename__ = 'order_items' + + id = Column(Integer, primary_key=True) + order_id = Column(Integer, ForeignKey('orders.id'), nullable=False) + product_id = Column(Integer, ForeignKey('products.id'), nullable=False) + quantity = Column(Integer, default=1) + + # Relationships + order = relationship("Order", back_populates="items") + product = relationship("Product", back_populates="order_items") + + def __repr__(self): + return f"" + + # Create tables + Base.metadata.create_all(engine) + + # Create session + Session = sessionmaker(bind=engine) + session = Session() + + # Add data + try: + # Add customers + customer1 = Customer(name="Alice Smith", email="alice@example.com") + customer2 = Customer(name="Bob Johnson", email="bob@example.com") + session.add_all([customer1, customer2]) + + # Add products + product1 = Product(name="Laptop", price=999.99) + product2 = Product(name="Smartphone", price=499.99) + product3 = Product(name="Headphones", price=99.99) + session.add_all([product1, product2, product3]) + + # Commit to get IDs + session.commit() + + # Create orders + order1 = Order(customer_id=customer1.id) + session.add(order1) + session.commit() + + # Add items to order + item1 = OrderItem(order_id=order1.id, product_id=product1.id, quantity=1) + item2 = OrderItem(order_id=order1.id, product_id=product3.id, quantity=2) + session.add_all([item1, item2]) + + order2 = Order(customer_id=customer2.id) + session.add(order2) + session.commit() + + item3 = OrderItem(order_id=order2.id, product_id=product2.id, quantity=1) + session.add(item3) + + session.commit() + + print("Data inserted successfully") + + except Exception as e: + session.rollback() + print(f"Error: {e}") + + # Query with ORM + print("\nAll customers:") + customers = session.query(Customer).all() + for customer in customers: + print(customer) + + print("\nAll products:") + products = session.query(Product).all() + for product in products: + print(product) + + print("\nOrders with customer info:") + orders = session.query(Order, Customer.name).join(Customer).all() + for order, customer_name in orders: + print(f"Order {order.id} by {customer_name}") + + print("\nOrder details:") + order_details = session.query( + Order.id.label('order_id'), + Customer.name.label('customer'), + Product.name.label('product'), + OrderItem.quantity, + (Product.price * OrderItem.quantity).label('subtotal') + ).join(Customer).join(OrderItem).join(Product).all() + + for detail in order_details: + print(f"Order #{detail.order_id}: {detail.customer} ordered {detail.quantity}x {detail.product} (${detail.subtotal:.2f})") + + print("\nOrder totals:") + order_totals = session.query( + Order.id, + func.sum(Product.price * OrderItem.quantity).label('total') + ).join(OrderItem).join(Product).group_by(Order.id).all() + + for order_id, total in order_totals: + print(f"Order #{order_id} total: ${total:.2f}") + + # Close session + session.close() + + +if __name__ == "__main__": + # SQLite examples + sqlite_basic_example() + sqlite_advanced_example() + + # PostgreSQL example (requires psycopg2) + postgresql_example() + + # MySQL example (requires mysql-connector-python) + mysql_example() + + # SQLAlchemy ORM example (requires sqlalchemy) + sqlalchemy_example() \ No newline at end of file diff --git a/snippets/design-patterns/factory/FactoryPattern.cpp b/snippets/design-patterns/factory/FactoryPattern.cpp new file mode 100644 index 0000000..3ceb7a8 --- /dev/null +++ b/snippets/design-patterns/factory/FactoryPattern.cpp @@ -0,0 +1,362 @@ +/** + * Factory Pattern Implementation in C++ + * + * The Factory Pattern is a creational design pattern that provides an interface for creating + * objects in a superclass, but allows subclasses to alter the type of objects that will be created. + * + * This example demonstrates a Vehicle Factory that can create different types of vehicles + * (Car, Motorcycle, Truck) based on the client's requirements. + */ + +#include +#include +#include +#include + +// Abstract Product - Vehicle +class Vehicle { +public: + Vehicle(const std::string& make, const std::string& model, int year) + : make_(make), model_(model), year_(year) {} + + virtual ~Vehicle() = default; + + virtual std::string getInfo() const { + return std::to_string(year_) + " " + make_ + " " + model_; + } + + virtual std::string start() const { + return getInfo() + " is starting..."; + } + + virtual std::string stop() const { + return getInfo() + " is stopping..."; + } + +protected: + std::string make_; + std::string model_; + int year_; +}; + +// Concrete Products +class Car : public Vehicle { +public: + Car(const std::string& make, const std::string& model, int year, int doors = 4) + : Vehicle(make, model, year), doors_(doors) {} + + std::string getInfo() const override { + return Vehicle::getInfo() + " (" + std::to_string(doors_) + "-door car)"; + } + + std::string drive() const { + return getInfo() + " is driving on the road."; + } + +private: + int doors_; +}; + +class Motorcycle : public Vehicle { +public: + Motorcycle(const std::string& make, const std::string& model, int year, int engineSize) + : Vehicle(make, model, year), engineSize_(engineSize) {} + + std::string getInfo() const override { + return Vehicle::getInfo() + " (" + std::to_string(engineSize_) + "cc motorcycle)"; + } + + std::string ride() const { + return getInfo() + " is riding at high speed."; + } + +private: + int engineSize_; +}; + +class Truck : public Vehicle { +public: + Truck(const std::string& make, const std::string& model, int year, double capacity) + : Vehicle(make, model, year), capacity_(capacity) {} + + std::string getInfo() const override { + return Vehicle::getInfo() + " (" + std::to_string(capacity_) + " ton truck)"; + } + + std::string haul() const { + return getInfo() + " is hauling cargo."; + } + +private: + double capacity_; +}; + +// Simple Factory +class VehicleFactory { +public: + enum VehicleType { + CAR, + MOTORCYCLE, + TRUCK + }; + + static std::unique_ptr createVehicle(VehicleType type, + const std::string& make, + const std::string& model, + int year, + const std::vector& options = {}) { + switch (type) { + case CAR: + return std::make_unique(make, model, year, + options.size() > 0 ? static_cast(options[0]) : 4); + case MOTORCYCLE: + return std::make_unique(make, model, year, + options.size() > 0 ? static_cast(options[0]) : 250); + case TRUCK: + return std::make_unique(make, model, year, + options.size() > 0 ? options[0] : 5.0); + default: + throw std::invalid_argument("Unknown vehicle type"); + } + } +}; + +// Factory Method Pattern Implementation +class VehicleFactoryMethod { +public: + virtual ~VehicleFactoryMethod() = default; + + virtual std::unique_ptr createVehicle(const std::string& make, + const std::string& model, + int year, + const std::vector& options = {}) = 0; + + std::unique_ptr registerVehicle(const std::string& make, + const std::string& model, + int year, + const std::vector& options = {}) { + // Common operations for all vehicles + auto vehicle = createVehicle(make, model, year, options); + std::cout << "Registering " << vehicle->getInfo() << std::endl; + std::cout << "Assigning license plate" << std::endl; + return vehicle; + } +}; + +// Concrete Factories +class CarFactory : public VehicleFactoryMethod { +public: + std::unique_ptr createVehicle(const std::string& make, + const std::string& model, + int year, + const std::vector& options = {}) override { + return std::make_unique(make, model, year, + options.size() > 0 ? static_cast(options[0]) : 4); + } +}; + +class MotorcycleFactory : public VehicleFactoryMethod { +public: + std::unique_ptr createVehicle(const std::string& make, + const std::string& model, + int year, + const std::vector& options = {}) override { + return std::make_unique(make, model, year, + options.size() > 0 ? static_cast(options[0]) : 250); + } +}; + +class TruckFactory : public VehicleFactoryMethod { +public: + std::unique_ptr createVehicle(const std::string& make, + const std::string& model, + int year, + const std::vector& options = {}) override { + return std::make_unique(make, model, year, + options.size() > 0 ? options[0] : 5.0); + } +}; + +// Abstract Factory Pattern Implementation +// Parts interfaces +class Engine { +public: + Engine(const std::string& type, int horsepower) + : type_(type), horsepower_(horsepower) {} + + virtual ~Engine() = default; + + std::string getSpecs() const { + return type_ + " engine with " + std::to_string(horsepower_) + "hp"; + } + +private: + std::string type_; + int horsepower_; +}; + +class Transmission { +public: + Transmission(const std::string& type, int gears) + : type_(type), gears_(gears) {} + + virtual ~Transmission() = default; + + std::string getSpecs() const { + return type_ + " transmission with " + std::to_string(gears_) + " gears"; + } + +private: + std::string type_; + int gears_; +}; + +class Chassis { +public: + Chassis(const std::string& material, double weight) + : material_(material), weight_(weight) {} + + virtual ~Chassis() = default; + + std::string getSpecs() const { + return material_ + " chassis weighing " + std::to_string(weight_) + "kg"; + } + +private: + std::string material_; + double weight_; +}; + +// Abstract Factory +class VehiclePartsFactory { +public: + virtual ~VehiclePartsFactory() = default; + virtual std::unique_ptr createEngine() = 0; + virtual std::unique_ptr createTransmission() = 0; + virtual std::unique_ptr createChassis() = 0; +}; + +// Concrete Abstract Factories +class SportVehiclePartsFactory : public VehiclePartsFactory { +public: + std::unique_ptr createEngine() override { + return std::make_unique("V8", 450); + } + + std::unique_ptr createTransmission() override { + return std::make_unique("Manual", 6); + } + + std::unique_ptr createChassis() override { + return std::make_unique("Carbon Fiber", 120); + } +}; + +class EconomyVehiclePartsFactory : public VehiclePartsFactory { +public: + std::unique_ptr createEngine() override { + return std::make_unique("Inline-4", 180); + } + + std::unique_ptr createTransmission() override { + return std::make_unique("Automatic", 5); + } + + std::unique_ptr createChassis() override { + return std::make_unique("Steel", 300); + } +}; + +class HeavyDutyVehiclePartsFactory : public VehiclePartsFactory { +public: + std::unique_ptr createEngine() override { + return std::make_unique("Diesel V6", 350); + } + + std::unique_ptr createTransmission() override { + return std::make_unique("Manual", 8); + } + + std::unique_ptr createChassis() override { + return std::make_unique("Reinforced Steel", 800); + } +}; + +// Vehicle Assembler - Uses the Abstract Factory +class VehicleAssembler { +public: + VehicleAssembler(std::unique_ptr factory) + : partsFactory_(std::move(factory)) {} + + void assembleVehicle() { + auto engine = partsFactory_->createEngine(); + auto transmission = partsFactory_->createTransmission(); + auto chassis = partsFactory_->createChassis(); + + std::cout << "Assembling vehicle with:" << std::endl; + std::cout << "- " << engine->getSpecs() << std::endl; + std::cout << "- " << transmission->getSpecs() << std::endl; + std::cout << "- " << chassis->getSpecs() << std::endl; + } + +private: + std::unique_ptr partsFactory_; +}; + +// Client code demonstration +void clientCode() { + std::cout << "===== Simple Factory Pattern =====" << std::endl; + + auto car = VehicleFactory::createVehicle(VehicleFactory::CAR, "Toyota", "Camry", 2023, {4}); + auto motorcycle = VehicleFactory::createVehicle(VehicleFactory::MOTORCYCLE, "Honda", "CBR", 2023, {600}); + auto truck = VehicleFactory::createVehicle(VehicleFactory::TRUCK, "Ford", "F-150", 2023, {3.0}); + + std::cout << car->getInfo() << std::endl; + std::cout << static_cast(car.get())->drive() << std::endl; + + std::cout << motorcycle->getInfo() << std::endl; + std::cout << static_cast(motorcycle.get())->ride() << std::endl; + + std::cout << truck->getInfo() << std::endl; + std::cout << static_cast(truck.get())->haul() << std::endl; + + std::cout << "\n===== Factory Method Pattern =====" << std::endl; + + CarFactory carFactory; + MotorcycleFactory motorcycleFactory; + TruckFactory truckFactory; + + auto newCar = carFactory.registerVehicle("BMW", "3 Series", 2023, {2}); + auto newMotorcycle = motorcycleFactory.registerVehicle("Ducati", "Monster", 2023, {821}); + auto newTruck = truckFactory.registerVehicle("Volvo", "VNL", 2023, {20.0}); + + std::cout << static_cast(newCar.get())->drive() << std::endl; + std::cout << static_cast(newMotorcycle.get())->ride() << std::endl; + std::cout << static_cast(newTruck.get())->haul() << std::endl; + + std::cout << "\n===== Abstract Factory Pattern =====" << std::endl; + + std::cout << "Building a sports car:" << std::endl; + VehicleAssembler sportCarAssembler(std::make_unique()); + sportCarAssembler.assembleVehicle(); + + std::cout << "\nBuilding an economy car:" << std::endl; + VehicleAssembler economyCarAssembler(std::make_unique()); + economyCarAssembler.assembleVehicle(); + + std::cout << "\nBuilding a heavy duty truck:" << std::endl; + VehicleAssembler heavyDutyTruckAssembler(std::make_unique()); + heavyDutyTruckAssembler.assembleVehicle(); +} + +// Main function, run the example +int main() { + try { + clientCode(); + } catch (const std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; + return 1; + } + return 0; +} + diff --git a/snippets/design-patterns/factory/FactoryPattern.cs b/snippets/design-patterns/factory/FactoryPattern.cs new file mode 100644 index 0000000..fb42d26 --- /dev/null +++ b/snippets/design-patterns/factory/FactoryPattern.cs @@ -0,0 +1,407 @@ +/** + * Factory Pattern Implementation in C# + * + * The Factory Pattern is a creational design pattern that provides an interface for creating + * objects in a superclass, but allows subclasses to alter the type of objects that will be created. + * + * This example demonstrates a Vehicle Factory that can create different types of vehicles + * (Car, Motorcycle, Truck) based on the client's requirements. + */ + +using System; +using System.Collections.Generic; + +namespace DesignPatterns.Factory +{ + // Abstract Product - Vehicle + public abstract class Vehicle + { + protected string Make { get; } + protected string Model { get; } + protected int Year { get; } + + protected Vehicle(string make, string model, int year) + { + Make = make; + Model = model; + Year = year; + } + + public virtual string GetInfo() + { + return $"{Year} {Make} {Model}"; + } + + public virtual string Start() + { + return $"{GetInfo()} is starting..."; + } + + public virtual string Stop() + { + return $"{GetInfo()} is stopping..."; + } + } + + // Concrete Products + public class Car : Vehicle + { + public int Doors { get; } + + public Car(string make, string model, int year, int doors = 4) + : base(make, model, year) + { + Doors = doors; + } + + public override string GetInfo() + { + return $"{base.GetInfo()} ({Doors}-door car)"; + } + + public string Drive() + { + return $"{GetInfo()} is driving on the road."; + } + } + + public class Motorcycle : Vehicle + { + public int EngineSize { get; } + + public Motorcycle(string make, string model, int year, int engineSize) + : base(make, model, year) + { + EngineSize = engineSize; + } + + public override string GetInfo() + { + return $"{base.GetInfo()} ({EngineSize}cc motorcycle)"; + } + + public string Ride() + { + return $"{GetInfo()} is riding at high speed."; + } + } + + public class Truck : Vehicle + { + public double Capacity { get; } + + public Truck(string make, string model, int year, double capacity) + : base(make, model, year) + { + Capacity = capacity; + } + + public override string GetInfo() + { + return $"{base.GetInfo()} ({Capacity} ton truck)"; + } + + public string Haul() + { + return $"{GetInfo()} is hauling cargo."; + } + } + + // Simple Factory + public class VehicleFactory + { + public enum VehicleType + { + Car, + Motorcycle, + Truck + } + + public static Vehicle CreateVehicle(VehicleType type, string make, string model, int year, object options = null) + { + switch (type) + { + case VehicleType.Car: + int doors = 4; + if (options is int doorsOption) + { + doors = doorsOption; + } + return new Car(make, model, year, doors); + + case VehicleType.Motorcycle: + int engineSize = 250; + if (options is int engineSizeOption) + { + engineSize = engineSizeOption; + } + return new Motorcycle(make, model, year, engineSize); + + case VehicleType.Truck: + double capacity = 5.0; + if (options is double capacityOption) + { + capacity = capacityOption; + } + return new Truck(make, model, year, capacity); + + default: + throw new ArgumentException($"Vehicle type {type} is not supported."); + } + } + } + + // Factory Method Pattern Implementation + public abstract class VehicleFactoryMethod + { + public abstract Vehicle CreateVehicle(string make, string model, int year, object options = null); + + public Vehicle RegisterVehicle(string make, string model, int year, object options = null) + { + // Common operations for all vehicles + Vehicle vehicle = CreateVehicle(make, model, year, options); + Console.WriteLine($"Registering {vehicle.GetInfo()}"); + Console.WriteLine("Assigning license plate"); + return vehicle; + } + } + + // Concrete Factories + public class CarFactory : VehicleFactoryMethod + { + public override Vehicle CreateVehicle(string make, string model, int year, object options = null) + { + int doors = 4; + if (options is int doorsOption) + { + doors = doorsOption; + } + return new Car(make, model, year, doors); + } + } + + public class MotorcycleFactory : VehicleFactoryMethod + { + public override Vehicle CreateVehicle(string make, string model, int year, object options = null) + { + int engineSize = 250; + if (options is int engineSizeOption) + { + engineSize = engineSizeOption; + } + return new Motorcycle(make, model, year, engineSize); + } + } + + public class TruckFactory : VehicleFactoryMethod + { + public override Vehicle CreateVehicle(string make, string model, int year, object options = null) + { + double capacity = 5.0; + if (options is double capacityOption) + { + capacity = capacityOption; + } + return new Truck(make, model, year, capacity); + } + } + + // Abstract Factory Pattern Implementation + // Parts + public class Engine + { + public string Type { get; } + public int Horsepower { get; } + + public Engine(string type, int horsepower) + { + Type = type; + Horsepower = horsepower; + } + + public string GetSpecs() + { + return $"{Type} engine with {Horsepower}hp"; + } + } + + public class Transmission + { + public string Type { get; } + public int Gears { get; } + + public Transmission(string type, int gears) + { + Type = type; + Gears = gears; + } + + public string GetSpecs() + { + return $"{Type} transmission with {Gears} gears"; + } + } + + public class Chassis + { + public string Material { get; } + public double Weight { get; } + + public Chassis(string material, double weight) + { + Material = material; + Weight = weight; + } + + public string GetSpecs() + { + return $"{Material} chassis weighing {Weight}kg"; + } + } + + // Abstract Factory + public interface IVehiclePartsFactory + { + Engine CreateEngine(); + Transmission CreateTransmission(); + Chassis CreateChassis(); + } + + // Concrete Abstract Factories + public class SportVehiclePartsFactory : IVehiclePartsFactory + { + public Engine CreateEngine() + { + return new Engine("V8", 450); + } + + public Transmission CreateTransmission() + { + return new Transmission("Manual", 6); + } + + public Chassis CreateChassis() + { + return new Chassis("Carbon Fiber", 120); + } + } + + public class EconomyVehiclePartsFactory : IVehiclePartsFactory + { + public Engine CreateEngine() + { + return new Engine("Inline-4", 180); + } + + public Transmission CreateTransmission() + { + return new Transmission("Automatic", 5); + } + + public Chassis CreateChassis() + { + return new Chassis("Steel", 300); + } + } + + public class HeavyDutyVehiclePartsFactory : IVehiclePartsFactory + { + public Engine CreateEngine() + { + return new Engine("Diesel V6", 350); + } + + public Transmission CreateTransmission() + { + return new Transmission("Manual", 8); + } + + public Chassis CreateChassis() + { + return new Chassis("Reinforced Steel", 800); + } + } + + // Vehicle Assembler - Uses the Abstract Factory + public class VehicleAssembler + { + private readonly IVehiclePartsFactory _partsFactory; + + public VehicleAssembler(IVehiclePartsFactory partsFactory) + { + _partsFactory = partsFactory; + } + + public void AssembleVehicle() + { + Engine engine = _partsFactory.CreateEngine(); + Transmission transmission = _partsFactory.CreateTransmission(); + Chassis chassis = _partsFactory.CreateChassis(); + + Console.WriteLine("Assembling vehicle with:"); + Console.WriteLine($"- {engine.GetSpecs()}"); + Console.WriteLine($"- {transmission.GetSpecs()}"); + Console.WriteLine($"- {chassis.GetSpecs()}"); + } + } + + // Client code demonstration + public class Client + { + public static void DemonstrateFactoryPattern() + { + Console.WriteLine("===== Simple Factory Pattern ====="); + + Vehicle car = VehicleFactory.CreateVehicle(VehicleFactory.VehicleType.Car, "Toyota", "Camry", 2023, 4); + Vehicle motorcycle = VehicleFactory.CreateVehicle(VehicleFactory.VehicleType.Motorcycle, "Honda", "CBR", 2023, 600); + Vehicle truck = VehicleFactory.CreateVehicle(VehicleFactory.VehicleType.Truck, "Ford", "F-150", 2023, 3.0); + + Console.WriteLine(car.GetInfo()); + Console.WriteLine(((Car)car).Drive()); + + Console.WriteLine(motorcycle.GetInfo()); + Console.WriteLine(((Motorcycle)motorcycle).Ride()); + + Console.WriteLine(truck.GetInfo()); + Console.WriteLine(((Truck)truck).Haul()); + + Console.WriteLine("\n===== Factory Method Pattern ====="); + + CarFactory carFactory = new CarFactory(); + MotorcycleFactory motorcycleFactory = new MotorcycleFactory(); + TruckFactory truckFactory = new TruckFactory(); + + Vehicle newCar = carFactory.RegisterVehicle("BMW", "3 Series", 2023, 2); + Vehicle newMotorcycle = motorcycleFactory.RegisterVehicle("Ducati", "Monster", 2023, 821); + Vehicle newTruck = truckFactory.RegisterVehicle("Volvo", "VNL", 2023, 20.0); + + Console.WriteLine(((Car)newCar).Drive()); + Console.WriteLine(((Motorcycle)newMotorcycle).Ride()); + Console.WriteLine(((Truck)newTruck).Haul()); + + Console.WriteLine("\n===== Abstract Factory Pattern ====="); + + Console.WriteLine("Building a sports car:"); + VehicleAssembler sportCarAssembler = new VehicleAssembler(new SportVehiclePartsFactory()); + sportCarAssembler.AssembleVehicle(); + + Console.WriteLine("\nBuilding an economy car:"); + VehicleAssembler economyCarAssembler = new VehicleAssembler(new EconomyVehiclePartsFactory()); + economyCarAssembler.AssembleVehicle(); + + Console.WriteLine("\nBuilding a heavy duty truck:"); + VehicleAssembler heavyDutyTruckAssembler = new VehicleAssembler(new HeavyDutyVehiclePartsFactory()); + heavyDutyTruckAssembler.AssembleVehicle(); + } + } + + // Main program, run the example + class Program + { + static void Main(string[] args) + { + Client.DemonstrateFactoryPattern(); + Console.ReadLine(); + } + } +} diff --git a/snippets/design-patterns/factory/FactoryPattern.java b/snippets/design-patterns/factory/FactoryPattern.java new file mode 100644 index 0000000..ce77cfa --- /dev/null +++ b/snippets/design-patterns/factory/FactoryPattern.java @@ -0,0 +1,288 @@ +/** + * Factory Pattern Implementation in Java + * + * This demonstrates three variations of the Factory Pattern: + * 1. Simple Factory + * 2. Factory Method + * 3. Abstract Factory + */ + +// ============= SIMPLE FACTORY EXAMPLE ============= + +// Product interface +interface Product { + void operation(); +} + +// Concrete products +class ConcreteProductA implements Product { + @Override + public void operation() { + System.out.println("Operation of ConcreteProductA"); + } +} + +class ConcreteProductB implements Product { + @Override + public void operation() { + System.out.println("Operation of ConcreteProductB"); + } +} + +// Simple factory +class SimpleFactory { + public Product createProduct(String type) { + if (type.equals("A")) { + return new ConcreteProductA(); + } else if (type.equals("B")) { + return new ConcreteProductB(); + } + throw new IllegalArgumentException("Invalid product type: " + type); + } +} + +// Client for Simple Factory +class SimpleFactoryClient { + public void run() { + System.out.println("\n=== Simple Factory Example ==="); + SimpleFactory factory = new SimpleFactory(); + + Product productA = factory.createProduct("A"); + productA.operation(); + + Product productB = factory.createProduct("B"); + productB.operation(); + } +} + +// ============= FACTORY METHOD EXAMPLE ============= + +// Product interface for Factory Method +interface Transport { + void deliver(); +} + +// Concrete products +class Truck implements Transport { + @Override + public void deliver() { + System.out.println("Delivering by land in a truck"); + } +} + +class Ship implements Transport { + @Override + public void deliver() { + System.out.println("Delivering by sea in a ship"); + } +} + +// Creator abstract class with factory method +abstract class Logistics { + // Factory method + public abstract Transport createTransport(); + + // Business logic that uses the factory method + public void planDelivery() { + System.out.println("Preparing for delivery..."); + Transport transport = createTransport(); + System.out.println("Planning route..."); + transport.deliver(); + System.out.println("Delivery complete!"); + } +} + +// Concrete creators override factory method +class RoadLogistics extends Logistics { + @Override + public Transport createTransport() { + return new Truck(); + } +} + +class SeaLogistics extends Logistics { + @Override + public Transport createTransport() { + return new Ship(); + } +} + +// Client for Factory Method +class FactoryMethodClient { + public void run() { + System.out.println("\n=== Factory Method Example ==="); + + System.out.println("\nRoad delivery:"); + Logistics roadLogistics = new RoadLogistics(); + roadLogistics.planDelivery(); + + System.out.println("\nSea delivery:"); + Logistics seaLogistics = new SeaLogistics(); + seaLogistics.planDelivery(); + } +} + +// ============= ABSTRACT FACTORY EXAMPLE ============= + +// Abstract products +interface Button { + void render(); + void click(); +} + +interface Checkbox { + void render(); + void toggle(); +} + +// Concrete products for Windows +class WindowsButton implements Button { + @Override + public void render() { + System.out.println("Rendering a Windows button"); + } + + @Override + public void click() { + System.out.println("Windows button clicked"); + } +} + +class WindowsCheckbox implements Checkbox { + @Override + public void render() { + System.out.println("Rendering a Windows checkbox"); + } + + @Override + public void toggle() { + System.out.println("Windows checkbox toggled"); + } +} + +// Concrete products for macOS +class MacOSButton implements Button { + @Override + public void render() { + System.out.println("Rendering a macOS button"); + } + + @Override + public void click() { + System.out.println("macOS button clicked"); + } +} + +class MacOSCheckbox implements Checkbox { + @Override + public void render() { + System.out.println("Rendering a macOS checkbox"); + } + + @Override + public void toggle() { + System.out.println("macOS checkbox toggled"); + } +} + +// Abstract factory interface +interface GUIFactory { + Button createButton(); + Checkbox createCheckbox(); +} + +// Concrete factories +class WindowsFactory implements GUIFactory { + @Override + public Button createButton() { + return new WindowsButton(); + } + + @Override + public Checkbox createCheckbox() { + return new WindowsCheckbox(); + } +} + +class MacOSFactory implements GUIFactory { + @Override + public Button createButton() { + return new MacOSButton(); + } + + @Override + public Checkbox createCheckbox() { + return new MacOSCheckbox(); + } +} + +// Client code that works with factories and products +class Application { + private Button button; + private Checkbox checkbox; + + public Application(GUIFactory factory) { + button = factory.createButton(); + checkbox = factory.createCheckbox(); + } + + public void createUI() { + System.out.println("Creating UI components..."); + button.render(); + checkbox.render(); + } + + public void clickButton() { + button.click(); + } + + public void toggleCheckbox() { + checkbox.toggle(); + } +} + +// Client for Abstract Factory +class AbstractFactoryClient { + public void run() { + System.out.println("\n=== Abstract Factory Example ==="); + + // Simulate different OS environments + createApplicationFor("Windows"); + createApplicationFor("macOS"); + } + + private void createApplicationFor(String os) { + System.out.println("\nCreating app for " + os); + + GUIFactory factory; + if (os.equals("Windows")) { + factory = new WindowsFactory(); + } else if (os.equals("macOS")) { + factory = new MacOSFactory(); + } else { + throw new IllegalArgumentException("Unknown OS: " + os); + } + + Application app = new Application(factory); + app.createUI(); + app.clickButton(); + app.toggleCheckbox(); + } +} + +// Main class to run all examples +public class FactoryPattern { + public static void main(String[] args) { + System.out.println("Factory Pattern Demonstrations"); + System.out.println("=============================="); + + // Run Simple Factory example + new SimpleFactoryClient().run(); + + // Run Factory Method example + new FactoryMethodClient().run(); + + // Run Abstract Factory example + new AbstractFactoryClient().run(); + } +} \ No newline at end of file diff --git a/snippets/design-patterns/factory/factory_pattern.c b/snippets/design-patterns/factory/factory_pattern.c new file mode 100644 index 0000000..9024782 --- /dev/null +++ b/snippets/design-patterns/factory/factory_pattern.c @@ -0,0 +1,547 @@ +/** + * Factory Pattern Implementation in C + * + * The Factory Pattern is a creational design pattern that provides an interface for creating + * objects in a superclass, but allows subclasses to alter the type of objects that will be created. + * + * This example demonstrates a Vehicle Factory that can create different types of vehicles + * (Car, Motorcycle, Truck) based on the client's requirements. + * + * Note: Since C is not an object-oriented language, we use structs with function pointers + * to simulate classes and polymorphism. + */ + +#include +#include +#include +#include + +// Define vehicle types +typedef enum { + VEHICLE_TYPE_CAR, + VEHICLE_TYPE_MOTORCYCLE, + VEHICLE_TYPE_TRUCK +} VehicleType; + +// Forward declarations +typedef struct Vehicle Vehicle; +typedef struct Car Car; +typedef struct Motorcycle Motorcycle; +typedef struct Truck Truck; + +// Function pointer types for common vehicle methods +typedef char* (*GetInfoFunc)(const void*); +typedef char* (*StartFunc)(const void*); +typedef char* (*StopFunc)(const void*); + +// Base "class" for vehicles +struct Vehicle { + char make[50]; + char model[50]; + int year; + + // Virtual functions (function pointers) + GetInfoFunc getInfo; + StartFunc start; + StopFunc stop; +}; + +// Car "class" +struct Car { + Vehicle base; // "Inherit" from Vehicle + int doors; + + // Car-specific method + char* (*drive)(const Car*); +}; + +// Motorcycle "class" +struct Motorcycle { + Vehicle base; // "Inherit" from Vehicle + int engineSize; + + // Motorcycle-specific method + char* (*ride)(const Motorcycle*); +}; + +// Truck "class" +struct Truck { + Vehicle base; // "Inherit" from Vehicle + double capacity; + + // Truck-specific method + char* (*haul)(const Truck*); +}; + +// Helper function to create a string (caller must free) +char* createString(const char* format, ...) { + char* buffer = NULL; + va_list args; + int length; + + va_start(args, format); + length = vsnprintf(NULL, 0, format, args); + va_end(args); + + buffer = (char*)malloc(length + 1); + if (buffer == NULL) { + return NULL; + } + + va_start(args, format); + vsnprintf(buffer, length + 1, format, args); + va_end(args); + + return buffer; +} + +// Vehicle method implementations +char* vehicle_getInfo(const Vehicle* vehicle) { + return createString("%d %s %s", vehicle->year, vehicle->make, vehicle->model); +} + +char* vehicle_start(const Vehicle* vehicle) { + char* info = vehicle->getInfo(vehicle); + char* result = createString("%s is starting...", info); + free(info); + return result; +} + +char* vehicle_stop(const Vehicle* vehicle) { + char* info = vehicle->getInfo(vehicle); + char* result = createString("%s is stopping...", info); + free(info); + return result; +} + +// Car method implementations +char* car_getInfo(const Car* car) { + char* baseInfo = vehicle_getInfo((Vehicle*)car); + char* result = createString("%s (%d-door car)", baseInfo, car->doors); + free(baseInfo); + return result; +} + +char* car_drive(const Car* car) { + char* info = car_getInfo(car); + char* result = createString("%s is driving on the road.", info); + free(info); + return result; +} + +// Motorcycle method implementations +char* motorcycle_getInfo(const Motorcycle* motorcycle) { + char* baseInfo = vehicle_getInfo((Vehicle*)motorcycle); + char* result = createString("%s (%dcc motorcycle)", baseInfo, motorcycle->engineSize); + free(baseInfo); + return result; +} + +char* motorcycle_ride(const Motorcycle* motorcycle) { + char* info = motorcycle_getInfo(motorcycle); + char* result = createString("%s is riding at high speed.", info); + free(info); + return result; +} + +// Truck method implementations +char* truck_getInfo(const Truck* truck) { + char* baseInfo = vehicle_getInfo((Vehicle*)truck); + char* result = createString("%s (%.1f ton truck)", baseInfo, truck->capacity); + free(baseInfo); + return result; +} + +char* truck_haul(const Truck* truck) { + char* info = truck_getInfo(truck); + char* result = createString("%s is hauling cargo.", info); + free(info); + return result; +} + +// Factory functions to create vehicles +Car* createCar(const char* make, const char* model, int year, int doors) { + Car* car = (Car*)malloc(sizeof(Car)); + if (car == NULL) { + return NULL; + } + + // Initialize base vehicle + strncpy(car->base.make, make, sizeof(car->base.make) - 1); + strncpy(car->base.model, model, sizeof(car->base.model) - 1); + car->base.year = year; + + // Set function pointers for virtual methods + car->base.getInfo = (GetInfoFunc)car_getInfo; + car->base.start = (StartFunc)vehicle_start; + car->base.stop = (StopFunc)vehicle_stop; + + // Set car-specific properties and methods + car->doors = doors; + car->drive = car_drive; + + return car; +} + +Motorcycle* createMotorcycle(const char* make, const char* model, int year, int engineSize) { + Motorcycle* motorcycle = (Motorcycle*)malloc(sizeof(Motorcycle)); + if (motorcycle == NULL) { + return NULL; + } + + // Initialize base vehicle + strncpy(motorcycle->base.make, make, sizeof(motorcycle->base.make) - 1); + strncpy(motorcycle->base.model, model, sizeof(motorcycle->base.model) - 1); + motorcycle->base.year = year; + + // Set function pointers for virtual methods + motorcycle->base.getInfo = (GetInfoFunc)motorcycle_getInfo; + motorcycle->base.start = (StartFunc)vehicle_start; + motorcycle->base.stop = (StopFunc)vehicle_stop; + + // Set motorcycle-specific properties and methods + motorcycle->engineSize = engineSize; + motorcycle->ride = motorcycle_ride; + + return motorcycle; +} + +Truck* createTruck(const char* make, const char* model, int year, double capacity) { + Truck* truck = (Truck*)malloc(sizeof(Truck)); + if (truck == NULL) { + return NULL; + } + + // Initialize base vehicle + strncpy(truck->base.make, make, sizeof(truck->base.make) - 1); + strncpy(truck->base.model, model, sizeof(truck->base.model) - 1); + truck->base.year = year; + + // Set function pointers for virtual methods + truck->base.getInfo = (GetInfoFunc)truck_getInfo; + truck->base.start = (StartFunc)vehicle_start; + truck->base.stop = (StopFunc)vehicle_stop; + + // Set truck-specific properties and methods + truck->capacity = capacity; + truck->haul = truck_haul; + + return truck; +} + +// Simple Factory +void* vehicleFactory_createVehicle(VehicleType type, const char* make, const char* model, int year, void* options) { + switch (type) { + case VEHICLE_TYPE_CAR: { + int doors = options ? *(int*)options : 4; + return createCar(make, model, year, doors); + } + case VEHICLE_TYPE_MOTORCYCLE: { + int engineSize = options ? *(int*)options : 250; + return createMotorcycle(make, model, year, engineSize); + } + case VEHICLE_TYPE_TRUCK: { + double capacity = options ? *(double*)options : 5.0; + return createTruck(make, model, year, capacity); + } + default: + return NULL; + } +} + +// Factory Method Pattern Implementation +// In C, we would typically use function pointers to implement this pattern +typedef void* (*CreateVehicleFunc)(const char* make, const char* model, int year, void* options); +typedef void* (*RegisterVehicleFunc)(CreateVehicleFunc createFunc, const char* make, const char* model, int year, void* options); + +// Common register vehicle implementation +void* registerVehicle(CreateVehicleFunc createFunc, const char* make, const char* model, int year, void* options) { + void* vehicle = createFunc(make, model, year, options); + + // Common operations for all vehicles + Vehicle* baseVehicle = (Vehicle*)vehicle; + char* info = baseVehicle->getInfo(baseVehicle); + printf("Registering %s\n", info); + printf("Assigning license plate\n"); + free(info); + + return vehicle; +} + +// Concrete factory functions +void* createCarFactory(const char* make, const char* model, int year, void* options) { + int doors = options ? *(int*)options : 4; + return createCar(make, model, year, doors); +} + +void* createMotorcycleFactory(const char* make, const char* model, int year, void* options) { + int engineSize = options ? *(int*)options : 250; + return createMotorcycle(make, model, year, engineSize); +} + +void* createTruckFactory(const char* make, const char* model, int year, void* options) { + double capacity = options ? *(double*)options : 5.0; + return createTruck(make, model, year, capacity); +} + +// Abstract Factory Pattern Implementation +// Parts +typedef struct { + char type[50]; + int horsepower; +} Engine; + +typedef struct { + char type[50]; + int gears; +} Transmission; + +typedef struct { + char material[50]; + double weight; +} Chassis; + +// Function to get engine specs +char* engine_getSpecs(const Engine* engine) { + return createString("%s engine with %dhp", engine->type, engine->horsepower); +} + +// Function to get transmission specs +char* transmission_getSpecs(const Transmission* transmission) { + return createString("%s transmission with %d gears", transmission->type, transmission->gears); +} + +// Function to get chassis specs +char* chassis_getSpecs(const Chassis* chassis) { + return createString("%s chassis weighing %.1fkg", chassis->material, chassis->weight); +} + +// Abstract Factory interface +typedef struct { + Engine* (*createEngine)(); + Transmission* (*createTransmission)(); + Chassis* (*createChassis)(); +} VehiclePartsFactory; + +// Concrete Abstract Factories +Engine* sportVehiclePartsFactory_createEngine() { + Engine* engine = (Engine*)malloc(sizeof(Engine)); + strncpy(engine->type, "V8", sizeof(engine->type) - 1); + engine->horsepower = 450; + return engine; +} + +Transmission* sportVehiclePartsFactory_createTransmission() { + Transmission* transmission = (Transmission*)malloc(sizeof(Transmission)); + strncpy(transmission->type, "Manual", sizeof(transmission->type) - 1); + transmission->gears = 6; + return transmission; +} + +Chassis* sportVehiclePartsFactory_createChassis() { + Chassis* chassis = (Chassis*)malloc(sizeof(Chassis)); + strncpy(chassis->material, "Carbon Fiber", sizeof(chassis->material) - 1); + chassis->weight = 120.0; + return chassis; +} + +Engine* economyVehiclePartsFactory_createEngine() { + Engine* engine = (Engine*)malloc(sizeof(Engine)); + strncpy(engine->type, "Inline-4", sizeof(engine->type) - 1); + engine->horsepower = 180; + return engine; +} + +Transmission* economyVehiclePartsFactory_createTransmission() { + Transmission* transmission = (Transmission*)malloc(sizeof(Transmission)); + strncpy(transmission->type, "Automatic", sizeof(transmission->type) - 1); + transmission->gears = 5; + return transmission; +} + +Chassis* economyVehiclePartsFactory_createChassis() { + Chassis* chassis = (Chassis*)malloc(sizeof(Chassis)); + strncpy(chassis->material, "Steel", sizeof(chassis->material) - 1); + chassis->weight = 300.0; + return chassis; +} + +Engine* heavyDutyVehiclePartsFactory_createEngine() { + Engine* engine = (Engine*)malloc(sizeof(Engine)); + strncpy(engine->type, "Diesel V6", sizeof(engine->type) - 1); + engine->horsepower = 350; + return engine; +} + +Transmission* heavyDutyVehiclePartsFactory_createTransmission() { + Transmission* transmission = (Transmission*)malloc(sizeof(Transmission)); + strncpy(transmission->type, "Manual", sizeof(transmission->type) - 1); + transmission->gears = 8; + return transmission; +} + +Chassis* heavyDutyVehiclePartsFactory_createChassis() { + Chassis* chassis = (Chassis*)malloc(sizeof(Chassis)); + strncpy(chassis->material, "Reinforced Steel", sizeof(chassis->material) - 1); + chassis->weight = 800.0; + return chassis; +} + +// Create concrete factories +VehiclePartsFactory createSportVehiclePartsFactory() { + VehiclePartsFactory factory = { + .createEngine = sportVehiclePartsFactory_createEngine, + .createTransmission = sportVehiclePartsFactory_createTransmission, + .createChassis = sportVehiclePartsFactory_createChassis + }; + return factory; +} + +VehiclePartsFactory createEconomyVehiclePartsFactory() { + VehiclePartsFactory factory = { + .createEngine = economyVehiclePartsFactory_createEngine, + .createTransmission = economyVehiclePartsFactory_createTransmission, + .createChassis = economyVehiclePartsFactory_createChassis + }; + return factory; +} + +VehiclePartsFactory createHeavyDutyVehiclePartsFactory() { + VehiclePartsFactory factory = { + .createEngine = heavyDutyVehiclePartsFactory_createEngine, + .createTransmission = heavyDutyVehiclePartsFactory_createTransmission, + .createChassis = heavyDutyVehiclePartsFactory_createChassis + }; + return factory; +} + +// Vehicle Assembler - Uses the Abstract Factory +void assembleVehicle(const VehiclePartsFactory* factory) { + Engine* engine = factory->createEngine(); + Transmission* transmission = factory->createTransmission(); + Chassis* chassis = factory->createChassis(); + + char* engineSpecs = engine_getSpecs(engine); + char* transmissionSpecs = transmission_getSpecs(transmission); + char* chassisSpecs = chassis_getSpecs(chassis); + + printf("Assembling vehicle with:\n"); + printf("- %s\n", engineSpecs); + printf("- %s\n", transmissionSpecs); + printf("- %s\n", chassisSpecs); + + free(engineSpecs); + free(transmissionSpecs); + free(chassisSpecs); + free(engine); + free(transmission); + free(chassis); +} + +// Free memory functions +void freeCar(Car* car) { + if (car) { + free(car); + } +} + +void freeMotorcycle(Motorcycle* motorcycle) { + if (motorcycle) { + free(motorcycle); + } +} + +void freeTruck(Truck* truck) { + if (truck) { + free(truck); + } +} + +// Client code demonstration +void clientCode() { + printf("===== Simple Factory Pattern =====\n"); + + int carDoors = 4; + int motorcycleEngineSize = 600; + double truckCapacity = 3.0; + + Car* car = vehicleFactory_createVehicle(VEHICLE_TYPE_CAR, "Toyota", "Camry", 2023, &carDoors); + Motorcycle* motorcycle = vehicleFactory_createVehicle(VEHICLE_TYPE_MOTORCYCLE, "Honda", "CBR", 2023, &motorcycleEngineSize); + Truck* truck = vehicleFactory_createVehicle(VEHICLE_TYPE_TRUCK, "Ford", "F-150", 2023, &truckCapacity); + + char* carInfo = car->base.getInfo(car); + char* carDriveResult = car->drive(car); + + char* motorcycleInfo = motorcycle->base.getInfo(motorcycle); + char* motorcycleRideResult = motorcycle->ride(motorcycle); + + char* truckInfo = truck->base.getInfo(truck); + char* truckHaulResult = truck->haul(truck); + + printf("%s\n", carInfo); + printf("%s\n", carDriveResult); + + printf("%s\n", motorcycleInfo); + printf("%s\n", motorcycleRideResult); + + printf("%s\n", truckInfo); + printf("%s\n", truckHaulResult); + + free(carInfo); + free(carDriveResult); + free(motorcycleInfo); + free(motorcycleRideResult); + free(truckInfo); + free(truckHaulResult); + + printf("\n===== Factory Method Pattern =====\n"); + + int bmwDoors = 2; + int ducatiEngineSize = 821; + double volvoCapacity = 20.0; + + Car* newCar = registerVehicle(createCarFactory, "BMW", "3 Series", 2023, &bmwDoors); + Motorcycle* newMotorcycle = registerVehicle(createMotorcycleFactory, "Ducati", "Monster", 2023, &ducatiEngineSize); + Truck* newTruck = registerVehicle(createTruckFactory, "Volvo", "VNL", 2023, &volvoCapacity); + + char* newCarDriveResult = newCar->drive(newCar); + char* newMotorcycleRideResult = newMotorcycle->ride(newMotorcycle); + char* newTruckHaulResult = newTruck->haul(newTruck); + + printf("%s\n", newCarDriveResult); + printf("%s\n", newMotorcycleRideResult); + printf("%s\n", newTruckHaulResult); + + free(newCarDriveResult); + free(newMotorcycleRideResult); + free(newTruckHaulResult); + + printf("\n===== Abstract Factory Pattern =====\n"); + + VehiclePartsFactory sportFactory = createSportVehiclePartsFactory(); + VehiclePartsFactory economyFactory = createEconomyVehiclePartsFactory(); + VehiclePartsFactory heavyDutyFactory = createHeavyDutyVehiclePartsFactory(); + + printf("Building a sports car:\n"); + assembleVehicle(&sportFactory); + + printf("\nBuilding an economy car:\n"); + assembleVehicle(&economyFactory); + + printf("\nBuilding a heavy duty truck:\n"); + assembleVehicle(&heavyDutyFactory); + + // Free memory + freeCar(car); + freeMotorcycle(motorcycle); + freeTruck(truck); + freeCar(newCar); + freeMotorcycle(newMotorcycle); + freeTruck(newTruck); +} + +int main() { + // Run the example + clientCode(); + return 0; +} diff --git a/snippets/design-patterns/factory/factory_pattern.go b/snippets/design-patterns/factory/factory_pattern.go new file mode 100644 index 0000000..26ec4f5 --- /dev/null +++ b/snippets/design-patterns/factory/factory_pattern.go @@ -0,0 +1,396 @@ +/** + * Factory Pattern Implementation in Go + * + * The Factory Pattern is a creational design pattern that provides an interface for creating + * objects in a superclass, but allows subclasses to alter the type of objects that will be created. + * + * This example demonstrates a Vehicle Factory that can create different types of vehicles + * (Car, Motorcycle, Truck) based on the client's requirements. + */ + +package main + +import ( + "fmt" + "strconv" +) + +// Vehicle is the abstract product interface +type Vehicle interface { + GetInfo() string + Start() string + Stop() string +} + +// Car is a concrete product +type Car struct { + Make string + Model string + Year int + Doors int +} + +func NewCar(make, model string, year, doors int) *Car { + return &Car{ + Make: make, + Model: model, + Year: year, + Doors: doors, + } +} + +func (c *Car) GetInfo() string { + return strconv.Itoa(c.Year) + " " + c.Make + " " + c.Model + " (" + strconv.Itoa(c.Doors) + "-door car)" +} + +func (c *Car) Start() string { + return c.GetInfo() + " is starting..." +} + +func (c *Car) Stop() string { + return c.GetInfo() + " is stopping..." +} + +func (c *Car) Drive() string { + return c.GetInfo() + " is driving on the road." +} + +// Motorcycle is a concrete product +type Motorcycle struct { + Make string + Model string + Year int + EngineSize int +} + +func NewMotorcycle(make, model string, year, engineSize int) *Motorcycle { + return &Motorcycle{ + Make: make, + Model: model, + Year: year, + EngineSize: engineSize, + } +} + +func (m *Motorcycle) GetInfo() string { + return strconv.Itoa(m.Year) + " " + m.Make + " " + m.Model + " (" + strconv.Itoa(m.EngineSize) + "cc motorcycle)" +} + +func (m *Motorcycle) Start() string { + return m.GetInfo() + " is starting..." +} + +func (m *Motorcycle) Stop() string { + return m.GetInfo() + " is stopping..." +} + +func (m *Motorcycle) Ride() string { + return m.GetInfo() + " is riding at high speed." +} + +// Truck is a concrete product +type Truck struct { + Make string + Model string + Year int + Capacity float64 +} + +func NewTruck(make, model string, year int, capacity float64) *Truck { + return &Truck{ + Make: make, + Model: model, + Year: year, + Capacity: capacity, + } +} + +func (t *Truck) GetInfo() string { + return strconv.Itoa(t.Year) + " " + t.Make + " " + t.Model + " (" + fmt.Sprintf("%.1f", t.Capacity) + " ton truck)" +} + +func (t *Truck) Start() string { + return t.GetInfo() + " is starting..." +} + +func (t *Truck) Stop() string { + return t.GetInfo() + " is stopping..." +} + +func (t *Truck) Haul() string { + return t.GetInfo() + " is hauling cargo." +} + +// VehicleType defines the type of vehicle to create +type VehicleType string + +const ( + CarType VehicleType = "car" + MotorcycleType VehicleType = "motorcycle" + TruckType VehicleType = "truck" +) + +// SimpleVehicleFactory is a simple factory implementation +type SimpleVehicleFactory struct{} + +// CreateVehicle creates a vehicle based on the given type +func (f *SimpleVehicleFactory) CreateVehicle(vehicleType VehicleType, make, model string, year int, options map[string]interface{}) (Vehicle, error) { + switch vehicleType { + case CarType: + doors := 4 // default value + if val, ok := options["doors"]; ok { + if doorsVal, ok := val.(int); ok { + doors = doorsVal + } + } + return NewCar(make, model, year, doors), nil + + case MotorcycleType: + engineSize := 250 // default value + if val, ok := options["engineSize"]; ok { + if engineSizeVal, ok := val.(int); ok { + engineSize = engineSizeVal + } + } + return NewMotorcycle(make, model, year, engineSize), nil + + case TruckType: + capacity := 5.0 // default value + if val, ok := options["capacity"]; ok { + if capacityVal, ok := val.(float64); ok { + capacity = capacityVal + } + } + return NewTruck(make, model, year, capacity), nil + + default: + return nil, fmt.Errorf("unknown vehicle type: %s", vehicleType) + } +} + +// VehicleFactoryMethod is the factory method interface +type VehicleFactoryMethod interface { + CreateVehicle(make, model string, year int, options map[string]interface{}) Vehicle + RegisterVehicle(make, model string, year int, options map[string]interface{}) Vehicle +} + +// CarFactory is a concrete factory implementation +type CarFactory struct{} + +func (f *CarFactory) CreateVehicle(make, model string, year int, options map[string]interface{}) Vehicle { + doors := 4 // default value + if val, ok := options["doors"]; ok { + if doorsVal, ok := val.(int); ok { + doors = doorsVal + } + } + return NewCar(make, model, year, doors) +} + +func (f *CarFactory) RegisterVehicle(make, model string, year int, options map[string]interface{}) Vehicle { + vehicle := f.CreateVehicle(make, model, year, options) + fmt.Printf("Registering %s\n", vehicle.GetInfo()) + fmt.Println("Assigning license plate to car") + return vehicle +} + +// MotorcycleFactory is a concrete factory implementation +type MotorcycleFactory struct{} + +func (f *MotorcycleFactory) CreateVehicle(make, model string, year int, options map[string]interface{}) Vehicle { + engineSize := 250 // default value + if val, ok := options["engineSize"]; ok { + if engineSizeVal, ok := val.(int); ok { + engineSize = engineSizeVal + } + } + return NewMotorcycle(make, model, year, engineSize) +} + +func (f *MotorcycleFactory) RegisterVehicle(make, model string, year int, options map[string]interface{}) Vehicle { + vehicle := f.CreateVehicle(make, model, year, options) + fmt.Printf("Registering %s\n", vehicle.GetInfo()) + fmt.Println("Assigning license plate to motorcycle") + return vehicle +} + +// TruckFactory is a concrete factory implementation +type TruckFactory struct{} + +func (f *TruckFactory) CreateVehicle(make, model string, year int, options map[string]interface{}) Vehicle { + capacity := 5.0 // default value + if val, ok := options["capacity"]; ok { + if capacityVal, ok := val.(float64); ok { + capacity = capacityVal + } + } + return NewTruck(make, model, year, capacity) +} + +func (f *TruckFactory) RegisterVehicle(make, model string, year int, options map[string]interface{}) Vehicle { + vehicle := f.CreateVehicle(make, model, year, options) + fmt.Printf("Registering %s\n", vehicle.GetInfo()) + fmt.Println("Assigning license plate to truck") + return vehicle +} + +// Abstract Factory Pattern Implementation + +// Engine represents a vehicle engine +type Engine struct { + Type string + Horsepower int +} + +func (e *Engine) GetSpecs() string { + return e.Type + " engine with " + strconv.Itoa(e.Horsepower) + "hp" +} + +// Transmission represents a vehicle transmission +type Transmission struct { + Type string + Gears int +} + +func (t *Transmission) GetSpecs() string { + return t.Type + " transmission with " + strconv.Itoa(t.Gears) + " gears" +} + +// Chassis represents a vehicle chassis +type Chassis struct { + Material string + Weight float64 +} + +func (c *Chassis) GetSpecs() string { + return c.Material + " chassis weighing " + fmt.Sprintf("%.1f", c.Weight) + "kg" +} + +// VehiclePartsFactory is the abstract factory interface +type VehiclePartsFactory interface { + CreateEngine() *Engine + CreateTransmission() *Transmission + CreateChassis() *Chassis +} + +// SportVehiclePartsFactory creates parts for sport vehicles +type SportVehiclePartsFactory struct{} + +func (f *SportVehiclePartsFactory) CreateEngine() *Engine { + return &Engine{Type: "V8", Horsepower: 450} +} + +func (f *SportVehiclePartsFactory) CreateTransmission() *Transmission { + return &Transmission{Type: "Manual", Gears: 6} +} + +func (f *SportVehiclePartsFactory) CreateChassis() *Chassis { + return &Chassis{Material: "Carbon Fiber", Weight: 120.0} +} + +// EconomyVehiclePartsFactory creates parts for economy vehicles +type EconomyVehiclePartsFactory struct{} + +func (f *EconomyVehiclePartsFactory) CreateEngine() *Engine { + return &Engine{Type: "Inline-4", Horsepower: 180} +} + +func (f *EconomyVehiclePartsFactory) CreateTransmission() *Transmission { + return &Transmission{Type: "Automatic", Gears: 5} +} + +func (f *EconomyVehiclePartsFactory) CreateChassis() *Chassis { + return &Chassis{Material: "Steel", Weight: 300.0} +} + +// HeavyDutyVehiclePartsFactory creates parts for heavy duty vehicles +type HeavyDutyVehiclePartsFactory struct{} + +func (f *HeavyDutyVehiclePartsFactory) CreateEngine() *Engine { + return &Engine{Type: "Diesel V6", Horsepower: 350} +} + +func (f *HeavyDutyVehiclePartsFactory) CreateTransmission() *Transmission { + return &Transmission{Type: "Manual", Gears: 8} +} + +func (f *HeavyDutyVehiclePartsFactory) CreateChassis() *Chassis { + return &Chassis{Material: "Reinforced Steel", Weight: 800.0} +} + +// VehicleAssembler uses the abstract factory to assemble vehicles +type VehicleAssembler struct { + PartsFactory VehiclePartsFactory +} + +func NewVehicleAssembler(factory VehiclePartsFactory) *VehicleAssembler { + return &VehicleAssembler{PartsFactory: factory} +} + +func (a *VehicleAssembler) AssembleVehicle() { + engine := a.PartsFactory.CreateEngine() + transmission := a.PartsFactory.CreateTransmission() + chassis := a.PartsFactory.CreateChassis() + + fmt.Println("Assembling vehicle with:") + fmt.Println("- " + engine.GetSpecs()) + fmt.Println("- " + transmission.GetSpecs()) + fmt.Println("- " + chassis.GetSpecs()) +} + +// Example client code +func clientCode() { + fmt.Println("===== Simple Factory Pattern =====") + + factory := &SimpleVehicleFactory{} + + car, _ := factory.CreateVehicle(CarType, "Toyota", "Camry", 2023, map[string]interface{}{"doors": 4}) + motorcycle, _ := factory.CreateVehicle(MotorcycleType, "Honda", "CBR", 2023, map[string]interface{}{"engineSize": 600}) + truck, _ := factory.CreateVehicle(TruckType, "Ford", "F-150", 2023, map[string]interface{}{"capacity": 3.0}) + + fmt.Println(car.GetInfo()) + fmt.Println(car.(*Car).Drive()) + + fmt.Println(motorcycle.GetInfo()) + fmt.Println(motorcycle.(*Motorcycle).Ride()) + + fmt.Println(truck.GetInfo()) + fmt.Println(truck.(*Truck).Haul()) + + fmt.Println("\n===== Factory Method Pattern =====") + + carFactory := &CarFactory{} + motorcycleFactory := &MotorcycleFactory{} + truckFactory := &TruckFactory{} + + newCar := carFactory.RegisterVehicle("BMW", "3 Series", 2023, map[string]interface{}{"doors": 2}) + newMotorcycle := motorcycleFactory.RegisterVehicle("Ducati", "Monster", 2023, map[string]interface{}{"engineSize": 821}) + newTruck := truckFactory.RegisterVehicle("Volvo", "VNL", 2023, map[string]interface{}{"capacity": 20.0}) + + fmt.Println(newCar.(*Car).Drive()) + fmt.Println(newMotorcycle.(*Motorcycle).Ride()) + fmt.Println(newTruck.(*Truck).Haul()) + + fmt.Println("\n===== Abstract Factory Pattern =====") + + sportFactory := &SportVehiclePartsFactory{} + economyFactory := &EconomyVehiclePartsFactory{} + heavyDutyFactory := &HeavyDutyVehiclePartsFactory{} + + fmt.Println("Building a sports car:") + sportAssembler := NewVehicleAssembler(sportFactory) + sportAssembler.AssembleVehicle() + + fmt.Println("\nBuilding an economy car:") + economyAssembler := NewVehicleAssembler(economyFactory) + economyAssembler.AssembleVehicle() + + fmt.Println("\nBuilding a heavy duty truck:") + heavyDutyAssembler := NewVehicleAssembler(heavyDutyFactory) + heavyDutyAssembler.AssembleVehicle() +} + +func main() { + // Run the example + clientCode() +} diff --git a/snippets/design-patterns/factory/factory_pattern.js b/snippets/design-patterns/factory/factory_pattern.js new file mode 100644 index 0000000..b345a1d --- /dev/null +++ b/snippets/design-patterns/factory/factory_pattern.js @@ -0,0 +1,328 @@ +/** + * Factory Pattern Implementation in JavaScript + * + * The Factory Pattern is a creational design pattern that provides an interface for creating + * objects in a superclass, but allows subclasses to alter the type of objects that will be created. + * + * This example demonstrates a Vehicle Factory that can create different types of vehicles + * (Car, Motorcycle, Truck) based on the client's requirements. + */ + +// Abstract Product - Vehicle +class Vehicle { + constructor(make, model, year) { + this.make = make; + this.model = model; + this.year = year; + } + + getInfo() { + return `${this.year} ${this.make} ${this.model}`; + } + + start() { + return `${this.getInfo()} is starting...`; + } + + stop() { + return `${this.getInfo()} is stopping...`; + } +} + +// Concrete Products +class Car extends Vehicle { + constructor(make, model, year, doors = 4) { + super(make, model, year); + this.doors = doors; + this.type = 'Car'; + } + + getInfo() { + return `${super.getInfo()} (${this.doors}-door car)`; + } + + drive() { + return `${this.getInfo()} is driving on the road.`; + } +} + +class Motorcycle extends Vehicle { + constructor(make, model, year, engineSize) { + super(make, model, year); + this.engineSize = engineSize; + this.type = 'Motorcycle'; + } + + getInfo() { + return `${super.getInfo()} (${this.engineSize}cc motorcycle)`; + } + + ride() { + return `${this.getInfo()} is riding at high speed.`; + } +} + +class Truck extends Vehicle { + constructor(make, model, year, capacity) { + super(make, model, year); + this.capacity = capacity; + this.type = 'Truck'; + } + + getInfo() { + return `${super.getInfo()} (${this.capacity} ton truck)`; + } + + haul() { + return `${this.getInfo()} is hauling cargo.`; + } +} + +// Simple Factory +class VehicleFactory { + static createVehicle(type, make, model, year, options = {}) { + switch (type.toLowerCase()) { + case 'car': + return new Car(make, model, year, options.doors); + case 'motorcycle': + return new Motorcycle(make, model, year, options.engineSize); + case 'truck': + return new Truck(make, model, year, options.capacity); + default: + throw new Error(`Vehicle type ${type} is not supported.`); + } + } +} + +// Factory Method Pattern Implementation +class VehicleFactoryMethod { + createVehicle(make, model, year, options = {}) { + // This method will be implemented by concrete factories + throw new Error('createVehicle method must be implemented by subclasses'); + } + + registerVehicle(make, model, year, options = {}) { + // Common operations for all vehicles + const vehicle = this.createVehicle(make, model, year, options); + console.log(`Registering ${vehicle.getInfo()}`); + console.log(`Assigning license plate to ${vehicle.type}`); + return vehicle; + } +} + +// Concrete Factories +class CarFactory extends VehicleFactoryMethod { + createVehicle(make, model, year, options = {}) { + return new Car(make, model, year, options.doors || 4); + } +} + +class MotorcycleFactory extends VehicleFactoryMethod { + createVehicle(make, model, year, options = {}) { + return new Motorcycle(make, model, year, options.engineSize || 250); + } +} + +class TruckFactory extends VehicleFactoryMethod { + createVehicle(make, model, year, options = {}) { + return new Truck(make, model, year, options.capacity || 5); + } +} + +// Abstract Factory Pattern Implementation +class VehiclePartsFactory { + createEngine() { + throw new Error('Method not implemented'); + } + + createTransmission() { + throw new Error('Method not implemented'); + } + + createChassis() { + throw new Error('Method not implemented'); + } +} + +// Parts +class Engine { + constructor(type, horsepower) { + this.type = type; + this.horsepower = horsepower; + } + + getSpecs() { + return `${this.type} engine with ${this.horsepower}hp`; + } +} + +class Transmission { + constructor(type, gears) { + this.type = type; + this.gears = gears; + } + + getSpecs() { + return `${this.type} transmission with ${this.gears} gears`; + } +} + +class Chassis { + constructor(material, weight) { + this.material = material; + this.weight = weight; + } + + getSpecs() { + return `${this.material} chassis weighing ${this.weight}kg`; + } +} + +// Concrete Abstract Factories +class SportVehiclePartsFactory extends VehiclePartsFactory { + createEngine() { + return new Engine('V8', 450); + } + + createTransmission() { + return new Transmission('Manual', 6); + } + + createChassis() { + return new Chassis('Carbon Fiber', 120); + } +} + +class EconomyVehiclePartsFactory extends VehiclePartsFactory { + createEngine() { + return new Engine('Inline-4', 180); + } + + createTransmission() { + return new Transmission('Automatic', 5); + } + + createChassis() { + return new Chassis('Steel', 300); + } +} + +class HeavyDutyVehiclePartsFactory extends VehiclePartsFactory { + createEngine() { + return new Engine('Diesel V6', 350); + } + + createTransmission() { + return new Transmission('Manual', 8); + } + + createChassis() { + return new Chassis('Reinforced Steel', 800); + } +} + +// Vehicle Assembler - Uses the Abstract Factory +class VehicleAssembler { + constructor(partsFactory) { + this.partsFactory = partsFactory; + } + + assembleVehicle() { + const engine = this.partsFactory.createEngine(); + const transmission = this.partsFactory.createTransmission(); + const chassis = this.partsFactory.createChassis(); + + console.log('Assembling vehicle with:'); + console.log(`- ${engine.getSpecs()}`); + console.log(`- ${transmission.getSpecs()}`); + console.log(`- ${chassis.getSpecs()}`); + + return { + engine, + transmission, + chassis + }; + } +} + +// Client code demonstration +function clientCode() { + console.log('===== Simple Factory Pattern ====='); + + const car = VehicleFactory.createVehicle('car', 'Toyota', 'Camry', 2023, { doors: 4 }); + const motorcycle = VehicleFactory.createVehicle('motorcycle', 'Honda', 'CBR', 2023, { engineSize: 600 }); + const truck = VehicleFactory.createVehicle('truck', 'Ford', 'F-150', 2023, { capacity: 3 }); + + console.log(car.getInfo()); + console.log(car.drive()); + + console.log(motorcycle.getInfo()); + console.log(motorcycle.ride()); + + console.log(truck.getInfo()); + console.log(truck.haul()); + + console.log('\n===== Factory Method Pattern ====='); + + const carFactory = new CarFactory(); + const motorcycleFactory = new MotorcycleFactory(); + const truckFactory = new TruckFactory(); + + const newCar = carFactory.registerVehicle('BMW', '3 Series', 2023, { doors: 2 }); + const newMotorcycle = motorcycleFactory.registerVehicle('Ducati', 'Monster', 2023, { engineSize: 821 }); + const newTruck = truckFactory.registerVehicle('Volvo', 'VNL', 2023, { capacity: 20 }); + + console.log(newCar.drive()); + console.log(newMotorcycle.ride()); + console.log(newTruck.haul()); + + console.log('\n===== Abstract Factory Pattern ====='); + + const sportPartsFactory = new SportVehiclePartsFactory(); + const economyPartsFactory = new EconomyVehiclePartsFactory(); + const heavyDutyPartsFactory = new HeavyDutyVehiclePartsFactory(); + + console.log('Building a sports car:'); + const sportCarAssembler = new VehicleAssembler(sportPartsFactory); + const sportCarParts = sportCarAssembler.assembleVehicle(); + + console.log('\nBuilding an economy car:'); + const economyCarAssembler = new VehicleAssembler(economyPartsFactory); + const economyCarParts = economyCarAssembler.assembleVehicle(); + + console.log('\nBuilding a heavy duty truck:'); + const heavyDutyTruckAssembler = new VehicleAssembler(heavyDutyPartsFactory); + const heavyDutyTruckParts = heavyDutyTruckAssembler.assembleVehicle(); +} + +// Execute the client code +// Run the example +clientCode(); + +// Export classes for use in other modules +module.exports = { + // Simple Factory + VehicleFactory, + + // Factory Method + VehicleFactoryMethod, + CarFactory, + MotorcycleFactory, + TruckFactory, + + // Abstract Factory + VehiclePartsFactory, + SportVehiclePartsFactory, + EconomyVehiclePartsFactory, + HeavyDutyVehiclePartsFactory, + VehicleAssembler, + + // Products + Vehicle, + Car, + Motorcycle, + Truck, + Engine, + Transmission, + Chassis +}; diff --git a/snippets/design-patterns/factory/factory_pattern.php b/snippets/design-patterns/factory/factory_pattern.php new file mode 100644 index 0000000..1c1f422 --- /dev/null +++ b/snippets/design-patterns/factory/factory_pattern.php @@ -0,0 +1,308 @@ +make = $make; + $this->model = $model; + $this->year = $year; + } + + public function getInfo() { + return "{$this->year} {$this->make} {$this->model}"; + } + + public function start() { + return $this->getInfo() . " is starting..."; + } + + public function stop() { + return $this->getInfo() . " is stopping..."; + } +} + +// Concrete Products +class Car extends Vehicle { + protected $doors; + + public function __construct($make, $model, $year, $doors = 4) { + parent::__construct($make, $model, $year); + $this->doors = $doors; + } + + public function getInfo() { + return parent::getInfo() . " ({$this->doors}-door car)"; + } + + public function drive() { + return $this->getInfo() . " is driving on the road."; + } +} + +class Motorcycle extends Vehicle { + protected $engineSize; + + public function __construct($make, $model, $year, $engineSize) { + parent::__construct($make, $model, $year); + $this->engineSize = $engineSize; + } + + public function getInfo() { + return parent::getInfo() . " ({$this->engineSize}cc motorcycle)"; + } + + public function ride() { + return $this->getInfo() . " is riding at high speed."; + } +} + +class Truck extends Vehicle { + protected $capacity; + + public function __construct($make, $model, $year, $capacity) { + parent::__construct($make, $model, $year); + $this->capacity = $capacity; + } + + public function getInfo() { + return parent::getInfo() . " ({$this->capacity} ton truck)"; + } + + public function haul() { + return $this->getInfo() . " is hauling cargo."; + } +} + +// Simple Factory +class VehicleFactory { + const CAR = 'car'; + const MOTORCYCLE = 'motorcycle'; + const TRUCK = 'truck'; + + public static function createVehicle($type, $make, $model, $year, $options = []) { + switch (strtolower($type)) { + case self::CAR: + $doors = isset($options['doors']) ? $options['doors'] : 4; + return new Car($make, $model, $year, $doors); + case self::MOTORCYCLE: + $engineSize = isset($options['engineSize']) ? $options['engineSize'] : 250; + return new Motorcycle($make, $model, $year, $engineSize); + case self::TRUCK: + $capacity = isset($options['capacity']) ? $options['capacity'] : 5.0; + return new Truck($make, $model, $year, $capacity); + default: + throw new Exception("Vehicle type {$type} is not supported."); + } + } +} + +// Factory Method Pattern Implementation +abstract class VehicleFactoryMethod { + abstract public function createVehicle($make, $model, $year, $options = []); + + public function registerVehicle($make, $model, $year, $options = []) { + // Common operations for all vehicles + $vehicle = $this->createVehicle($make, $model, $year, $options); + echo "Registering " . $vehicle->getInfo() . PHP_EOL; + echo "Assigning license plate" . PHP_EOL; + return $vehicle; + } +} + +// Concrete Factories +class CarFactory extends VehicleFactoryMethod { + public function createVehicle($make, $model, $year, $options = []) { + $doors = isset($options['doors']) ? $options['doors'] : 4; + return new Car($make, $model, $year, $doors); + } +} + +class MotorcycleFactory extends VehicleFactoryMethod { + public function createVehicle($make, $model, $year, $options = []) { + $engineSize = isset($options['engineSize']) ? $options['engineSize'] : 250; + return new Motorcycle($make, $model, $year, $engineSize); + } +} + +class TruckFactory extends VehicleFactoryMethod { + public function createVehicle($make, $model, $year, $options = []) { + $capacity = isset($options['capacity']) ? $options['capacity'] : 5.0; + return new Truck($make, $model, $year, $capacity); + } +} + +// Abstract Factory Pattern Implementation +// Parts +class Engine { + protected $type; + protected $horsepower; + + public function __construct($type, $horsepower) { + $this->type = $type; + $this->horsepower = $horsepower; + } + + public function getSpecs() { + return "{$this->type} engine with {$this->horsepower}hp"; + } +} + +class Transmission { + protected $type; + protected $gears; + + public function __construct($type, $gears) { + $this->type = $type; + $this->gears = $gears; + } + + public function getSpecs() { + return "{$this->type} transmission with {$this->gears} gears"; + } +} + +class Chassis { + protected $material; + protected $weight; + + public function __construct($material, $weight) { + $this->material = $material; + $this->weight = $weight; + } + + public function getSpecs() { + return "{$this->material} chassis weighing {$this->weight}kg"; + } +} + +// Abstract Factory +interface VehiclePartsFactory { + public function createEngine(); + public function createTransmission(); + public function createChassis(); +} + +// Concrete Abstract Factories +class SportVehiclePartsFactory implements VehiclePartsFactory { + public function createEngine() { + return new Engine("V8", 450); + } + + public function createTransmission() { + return new Transmission("Manual", 6); + } + + public function createChassis() { + return new Chassis("Carbon Fiber", 120); + } +} + +class EconomyVehiclePartsFactory implements VehiclePartsFactory { + public function createEngine() { + return new Engine("Inline-4", 180); + } + + public function createTransmission() { + return new Transmission("Automatic", 5); + } + + public function createChassis() { + return new Chassis("Steel", 300); + } +} + +class HeavyDutyVehiclePartsFactory implements VehiclePartsFactory { + public function createEngine() { + return new Engine("Diesel V6", 350); + } + + public function createTransmission() { + return new Transmission("Manual", 8); + } + + public function createChassis() { + return new Chassis("Reinforced Steel", 800); + } +} + +// Vehicle Assembler - Uses the Abstract Factory +class VehicleAssembler { + protected $partsFactory; + + public function __construct(VehiclePartsFactory $partsFactory) { + $this->partsFactory = $partsFactory; + } + + public function assembleVehicle() { + $engine = $this->partsFactory->createEngine(); + $transmission = $this->partsFactory->createTransmission(); + $chassis = $this->partsFactory->createChassis(); + + echo "Assembling vehicle with:" . PHP_EOL; + echo "- " . $engine->getSpecs() . PHP_EOL; + echo "- " . $transmission->getSpecs() . PHP_EOL; + echo "- " . $chassis->getSpecs() . PHP_EOL; + } +} + +// Client code demonstration +function clientCode() { + echo "===== Simple Factory Pattern =====" . PHP_EOL; + + $car = VehicleFactory::createVehicle(VehicleFactory::CAR, "Toyota", "Camry", 2023, ['doors' => 4]); + $motorcycle = VehicleFactory::createVehicle(VehicleFactory::MOTORCYCLE, "Honda", "CBR", 2023, ['engineSize' => 600]); + $truck = VehicleFactory::createVehicle(VehicleFactory::TRUCK, "Ford", "F-150", 2023, ['capacity' => 3.0]); + + echo $car->getInfo() . PHP_EOL; + echo $car->drive() . PHP_EOL; + + echo $motorcycle->getInfo() . PHP_EOL; + echo $motorcycle->ride() . PHP_EOL; + + echo $truck->getInfo() . PHP_EOL; + echo $truck->haul() . PHP_EOL; + + echo PHP_EOL . "===== Factory Method Pattern =====" . PHP_EOL; + + $carFactory = new CarFactory(); + $motorcycleFactory = new MotorcycleFactory(); + $truckFactory = new TruckFactory(); + + $newCar = $carFactory->registerVehicle("BMW", "3 Series", 2023, ['doors' => 2]); + $newMotorcycle = $motorcycleFactory->registerVehicle("Ducati", "Monster", 2023, ['engineSize' => 821]); + $newTruck = $truckFactory->registerVehicle("Volvo", "VNL", 2023, ['capacity' => 20.0]); + + echo $newCar->drive() . PHP_EOL; + echo $newMotorcycle->ride() . PHP_EOL; + echo $newTruck->haul() . PHP_EOL; + + echo PHP_EOL . "===== Abstract Factory Pattern =====" . PHP_EOL; + + echo "Building a sports car:" . PHP_EOL; + $sportCarAssembler = new VehicleAssembler(new SportVehiclePartsFactory()); + $sportCarAssembler->assembleVehicle(); + + echo PHP_EOL . "Building an economy car:" . PHP_EOL; + $economyCarAssembler = new VehicleAssembler(new EconomyVehiclePartsFactory()); + $economyCarAssembler->assembleVehicle(); + + echo PHP_EOL . "Building a heavy duty truck:" . PHP_EOL; + $heavyDutyTruckAssembler = new VehicleAssembler(new HeavyDutyVehiclePartsFactory()); + $heavyDutyTruckAssembler->assembleVehicle(); +} + +// Run the example +clientCode(); diff --git a/snippets/design-patterns/factory/factory_pattern.py b/snippets/design-patterns/factory/factory_pattern.py new file mode 100644 index 0000000..70b01e0 --- /dev/null +++ b/snippets/design-patterns/factory/factory_pattern.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +""" +Factory Pattern Implementation in Python + +This demonstrates three variations of the Factory Pattern: +1. Simple Factory +2. Factory Method +3. Abstract Factory +""" + +from abc import ABC, abstractmethod +import sys + +# ============= SIMPLE FACTORY EXAMPLE ============= + +class Product(ABC): + """Product interface""" + @abstractmethod + def operation(self): + pass + +class ConcreteProductA(Product): + """Concrete product A""" + def operation(self): + return "Result of ConcreteProductA operation" + +class ConcreteProductB(Product): + """Concrete product B""" + def operation(self): + return "Result of ConcreteProductB operation" + +class SimpleFactory: + """Simple Factory implementation""" + @staticmethod + def create_product(product_type): + """Creates a product based on type""" + if product_type == "A": + return ConcreteProductA() + elif product_type == "B": + return ConcreteProductB() + else: + raise ValueError(f"Product type {product_type} not recognized") + +# Client code for Simple Factory +def simple_factory_client(): + """Demo of Simple Factory Pattern""" + print("\n=== Simple Factory Example ===") + factory = SimpleFactory() + + # Create and use product A + product_a = factory.create_product("A") + print(product_a.operation()) + + # Create and use product B + product_b = factory.create_product("B") + print(product_b.operation()) + +# ============= FACTORY METHOD EXAMPLE ============= + +class Creator(ABC): + """Creator class with factory method""" + @abstractmethod + def factory_method(self): + """Factory method to be implemented by subclasses""" + pass + + def some_operation(self): + """Business logic that uses the factory method""" + # Call the factory method to create a Product object + product = self.factory_method() + + # Use the product + result = f"Creator: The same creator's code has just worked with {product.operation()}" + return result + +class ConcreteCreator1(Creator): + """Concrete creator that returns ConcreteProductA""" + def factory_method(self): + return ConcreteProductA() + +class ConcreteCreator2(Creator): + """Concrete creator that returns ConcreteProductB""" + def factory_method(self): + return ConcreteProductB() + +# Client code for Factory Method +def factory_method_client(): + """Demo of Factory Method Pattern""" + print("\n=== Factory Method Example ===") + + print("App: Launched with ConcreteCreator1") + client_code(ConcreteCreator1()) + + print("\nApp: Launched with ConcreteCreator2") + client_code(ConcreteCreator2()) + +def client_code(creator): + """Client code that works with any creator subclass""" + print(f"Client: I'm not aware of the creator's class, but it still works.\n" + f"{creator.some_operation()}") + +# ============= ABSTRACT FACTORY EXAMPLE ============= + +class AbstractProductA(ABC): + """Abstract product A interface""" + @abstractmethod + def useful_function_a(self): + pass + +class ConcreteProductA1(AbstractProductA): + """Concrete product A1 - Family 1""" + def useful_function_a(self): + return "The result of the product A1." + +class ConcreteProductA2(AbstractProductA): + """Concrete product A2 - Family 2""" + def useful_function_a(self): + return "The result of the product A2." + +class AbstractProductB(ABC): + """Abstract product B interface""" + @abstractmethod + def useful_function_b(self): + pass + + @abstractmethod + def another_useful_function_b(self, collaborator: AbstractProductA): + """ + B products can work with A products + """ + pass + +class ConcreteProductB1(AbstractProductB): + """Concrete product B1 - Family 1""" + def useful_function_b(self): + return "The result of the product B1." + + def another_useful_function_b(self, collaborator: AbstractProductA): + result = collaborator.useful_function_a() + return f"The result of B1 collaborating with ({result})" + +class ConcreteProductB2(AbstractProductB): + """Concrete product B2 - Family 2""" + def useful_function_b(self): + return "The result of the product B2." + + def another_useful_function_b(self, collaborator: AbstractProductA): + result = collaborator.useful_function_a() + return f"The result of B2 collaborating with ({result})" + +class AbstractFactory(ABC): + """Abstract Factory Interface""" + @abstractmethod + def create_product_a(self) -> AbstractProductA: + pass + + @abstractmethod + def create_product_b(self) -> AbstractProductB: + pass + +class ConcreteFactory1(AbstractFactory): + """Concrete Factory for Family 1 products""" + def create_product_a(self) -> AbstractProductA: + return ConcreteProductA1() + + def create_product_b(self) -> AbstractProductB: + return ConcreteProductB1() + +class ConcreteFactory2(AbstractFactory): + """Concrete Factory for Family 2 products""" + def create_product_a(self) -> AbstractProductA: + return ConcreteProductA2() + + def create_product_b(self) -> AbstractProductB: + return ConcreteProductB2() + +def abstract_factory_client(): + """Demo of Abstract Factory Pattern""" + print("\n=== Abstract Factory Example ===") + + print("Client: Testing client code with the first factory type:") + client_abstract_factory_code(ConcreteFactory1()) + + print("\nClient: Testing the same client code with the second factory type:") + client_abstract_factory_code(ConcreteFactory2()) + +def client_abstract_factory_code(factory: AbstractFactory): + """ + Client code that works with factories and products through abstract interfaces + """ + product_a = factory.create_product_a() + product_b = factory.create_product_b() + + print(f"{product_b.useful_function_b()}") + print(f"{product_b.another_useful_function_b(product_a)}") + + +# ============= REAL-WORLD EXAMPLE: UI COMPONENTS ============= + +# Abstract products +class Button(ABC): + @abstractmethod + def render(self): + pass + + @abstractmethod + def on_click(self): + pass + +class Checkbox(ABC): + @abstractmethod + def render(self): + pass + + @abstractmethod + def toggle(self): + pass + +# Concrete products for Windows +class WindowsButton(Button): + def render(self): + return "Rendering a Windows button" + + def on_click(self): + return "Windows button clicked!" + +class WindowsCheckbox(Checkbox): + def render(self): + return "Rendering a Windows checkbox" + + def toggle(self): + return "Windows checkbox toggled!" + +# Concrete products for Web +class WebButton(Button): + def render(self): + return "Rendering a button in HTML" + + def on_click(self): + return "JavaScript click event triggered!" + +class WebCheckbox(Checkbox): + def render(self): + return "Rendering a checkbox in HTML" + + def toggle(self): + return "JavaScript toggle event triggered!" + +# Abstract factory +class GUIFactory(ABC): + @abstractmethod + def create_button(self) -> Button: + pass + + @abstractmethod + def create_checkbox(self) -> Checkbox: + pass + +# Concrete factories +class WindowsFactory(GUIFactory): + def create_button(self) -> Button: + return WindowsButton() + + def create_checkbox(self) -> Checkbox: + return WindowsCheckbox() + +class WebFactory(GUIFactory): + def create_button(self) -> Button: + return WebButton() + + def create_checkbox(self) -> Checkbox: + return WebCheckbox() + +# Client code +class Application: + def __init__(self, factory: GUIFactory): + self._factory = factory + self._button = None + self._checkbox = None + + def create_ui(self): + self._button = self._factory.create_button() + self._checkbox = self._factory.create_checkbox() + + print("Creating UI:") + print(self._button.render()) + print(self._checkbox.render()) + + def click_button(self): + print(self._button.on_click()) + + def toggle_checkbox(self): + print(self._checkbox.toggle()) + +def real_world_example(): + """Demo of a real-world Abstract Factory example""" + print("\n=== Real-World UI Factory Example ===") + + # Determine which factory to use based on configuration or environment + if sys.platform.startswith('win'): + print("\nRunning on Windows, using Windows UI components") + factory = WindowsFactory() + else: + print("\nRunning on a non-Windows platform, using Web UI components") + factory = WebFactory() + + # Create and use the application + app = Application(factory) + app.create_ui() + app.click_button() + app.toggle_checkbox() + + +if __name__ == "__main__": + print("Factory Pattern Demonstrations") + print("==============================") + + # Run the Simple Factory example + simple_factory_client() + + # Run the Factory Method example + factory_method_client() + + # Run the Abstract Factory example + abstract_factory_client() + + # Run the real-world example + real_world_example() \ No newline at end of file diff --git a/snippets/design-patterns/factory/factory_pattern.rb b/snippets/design-patterns/factory/factory_pattern.rb new file mode 100644 index 0000000..fd4d232 --- /dev/null +++ b/snippets/design-patterns/factory/factory_pattern.rb @@ -0,0 +1,310 @@ +#!/usr/bin/env ruby + +# Factory Pattern Implementation in Ruby +# +# The Factory Pattern is a creational design pattern that provides an interface for creating +# objects in a superclass, but allows subclasses to alter the type of objects that will be created. +# +# This example demonstrates a Vehicle Factory that can create different types of vehicles +# (Car, Motorcycle, Truck) based on the client's requirements. + +# Abstract Product - Vehicle +class Vehicle + attr_reader :make, :model, :year + + def initialize(make, model, year) + @make = make + @model = model + @year = year + end + + def get_info + "#{@year} #{@make} #{@model}" + end + + def start + "#{get_info} is starting..." + end + + def stop + "#{get_info} is stopping..." + end +end + +# Concrete Products +class Car < Vehicle + attr_reader :doors + + def initialize(make, model, year, doors = 4) + super(make, model, year) + @doors = doors + end + + def get_info + "#{super} (#{@doors}-door car)" + end + + def drive + "#{get_info} is driving on the road." + end +end + +class Motorcycle < Vehicle + attr_reader :engine_size + + def initialize(make, model, year, engine_size) + super(make, model, year) + @engine_size = engine_size + end + + def get_info + "#{super} (#{@engine_size}cc motorcycle)" + end + + def ride + "#{get_info} is riding at high speed." + end +end + +class Truck < Vehicle + attr_reader :capacity + + def initialize(make, model, year, capacity) + super(make, model, year) + @capacity = capacity + end + + def get_info + "#{super} (#{@capacity} ton truck)" + end + + def haul + "#{get_info} is hauling cargo." + end +end + +# Simple Factory +class VehicleFactory + CAR = 'car' + MOTORCYCLE = 'motorcycle' + TRUCK = 'truck' + + def self.create_vehicle(type, make, model, year, options = {}) + case type.downcase + when CAR + doors = options[:doors] || 4 + Car.new(make, model, year, doors) + when MOTORCYCLE + engine_size = options[:engine_size] || 250 + Motorcycle.new(make, model, year, engine_size) + when TRUCK + capacity = options[:capacity] || 5.0 + Truck.new(make, model, year, capacity) + else + raise "Vehicle type #{type} is not supported." + end + end +end + +# Factory Method Pattern Implementation +class VehicleFactoryMethod + def create_vehicle(make, model, year, options = {}) + raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def register_vehicle(make, model, year, options = {}) + # Common operations for all vehicles + vehicle = create_vehicle(make, model, year, options) + puts "Registering #{vehicle.get_info}" + puts "Assigning license plate" + vehicle + end +end + +# Concrete Factories +class CarFactory < VehicleFactoryMethod + def create_vehicle(make, model, year, options = {}) + doors = options[:doors] || 4 + Car.new(make, model, year, doors) + end +end + +class MotorcycleFactory < VehicleFactoryMethod + def create_vehicle(make, model, year, options = {}) + engine_size = options[:engine_size] || 250 + Motorcycle.new(make, model, year, engine_size) + end +end + +class TruckFactory < VehicleFactoryMethod + def create_vehicle(make, model, year, options = {}) + capacity = options[:capacity] || 5.0 + Truck.new(make, model, year, capacity) + end +end + +# Abstract Factory Pattern Implementation +# Parts +class Engine + attr_reader :type, :horsepower + + def initialize(type, horsepower) + @type = type + @horsepower = horsepower + end + + def get_specs + "#{@type} engine with #{@horsepower}hp" + end +end + +class Transmission + attr_reader :type, :gears + + def initialize(type, gears) + @type = type + @gears = gears + end + + def get_specs + "#{@type} transmission with #{@gears} gears" + end +end + +class Chassis + attr_reader :material, :weight + + def initialize(material, weight) + @material = material + @weight = weight + end + + def get_specs + "#{@material} chassis weighing #{@weight}kg" + end +end + +# Abstract Factory +class VehiclePartsFactory + def create_engine + raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def create_transmission + raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end + + def create_chassis + raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" + end +end + +# Concrete Abstract Factories +class SportVehiclePartsFactory < VehiclePartsFactory + def create_engine + Engine.new("V8", 450) + end + + def create_transmission + Transmission.new("Manual", 6) + end + + def create_chassis + Chassis.new("Carbon Fiber", 120) + end +end + +class EconomyVehiclePartsFactory < VehiclePartsFactory + def create_engine + Engine.new("Inline-4", 180) + end + + def create_transmission + Transmission.new("Automatic", 5) + end + + def create_chassis + Chassis.new("Steel", 300) + end +end + +class HeavyDutyVehiclePartsFactory < VehiclePartsFactory + def create_engine + Engine.new("Diesel V6", 350) + end + + def create_transmission + Transmission.new("Manual", 8) + end + + def create_chassis + Chassis.new("Reinforced Steel", 800) + end +end + +# Vehicle Assembler - Uses the Abstract Factory +class VehicleAssembler + def initialize(parts_factory) + @parts_factory = parts_factory + end + + def assemble_vehicle + engine = @parts_factory.create_engine + transmission = @parts_factory.create_transmission + chassis = @parts_factory.create_chassis + + puts "Assembling vehicle with:" + puts "- #{engine.get_specs}" + puts "- #{transmission.get_specs}" + puts "- #{chassis.get_specs}" + end +end + +# Client code demonstration +def client_code + puts "===== Simple Factory Pattern =====" + + car = VehicleFactory.create_vehicle(VehicleFactory::CAR, "Toyota", "Camry", 2023, { doors: 4 }) + motorcycle = VehicleFactory.create_vehicle(VehicleFactory::MOTORCYCLE, "Honda", "CBR", 2023, { engine_size: 600 }) + truck = VehicleFactory.create_vehicle(VehicleFactory::TRUCK, "Ford", "F-150", 2023, { capacity: 3.0 }) + + puts car.get_info + puts car.drive + + puts motorcycle.get_info + puts motorcycle.ride + + puts truck.get_info + puts truck.haul + + puts "\n===== Factory Method Pattern =====" + + car_factory = CarFactory.new + motorcycle_factory = MotorcycleFactory.new + truck_factory = TruckFactory.new + + new_car = car_factory.register_vehicle("BMW", "3 Series", 2023, { doors: 2 }) + new_motorcycle = motorcycle_factory.register_vehicle("Ducati", "Monster", 2023, { engine_size: 821 }) + new_truck = truck_factory.register_vehicle("Volvo", "VNL", 2023, { capacity: 20.0 }) + + puts new_car.drive + puts new_motorcycle.ride + puts new_truck.haul + + puts "\n===== Abstract Factory Pattern =====" + + puts "Building a sports car:" + sport_car_assembler = VehicleAssembler.new(SportVehiclePartsFactory.new) + sport_car_assembler.assemble_vehicle + + puts "\nBuilding an economy car:" + economy_car_assembler = VehicleAssembler.new(EconomyVehiclePartsFactory.new) + economy_car_assembler.assemble_vehicle + + puts "\nBuilding a heavy duty truck:" + heavy_duty_truck_assembler = VehicleAssembler.new(HeavyDutyVehiclePartsFactory.new) + heavy_duty_truck_assembler.assemble_vehicle +end + +# Run the example +client_code diff --git a/snippets/design-patterns/factory/factory_pattern.rs b/snippets/design-patterns/factory/factory_pattern.rs new file mode 100644 index 0000000..cf0b45c --- /dev/null +++ b/snippets/design-patterns/factory/factory_pattern.rs @@ -0,0 +1,447 @@ +/** + * Factory Pattern Implementation in Rust + * + * The Factory Pattern is a creational design pattern that provides an interface for creating + * objects in a superclass, but allows subclasses to alter the type of objects that will be created. + * + * This example demonstrates a Vehicle Factory that can create different types of vehicles + * (Car, Motorcycle, Truck) based on the client's requirements. + */ + +use std::fmt; + +// Abstract Product - Vehicle Trait +trait Vehicle { + fn get_info(&self) -> String; + fn start(&self) -> String { + format!("{} is starting...", self.get_info()) + } + fn stop(&self) -> String { + format!("{} is stopping...", self.get_info()) + } +} + +// Concrete Products +struct Car { + make: String, + model: String, + year: u32, + doors: u32, +} + +impl Car { + fn new(make: &str, model: &str, year: u32, doors: u32) -> Self { + Car { + make: make.to_string(), + model: model.to_string(), + year, + doors, + } + } + + fn drive(&self) -> String { + format!("{} is driving on the road.", self.get_info()) + } +} + +impl Vehicle for Car { + fn get_info(&self) -> String { + format!("{} {} {} ({}-door car)", self.year, self.make, self.model, self.doors) + } +} + +struct Motorcycle { + make: String, + model: String, + year: u32, + engine_size: u32, +} + +impl Motorcycle { + fn new(make: &str, model: &str, year: u32, engine_size: u32) -> Self { + Motorcycle { + make: make.to_string(), + model: model.to_string(), + year, + engine_size, + } + } + + fn ride(&self) -> String { + format!("{} is riding at high speed.", self.get_info()) + } +} + +impl Vehicle for Motorcycle { + fn get_info(&self) -> String { + format!( + "{} {} {} ({}cc motorcycle)", + self.year, self.make, self.model, self.engine_size + ) + } +} + +struct Truck { + make: String, + model: String, + year: u32, + capacity: f64, +} + +impl Truck { + fn new(make: &str, model: &str, year: u32, capacity: f64) -> Self { + Truck { + make: make.to_string(), + model: model.to_string(), + year, + capacity, + } + } + + fn haul(&self) -> String { + format!("{} is hauling cargo.", self.get_info()) + } +} + +impl Vehicle for Truck { + fn get_info(&self) -> String { + format!( + "{} {} {} ({} ton truck)", + self.year, self.make, self.model, self.capacity + ) + } +} + +// Simple Factory +enum VehicleType { + Car, + Motorcycle, + Truck, +} + +struct VehicleFactory; + +impl VehicleFactory { + fn create_car(make: &str, model: &str, year: u32, doors: u32) -> Box { + Box::new(Car::new(make, model, year, doors)) + } + + fn create_motorcycle(make: &str, model: &str, year: u32, engine_size: u32) -> Box { + Box::new(Motorcycle::new(make, model, year, engine_size)) + } + + fn create_truck(make: &str, model: &str, year: u32, capacity: f64) -> Box { + Box::new(Truck::new(make, model, year, capacity)) + } + + fn create_vehicle( + vehicle_type: VehicleType, + make: &str, + model: &str, + year: u32, + options: &[f64], + ) -> Box { + match vehicle_type { + VehicleType::Car => { + let doors = if options.is_empty() { 4 } else { options[0] as u32 }; + Self::create_car(make, model, year, doors) + } + VehicleType::Motorcycle => { + let engine_size = if options.is_empty() { 250 } else { options[0] as u32 }; + Self::create_motorcycle(make, model, year, engine_size) + } + VehicleType::Truck => { + let capacity = if options.is_empty() { 5.0 } else { options[0] }; + Self::create_truck(make, model, year, capacity) + } + } + } +} + +// Factory Method Pattern Implementation +trait VehicleFactoryMethod { + fn create_vehicle(&self, make: &str, model: &str, year: u32, options: &[f64]) -> Box; + + fn register_vehicle(&self, make: &str, model: &str, year: u32, options: &[f64]) -> Box { + // Common operations for all vehicles + let vehicle = self.create_vehicle(make, model, year, options); + println!("Registering {}", vehicle.get_info()); + println!("Assigning license plate"); + vehicle + } +} + +// Concrete Factories +struct CarFactory; + +impl VehicleFactoryMethod for CarFactory { + fn create_vehicle(&self, make: &str, model: &str, year: u32, options: &[f64]) -> Box { + let doors = if options.is_empty() { 4 } else { options[0] as u32 }; + Box::new(Car::new(make, model, year, doors)) + } +} + +struct MotorcycleFactory; + +impl VehicleFactoryMethod for MotorcycleFactory { + fn create_vehicle(&self, make: &str, model: &str, year: u32, options: &[f64]) -> Box { + let engine_size = if options.is_empty() { 250 } else { options[0] as u32 }; + Box::new(Motorcycle::new(make, model, year, engine_size)) + } +} + +struct TruckFactory; + +impl VehicleFactoryMethod for TruckFactory { + fn create_vehicle(&self, make: &str, model: &str, year: u32, options: &[f64]) -> Box { + let capacity = if options.is_empty() { 5.0 } else { options[0] }; + Box::new(Truck::new(make, model, year, capacity)) + } +} + +// Abstract Factory Pattern Implementation +// Parts +struct Engine { + engine_type: String, + horsepower: u32, +} + +impl Engine { + fn new(engine_type: &str, horsepower: u32) -> Self { + Engine { + engine_type: engine_type.to_string(), + horsepower, + } + } + + fn get_specs(&self) -> String { + format!("{} engine with {}hp", self.engine_type, self.horsepower) + } +} + +struct Transmission { + transmission_type: String, + gears: u32, +} + +impl Transmission { + fn new(transmission_type: &str, gears: u32) -> Self { + Transmission { + transmission_type: transmission_type.to_string(), + gears, + } + } + + fn get_specs(&self) -> String { + format!( + "{} transmission with {} gears", + self.transmission_type, self.gears + ) + } +} + +struct Chassis { + material: String, + weight: f64, +} + +impl Chassis { + fn new(material: &str, weight: f64) -> Self { + Chassis { + material: material.to_string(), + weight, + } + } + + fn get_specs(&self) -> String { + format!("{} chassis weighing {}kg", self.material, self.weight) + } +} + +// Abstract Factory +trait VehiclePartsFactory { + fn create_engine(&self) -> Engine; + fn create_transmission(&self) -> Transmission; + fn create_chassis(&self) -> Chassis; +} + +// Concrete Abstract Factories +struct SportVehiclePartsFactory; + +impl VehiclePartsFactory for SportVehiclePartsFactory { + fn create_engine(&self) -> Engine { + Engine::new("V8", 450) + } + + fn create_transmission(&self) -> Transmission { + Transmission::new("Manual", 6) + } + + fn create_chassis(&self) -> Chassis { + Chassis::new("Carbon Fiber", 120.0) + } +} + +struct EconomyVehiclePartsFactory; + +impl VehiclePartsFactory for EconomyVehiclePartsFactory { + fn create_engine(&self) -> Engine { + Engine::new("Inline-4", 180) + } + + fn create_transmission(&self) -> Transmission { + Transmission::new("Automatic", 5) + } + + fn create_chassis(&self) -> Chassis { + Chassis::new("Steel", 300.0) + } +} + +struct HeavyDutyVehiclePartsFactory; + +impl VehiclePartsFactory for HeavyDutyVehiclePartsFactory { + fn create_engine(&self) -> Engine { + Engine::new("Diesel V6", 350) + } + + fn create_transmission(&self) -> Transmission { + Transmission::new("Manual", 8) + } + + fn create_chassis(&self) -> Chassis { + Chassis::new("Reinforced Steel", 800.0) + } +} + +// Vehicle Assembler - Uses the Abstract Factory +struct VehicleAssembler { + parts_factory: T, +} + +impl VehicleAssembler { + fn new(parts_factory: T) -> Self { + VehicleAssembler { parts_factory } + } + + fn assemble_vehicle(&self) { + let engine = self.parts_factory.create_engine(); + let transmission = self.parts_factory.create_transmission(); + let chassis = self.parts_factory.create_chassis(); + + println!("Assembling vehicle with:"); + println!("- {}", engine.get_specs()); + println!("- {}", transmission.get_specs()); + println!("- {}", chassis.get_specs()); + } +} + +// Client code demonstration +fn client_code() { + println!("===== Simple Factory Pattern ====="); + + let car = VehicleFactory::create_vehicle( + VehicleType::Car, + "Toyota", + "Camry", + 2023, + &[4.0], + ); + let motorcycle = VehicleFactory::create_vehicle( + VehicleType::Motorcycle, + "Honda", + "CBR", + 2023, + &[600.0], + ); + let truck = VehicleFactory::create_vehicle( + VehicleType::Truck, + "Ford", + "F-150", + 2023, + &[3.0], + ); + + println!("{}", car.get_info()); + // We need to downcast to call specific methods + if let Some(car) = car.as_any().downcast_ref::() { + println!("{}", car.drive()); + } + + println!("{}", motorcycle.get_info()); + if let Some(motorcycle) = motorcycle.as_any().downcast_ref::() { + println!("{}", motorcycle.ride()); + } + + println!("{}", truck.get_info()); + if let Some(truck) = truck.as_any().downcast_ref::() { + println!("{}", truck.haul()); + } + + println!("\n===== Factory Method Pattern ====="); + + let car_factory = CarFactory; + let motorcycle_factory = MotorcycleFactory; + let truck_factory = TruckFactory; + + let new_car = car_factory.register_vehicle("BMW", "3 Series", 2023, &[2.0]); + let new_motorcycle = motorcycle_factory.register_vehicle("Ducati", "Monster", 2023, &[821.0]); + let new_truck = truck_factory.register_vehicle("Volvo", "VNL", 2023, &[20.0]); + + if let Some(car) = new_car.as_any().downcast_ref::() { + println!("{}", car.drive()); + } + if let Some(motorcycle) = new_motorcycle.as_any().downcast_ref::() { + println!("{}", motorcycle.ride()); + } + if let Some(truck) = new_truck.as_any().downcast_ref::() { + println!("{}", truck.haul()); + } + + println!("\n===== Abstract Factory Pattern ====="); + + println!("Building a sports car:"); + let sport_car_assembler = VehicleAssembler::new(SportVehiclePartsFactory); + sport_car_assembler.assemble_vehicle(); + + println!("\nBuilding an economy car:"); + let economy_car_assembler = VehicleAssembler::new(EconomyVehiclePartsFactory); + economy_car_assembler.assemble_vehicle(); + + println!("\nBuilding a heavy duty truck:"); + let heavy_duty_truck_assembler = VehicleAssembler::new(HeavyDutyVehiclePartsFactory); + heavy_duty_truck_assembler.assemble_vehicle(); +} + +// Extension trait to allow downcasting +trait AsAny { + fn as_any(&self) -> &dyn std::any::Any; +} + +impl AsAny for T { + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +// Extend Vehicle trait to include AsAny +trait VehicleExt: Vehicle + AsAny {} + +// Implement VehicleExt for all types that implement Vehicle +impl VehicleExt for T {} + +// Update the Vehicle trait to include AsAny functionality +trait Vehicle: AsAny { + fn get_info(&self) -> String; + fn start(&self) -> String { + format!("{} is starting...", self.get_info()) + } + fn stop(&self) -> String { + format!("{} is stopping...", self.get_info()) + } +} + +fn main() { + // Run the example + client_code(); +} diff --git a/snippets/design-patterns/observer/ObserverPattern.cpp b/snippets/design-patterns/observer/ObserverPattern.cpp new file mode 100644 index 0000000..ea9aa96 --- /dev/null +++ b/snippets/design-patterns/observer/ObserverPattern.cpp @@ -0,0 +1,363 @@ +#include +#include +#include +#include +#include + +/** + * Observer Design Pattern Implementation in C++ + * + * This demonstrates a weather station example of the Observer pattern. + */ + +// Forward declaration +class Subject; + +// Observer interface +class Observer { +public: + virtual ~Observer() = default; + virtual void update(Subject* subject) = 0; +}; + +// Subject interface +class Subject { +private: + std::vector observers; + +public: + virtual ~Subject() = default; + + void attach(Observer* observer) { + std::cout << "Attaching an observer\n"; + observers.push_back(observer); + } + + void detach(Observer* observer) { + std::cout << "Detaching an observer\n"; + observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); + } + + void notify() { + std::cout << "Notifying observers...\n"; + for (Observer* observer : observers) { + observer->update(this); + } + } + + // Subject should provide interface for observers to get state + virtual float getTemperature() const = 0; + virtual float getHumidity() const = 0; + virtual float getPressure() const = 0; +}; + +// Concrete Subject +class WeatherStation : public Subject { +private: + float temperature; + float humidity; + float pressure; + +public: + WeatherStation() : temperature(0.0f), humidity(0.0f), pressure(0.0f) {} + + void setMeasurements(float temp, float humidity, float pressure) { + std::cout << "Setting measurements: " << temp << "°C, " + << humidity << "%, " << pressure << " hPa\n"; + this->temperature = temp; + this->humidity = humidity; + this->pressure = pressure; + measurementsChanged(); + } + + void measurementsChanged() { + notify(); + } + + float getTemperature() const override { + return temperature; + } + + float getHumidity() const override { + return humidity; + } + + float getPressure() const override { + return pressure; + } +}; + +// Concrete Observer +class CurrentConditionsDisplay : public Observer { +private: + float temperature; + float humidity; + Subject* weatherStation; + +public: + CurrentConditionsDisplay(Subject* weatherStation) + : temperature(0.0f), humidity(0.0f), weatherStation(weatherStation) { + // Register with the subject + weatherStation->attach(this); + } + + ~CurrentConditionsDisplay() override { + weatherStation->detach(this); + } + + void update(Subject* subject) override { + if (subject == weatherStation) { + temperature = subject->getTemperature(); + humidity = subject->getHumidity(); + display(); + } + } + + void display() const { + std::cout << "Current conditions: " << temperature << "°C and " + << humidity << "% humidity\n"; + } +}; + +// Concrete Observer +class StatisticsDisplay : public Observer { +private: + float maxTemp; + float minTemp; + float tempSum; + int numReadings; + Subject* weatherStation; + +public: + StatisticsDisplay(Subject* weatherStation) + : maxTemp(0.0f), minTemp(200.0f), tempSum(0.0f), numReadings(0), weatherStation(weatherStation) { + // Register with the subject + weatherStation->attach(this); + } + + ~StatisticsDisplay() override { + weatherStation->detach(this); + } + + void update(Subject* subject) override { + if (subject == weatherStation) { + float temp = subject->getTemperature(); + tempSum += temp; + numReadings++; + + if (temp > maxTemp) { + maxTemp = temp; + } + + if (temp < minTemp) { + minTemp = temp; + } + + display(); + } + } + + void display() const { + std::cout << "Avg/Max/Min temperature: " << (tempSum / numReadings) + << "/" << maxTemp << "/" << minTemp << "\n"; + } +}; + +// Concrete Observer +class ForecastDisplay : public Observer { +private: + float currentPressure; + float lastPressure; + Subject* weatherStation; + +public: + ForecastDisplay(Subject* weatherStation) + : currentPressure(29.92f), lastPressure(0.0f), weatherStation(weatherStation) { + // Register with the subject + weatherStation->attach(this); + } + + ~ForecastDisplay() override { + weatherStation->detach(this); + } + + void update(Subject* subject) override { + if (subject == weatherStation) { + lastPressure = currentPressure; + currentPressure = subject->getPressure(); + display(); + } + } + + void display() const { + std::cout << "Forecast: "; + if (currentPressure > lastPressure) { + std::cout << "Improving weather on the way!\n"; + } else if (currentPressure == lastPressure) { + std::cout << "More of the same\n"; + } else if (currentPressure < lastPressure) { + std::cout << "Watch out for cooler, rainy weather\n"; + } + } +}; + +// Alternative implementation using modern C++ with smart pointers and templates +namespace ModernCpp { + +// Generic Subject class with templated notification +template +class Subject { +private: + // Using std::function for more flexibility in callback signatures + using Callback = std::function; + std::vector> observers; + size_t nextId = 0; + +public: + virtual ~Subject() = default; + + // Returns a token that can be used to detach + size_t attach(Callback callback) { + size_t id = nextId++; + observers.push_back({id, std::move(callback)}); + std::cout << "Modern C++: Attaching observer with ID " << id << "\n"; + return id; + } + + void detach(size_t id) { + std::cout << "Modern C++: Detaching observer with ID " << id << "\n"; + observers.erase( + std::remove_if(observers.begin(), observers.end(), + [id](const auto& pair) { return pair.first == id; }), + observers.end()); + } + + void notify(Args... args) { + std::cout << "Modern C++: Notifying observers...\n"; + for (const auto& [_, callback] : observers) { + callback(args...); + } + } +}; + +// Weather data structure +struct WeatherData { + float temperature; + float humidity; + float pressure; +}; + +// Concrete Subject: WeatherStation +class WeatherStation : public Subject { +private: + WeatherData data; + +public: + WeatherStation() : data{0.0f, 0.0f, 0.0f} {} + + void setMeasurements(float temp, float humidity, float pressure) { + std::cout << "Modern C++: Setting measurements: " << temp << "°C, " + << humidity << "%, " << pressure << " hPa\n"; + data.temperature = temp; + data.humidity = humidity; + data.pressure = pressure; + notify(data); + } + + const WeatherData& getCurrentData() const { + return data; + } +}; + +} // namespace ModernCpp + +int main() { + std::cout << "Observer Pattern Demonstration in C++\n"; + std::cout << "====================================\n\n"; + + // Classic implementation + std::cout << "Classic Implementation:\n"; + std::cout << "----------------------\n"; + + WeatherStation weatherStation; + + // Create displays (observers) + CurrentConditionsDisplay currentDisplay(&weatherStation); + StatisticsDisplay statisticsDisplay(&weatherStation); + ForecastDisplay forecastDisplay(&weatherStation); + + // Simulate weather changes + std::cout << "\nFirst weather update:\n"; + weatherStation.setMeasurements(27.5f, 65.0f, 30.4f); + + std::cout << "\nSecond weather update:\n"; + weatherStation.setMeasurements(28.2f, 70.0f, 29.2f); + + // Modern C++ implementation + std::cout << "\nModern C++ Implementation:\n"; + std::cout << "-------------------------\n"; + + ModernCpp::WeatherStation modernStation; + + // Attach observers using lambdas + auto currentDisplayId = modernStation.attach([](const ModernCpp::WeatherData& data) { + std::cout << "Current conditions: " << data.temperature << "°C and " + << data.humidity << "% humidity\n"; + }); + + // Statistics display with state in the lambda capture + float maxTemp = 0.0f; + float minTemp = 200.0f; + float tempSum = 0.0f; + int numReadings = 0; + + auto statisticsDisplayId = modernStation.attach([&](const ModernCpp::WeatherData& data) { + tempSum += data.temperature; + numReadings++; + + if (data.temperature > maxTemp) { + maxTemp = data.temperature; + } + + if (data.temperature < minTemp) { + minTemp = data.temperature; + } + + std::cout << "Avg/Max/Min temperature: " << (tempSum / numReadings) + << "/" << maxTemp << "/" << minTemp << "\n"; + }); + + // Forecast display with state in the lambda capture + float currentPressure = 29.92f; + float lastPressure = 0.0f; + + auto forecastDisplayId = modernStation.attach([&](const ModernCpp::WeatherData& data) { + lastPressure = currentPressure; + currentPressure = data.pressure; + + std::cout << "Forecast: "; + if (currentPressure > lastPressure) { + std::cout << "Improving weather on the way!\n"; + } else if (currentPressure == lastPressure) { + std::cout << "More of the same\n"; + } else if (currentPressure < lastPressure) { + std::cout << "Watch out for cooler, rainy weather\n"; + } + }); + + // Simulate weather changes + std::cout << "\nFirst weather update:\n"; + modernStation.setMeasurements(27.5f, 65.0f, 30.4f); + + std::cout << "\nSecond weather update:\n"; + modernStation.setMeasurements(28.2f, 70.0f, 29.2f); + + // Detach an observer + std::cout << "\nDetaching the current conditions display...\n"; + modernStation.detach(currentDisplayId); + + std::cout << "\nThird weather update (with one less observer):\n"; + modernStation.setMeasurements(26.7f, 90.0f, 29.2f); + + return 0; +} \ No newline at end of file diff --git a/snippets/design-patterns/observer/ObserverPattern.cs b/snippets/design-patterns/observer/ObserverPattern.cs new file mode 100644 index 0000000..957c369 --- /dev/null +++ b/snippets/design-patterns/observer/ObserverPattern.cs @@ -0,0 +1,448 @@ +/** + * Observer Pattern Implementation in C# + * + * The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency + * between objects so that when one object changes state, all its dependents are notified + * and updated automatically. + * + * This example demonstrates a simple weather station (subject) that notifies + * multiple display devices (observers) when weather data changes. + */ + +using System; +using System.Collections.Generic; + +namespace DesignPatterns.Observer +{ + // ========== Observer Interface ========== + + /// + /// Observer interface to be implemented by all display devices + /// + public interface IObserver + { + /// + /// Update method called by the subject when state changes + /// + /// The current temperature + /// The current humidity + /// The current pressure + void Update(float temperature, float humidity, float pressure); + + /// + /// Gets the name of the observer for identification + /// + string Name { get; } + } + + // ========== Subject Interface ========== + + /// + /// Subject interface to be implemented by objects that notify observers + /// + public interface ISubject + { + /// + /// Register an observer to be notified of changes + /// + /// The observer to register + void RegisterObserver(IObserver observer); + + /// + /// Remove an observer from the notification list + /// + /// The observer to remove + void RemoveObserver(IObserver observer); + + /// + /// Notify all registered observers of state changes + /// + void NotifyObservers(); + } + + // ========== Weather Data Implementation ========== + + /// + /// WeatherData class implements the ISubject interface + /// Maintains the current weather state and notifies observers when it changes + /// + public class WeatherData : ISubject + { + private readonly List _observers; + private float _temperature; + private float _humidity; + private float _pressure; + + /// + /// Constructor initializes the observers list + /// + public WeatherData() + { + _observers = new List(); + } + + /// + /// Register an observer to be notified of changes + /// + /// The observer to register + public void RegisterObserver(IObserver observer) + { + _observers.Add(observer); + Console.WriteLine($"Observer '{observer.Name}' registered"); + } + + /// + /// Remove an observer from the notification list + /// + /// The observer to remove + public void RemoveObserver(IObserver observer) + { + if (_observers.Remove(observer)) + { + Console.WriteLine($"Observer '{observer.Name}' removed"); + } + } + + /// + /// Notify all registered observers of state changes + /// + public void NotifyObservers() + { + foreach (var observer in _observers) + { + observer.Update(_temperature, _humidity, _pressure); + } + } + + /// + /// Called when measurements have been updated + /// + private void MeasurementsChanged() + { + NotifyObservers(); + } + + /// + /// Set new weather measurements + /// + /// New temperature value + /// New humidity value + /// New pressure value + public void SetMeasurements(float temperature, float humidity, float pressure) + { + _temperature = temperature; + _humidity = humidity; + _pressure = pressure; + MeasurementsChanged(); + } + + /// + /// Get the current temperature + /// + public float Temperature => _temperature; + + /// + /// Get the current humidity + /// + public float Humidity => _humidity; + + /// + /// Get the current pressure + /// + public float Pressure => _pressure; + } + + // ========== Display Implementations ========== + + /// + /// CurrentConditionsDisplay implements the IObserver interface + /// Displays the current weather conditions + /// + public class CurrentConditionsDisplay : IObserver + { + private readonly ISubject _weatherData; + private float _temperature; + private float _humidity; + + /// + /// Constructor + /// + /// The weather data subject + /// The name of this observer + public CurrentConditionsDisplay(ISubject weatherData, string name) + { + Name = name; + _weatherData = weatherData; + weatherData.RegisterObserver(this); + } + + /// + /// Update method implementation + /// + /// The current temperature + /// The current humidity + /// The current pressure + public void Update(float temperature, float humidity, float pressure) + { + _temperature = temperature; + _humidity = humidity; + Display(); + } + + /// + /// Display the current conditions + /// + public void Display() + { + Console.WriteLine($"[{Name}] Current conditions: {_temperature:F1}°F and {_humidity:F1}% humidity"); + } + + /// + /// Gets the name of the observer + /// + public string Name { get; } + } + + /// + /// StatisticsDisplay implements the IObserver interface + /// Displays average, minimum, and maximum temperatures + /// + public class StatisticsDisplay : IObserver + { + private readonly ISubject _weatherData; + private float _maxTemp = 0.0f; + private float _minTemp = 200.0f; + private float _tempSum = 0.0f; + private int _numReadings = 0; + + /// + /// Constructor + /// + /// The weather data subject + /// The name of this observer + public StatisticsDisplay(ISubject weatherData, string name) + { + Name = name; + _weatherData = weatherData; + weatherData.RegisterObserver(this); + } + + /// + /// Update method implementation + /// + /// The current temperature + /// The current humidity + /// The current pressure + public void Update(float temperature, float humidity, float pressure) + { + _tempSum += temperature; + _numReadings++; + + if (temperature > _maxTemp) + { + _maxTemp = temperature; + } + + if (temperature < _minTemp) + { + _minTemp = temperature; + } + + Display(); + } + + /// + /// Display the statistics + /// + public void Display() + { + Console.WriteLine($"[{Name}] Avg/Max/Min temperature: {_tempSum / _numReadings:F1}/{_maxTemp:F1}/{_minTemp:F1}"); + } + + /// + /// Gets the name of the observer + /// + public string Name { get; } + } + + /// + /// ForecastDisplay implements the IObserver interface + /// Displays a simple weather forecast based on pressure changes + /// + public class ForecastDisplay : IObserver + { + private readonly ISubject _weatherData; + private float _currentPressure = 29.92f; + private float _lastPressure; + + /// + /// Constructor + /// + /// The weather data subject + /// The name of this observer + public ForecastDisplay(ISubject weatherData, string name) + { + Name = name; + _weatherData = weatherData; + weatherData.RegisterObserver(this); + } + + /// + /// Update method implementation + /// + /// The current temperature + /// The current humidity + /// The current pressure + public void Update(float temperature, float humidity, float pressure) + { + _lastPressure = _currentPressure; + _currentPressure = pressure; + Display(); + } + + /// + /// Display the forecast + /// + public void Display() + { + Console.Write($"[{Name}] Forecast: "); + + if (_currentPressure > _lastPressure) + { + Console.WriteLine("Improving weather on the way!"); + } + else if (Math.Abs(_currentPressure - _lastPressure) < 0.001) + { + Console.WriteLine("More of the same"); + } + else + { + Console.WriteLine("Watch out for cooler, rainy weather"); + } + } + + /// + /// Gets the name of the observer + /// + public string Name { get; } + } + + /// + /// HeatIndexDisplay implements the IObserver interface + /// Displays the heat index based on temperature and humidity + /// + public class HeatIndexDisplay : IObserver + { + private readonly ISubject _weatherData; + private float _heatIndex = 0.0f; + + /// + /// Constructor + /// + /// The weather data subject + /// The name of this observer + public HeatIndexDisplay(ISubject weatherData, string name) + { + Name = name; + _weatherData = weatherData; + weatherData.RegisterObserver(this); + } + + /// + /// Update method implementation + /// + /// The current temperature + /// The current humidity + /// The current pressure + public void Update(float temperature, float humidity, float pressure) + { + _heatIndex = ComputeHeatIndex(temperature, humidity); + Display(); + } + + /// + /// Compute the heat index based on temperature and humidity + /// + /// Temperature + /// Relative humidity + /// Heat index + private float ComputeHeatIndex(float t, float rh) + { + // This is a simplified formula for heat index + return (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) + + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + + 0.000000000843296 * (t * t * rh * rh * rh)) - + (0.0000000000481975 * (t * t * t * rh * rh * rh)); + } + + /// + /// Display the heat index + /// + public void Display() + { + Console.WriteLine($"[{Name}] Heat index: {_heatIndex:F1}"); + } + + /// + /// Gets the name of the observer + /// + public string Name { get; } + } + + // ========== Demo Code ========== + + /// + /// Demo class to run the weather station example + /// + public class WeatherStationDemo + { + /// + /// Run the weather station demo + /// + public static void RunWeatherStation() + { + // Create the WeatherData subject + var weatherData = new WeatherData(); + + // Create display devices (observers) + var currentDisplay = new CurrentConditionsDisplay(weatherData, "Current Display"); + var statisticsDisplay = new StatisticsDisplay(weatherData, "Statistics Display"); + var forecastDisplay = new ForecastDisplay(weatherData, "Forecast Display"); + var heatIndexDisplay = new HeatIndexDisplay(weatherData, "Heat Index Display"); + + Console.WriteLine("\n=== First Weather Update ==="); + // Simulate new weather measurements + weatherData.SetMeasurements(80, 65, 30.4f); + + Console.WriteLine("\n=== Second Weather Update ==="); + // Simulate new weather measurements + weatherData.SetMeasurements(82, 70, 29.2f); + + Console.WriteLine("\n=== Third Weather Update ==="); + // Simulate new weather measurements + weatherData.SetMeasurements(78, 90, 29.2f); + + Console.WriteLine("\n=== Removing an Observer ==="); + // Remove an observer + weatherData.RemoveObserver(forecastDisplay); + + Console.WriteLine("\n=== Fourth Weather Update ==="); + // One more measurement after removing an observer + weatherData.SetMeasurements(75, 60, 30.1f); + } + } + + // Main program, run the example + class Program + { + static void Main(string[] args) + { + WeatherStationDemo.RunWeatherStation(); + Console.ReadLine(); + } + } +} \ No newline at end of file diff --git a/snippets/design-patterns/observer/ObserverPattern.java b/snippets/design-patterns/observer/ObserverPattern.java new file mode 100644 index 0000000..cfa3194 --- /dev/null +++ b/snippets/design-patterns/observer/ObserverPattern.java @@ -0,0 +1,192 @@ +/** + * Observer Pattern Implementation in Java + * + * This demonstrates a weather station example of the Observer pattern. + */ + +import java.util.ArrayList; +import java.util.List; + +// Observer interface +interface Observer { + void update(); +} + +// Subject interface +interface Subject { + void registerObserver(Observer observer); + void removeObserver(Observer observer); + void notifyObservers(); +} + +// Concrete Subject: WeatherStation +class WeatherStation implements Subject { + private List observers; + private float temperature; + private float humidity; + private float pressure; + + public WeatherStation() { + observers = new ArrayList<>(); + } + + @Override + public void registerObserver(Observer observer) { + observers.add(observer); + } + + @Override + public void removeObserver(Observer observer) { + int index = observers.indexOf(observer); + if (index >= 0) { + observers.remove(index); + } + } + + @Override + public void notifyObservers() { + for (Observer observer : observers) { + observer.update(); + } + } + + public void measurementsChanged() { + notifyObservers(); + } + + public void setMeasurements(float temperature, float humidity, float pressure) { + this.temperature = temperature; + this.humidity = humidity; + this.pressure = pressure; + measurementsChanged(); + } + + // Getter methods that Observers can use to pull data + public float getTemperature() { + return temperature; + } + + public float getHumidity() { + return humidity; + } + + public float getPressure() { + return pressure; + } +} + +// Concrete Observer: CurrentConditionsDisplay +class CurrentConditionsDisplay implements Observer { + private float temperature; + private float humidity; + private WeatherStation weatherStation; + + public CurrentConditionsDisplay(WeatherStation weatherStation) { + this.weatherStation = weatherStation; + weatherStation.registerObserver(this); + } + + @Override + public void update() { + // Pull model - observer gets the data it needs from the subject + this.temperature = weatherStation.getTemperature(); + this.humidity = weatherStation.getHumidity(); + display(); + } + + public void display() { + System.out.println("Current conditions: " + temperature + "°C and " + humidity + "% humidity"); + } +} + +// Concrete Observer: StatisticsDisplay +class StatisticsDisplay implements Observer { + private float maxTemp = 0.0f; + private float minTemp = 200.0f; + private float tempSum = 0.0f; + private int numReadings = 0; + private WeatherStation weatherStation; + + public StatisticsDisplay(WeatherStation weatherStation) { + this.weatherStation = weatherStation; + weatherStation.registerObserver(this); + } + + @Override + public void update() { + float temperature = weatherStation.getTemperature(); + tempSum += temperature; + numReadings++; + + if (temperature > maxTemp) { + maxTemp = temperature; + } + + if (temperature < minTemp) { + minTemp = temperature; + } + + display(); + } + + public void display() { + System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp); + } +} + +// Concrete Observer: ForecastDisplay +class ForecastDisplay implements Observer { + private float currentPressure = 29.92f; + private float lastPressure; + private WeatherStation weatherStation; + + public ForecastDisplay(WeatherStation weatherStation) { + this.weatherStation = weatherStation; + weatherStation.registerObserver(this); + } + + @Override + public void update() { + lastPressure = currentPressure; + currentPressure = weatherStation.getPressure(); + + display(); + } + + public void display() { + System.out.print("Forecast: "); + if (currentPressure > lastPressure) { + System.out.println("Improving weather on the way!"); + } else if (currentPressure == lastPressure) { + System.out.println("More of the same"); + } else if (currentPressure < lastPressure) { + System.out.println("Watch out for cooler, rainy weather"); + } + } +} + +// Demonstration +public class ObserverPattern { + public static void main(String[] args) { + // Create the subject + WeatherStation weatherStation = new WeatherStation(); + + // Create and register observers + CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherStation); + StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherStation); + ForecastDisplay forecastDisplay = new ForecastDisplay(weatherStation); + + // Simulate new weather measurements + System.out.println("First weather update:"); + weatherStation.setMeasurements(27.5f, 65.0f, 30.4f); + + System.out.println("\nSecond weather update:"); + weatherStation.setMeasurements(28.2f, 70.0f, 29.2f); + + System.out.println("\nRemoving current conditions display..."); + weatherStation.removeObserver(currentDisplay); + + System.out.println("\nThird weather update (with one less observer):"); + weatherStation.setMeasurements(26.7f, 90.0f, 29.2f); + } +} \ No newline at end of file diff --git a/snippets/design-patterns/observer/observer_pattern.c b/snippets/design-patterns/observer/observer_pattern.c new file mode 100644 index 0000000..8fa5808 --- /dev/null +++ b/snippets/design-patterns/observer/observer_pattern.c @@ -0,0 +1,528 @@ +/** + * Observer Pattern Implementation in C + * + * The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency + * between objects so that when one object changes state, all its dependents are notified + * and updated automatically. + * + * This example demonstrates a simple weather station (subject) that notifies + * multiple display devices (observers) when weather data changes. + * + * Note: Since C is not an object-oriented language, we use structs with function pointers + * to simulate classes and interfaces. + */ + +#include +#include +#include +#include + +// Forward declarations +typedef struct Observer Observer; +typedef struct Subject Subject; +typedef struct WeatherData WeatherData; +typedef struct DisplayDevice DisplayDevice; + +// ========== Observer Interface ========== + +// Observer "interface" - implemented by display devices +struct Observer { + void (*update)(Observer* self, float temperature, float humidity, float pressure); + char name[50]; +}; + +// ========== Subject Interface ========== + +// Subject "interface" - implemented by weather data +typedef struct ObserverNode { + Observer* observer; + struct ObserverNode* next; +} ObserverNode; + +struct Subject { + void (*registerObserver)(Subject* self, Observer* observer); + void (*removeObserver)(Subject* self, Observer* observer); + void (*notifyObservers)(Subject* self); + + // List of observers + ObserverNode* observers; +}; + +// ========== Weather Data Implementation ========== + +// Concrete Subject implementation +struct WeatherData { + Subject subject; + float temperature; + float humidity; + float pressure; +}; + +// Forward declarations of WeatherData methods +void weatherData_registerObserver(Subject* self, Observer* observer); +void weatherData_removeObserver(Subject* self, Observer* observer); +void weatherData_notifyObservers(Subject* self); +void weatherData_measurementsChanged(WeatherData* self); +void weatherData_setMeasurements(WeatherData* self, float temperature, float humidity, float pressure); + +// Create a new WeatherData instance +WeatherData* createWeatherData() { + WeatherData* weatherData = (WeatherData*)malloc(sizeof(WeatherData)); + if (weatherData == NULL) { + return NULL; + } + + // Initialize the subject interface + weatherData->subject.registerObserver = weatherData_registerObserver; + weatherData->subject.removeObserver = weatherData_removeObserver; + weatherData->subject.notifyObservers = weatherData_notifyObservers; + weatherData->subject.observers = NULL; + + // Initialize weather data + weatherData->temperature = 0.0f; + weatherData->humidity = 0.0f; + weatherData->pressure = 0.0f; + + return weatherData; +} + +// Register an observer +void weatherData_registerObserver(Subject* self, Observer* observer) { + WeatherData* weatherData = (WeatherData*)self; + + // Create a new node + ObserverNode* node = (ObserverNode*)malloc(sizeof(ObserverNode)); + if (node == NULL) { + return; + } + + node->observer = observer; + node->next = NULL; + + // Add to the end of the list + if (self->observers == NULL) { + self->observers = node; + } else { + ObserverNode* current = self->observers; + while (current->next != NULL) { + current = current->next; + } + current->next = node; + } + + printf("Observer '%s' registered\n", observer->name); +} + +// Remove an observer +void weatherData_removeObserver(Subject* self, Observer* observer) { + if (self->observers == NULL) { + return; + } + + // If the first node matches + if (self->observers->observer == observer) { + ObserverNode* temp = self->observers; + self->observers = self->observers->next; + free(temp); + printf("Observer '%s' removed\n", observer->name); + return; + } + + // Check the rest of the list + ObserverNode* current = self->observers; + while (current->next != NULL) { + if (current->next->observer == observer) { + ObserverNode* temp = current->next; + current->next = temp->next; + free(temp); + printf("Observer '%s' removed\n", observer->name); + return; + } + current = current->next; + } +} + +// Notify all observers +void weatherData_notifyObservers(Subject* self) { + WeatherData* weatherData = (WeatherData*)self; + ObserverNode* current = self->observers; + + while (current != NULL) { + current->observer->update(current->observer, weatherData->temperature, + weatherData->humidity, weatherData->pressure); + current = current->next; + } +} + +// Called when measurements change +void weatherData_measurementsChanged(WeatherData* self) { + self->subject.notifyObservers((Subject*)self); +} + +// Set new measurements +void weatherData_setMeasurements(WeatherData* self, float temperature, float humidity, float pressure) { + self->temperature = temperature; + self->humidity = humidity; + self->pressure = pressure; + weatherData_measurementsChanged(self); +} + +// Free WeatherData resources +void destroyWeatherData(WeatherData* weatherData) { + if (weatherData == NULL) { + return; + } + + // Free all observer nodes + ObserverNode* current = weatherData->subject.observers; + while (current != NULL) { + ObserverNode* temp = current; + current = current->next; + free(temp); + } + + // Free the WeatherData itself + free(weatherData); +} + +// ========== Display Device Implementations ========== + +// Current Conditions Display +typedef struct { + Observer observer; + float temperature; + float humidity; + Subject* weatherData; +} CurrentConditionsDisplay; + +// Forward declaration +void currentConditionsDisplay_update(Observer* self, float temperature, float humidity, float pressure); +void currentConditionsDisplay_display(CurrentConditionsDisplay* display); + +// Create a new CurrentConditionsDisplay +CurrentConditionsDisplay* createCurrentConditionsDisplay(Subject* weatherData, const char* name) { + CurrentConditionsDisplay* display = (CurrentConditionsDisplay*)malloc(sizeof(CurrentConditionsDisplay)); + if (display == NULL) { + return NULL; + } + + // Initialize the observer interface + display->observer.update = currentConditionsDisplay_update; + strncpy(display->observer.name, name, sizeof(display->observer.name) - 1); + display->observer.name[sizeof(display->observer.name) - 1] = '\0'; + + // Initialize display data + display->temperature = 0.0f; + display->humidity = 0.0f; + display->weatherData = weatherData; + + // Register with the subject + weatherData->registerObserver(weatherData, (Observer*)display); + + return display; +} + +// Update method implementation +void currentConditionsDisplay_update(Observer* self, float temperature, float humidity, float pressure) { + CurrentConditionsDisplay* display = (CurrentConditionsDisplay*)self; + display->temperature = temperature; + display->humidity = humidity; + currentConditionsDisplay_display(display); +} + +// Display current conditions +void currentConditionsDisplay_display(CurrentConditionsDisplay* display) { + printf("[%s] Current conditions: %.1f°F and %.1f%% humidity\n", + display->observer.name, display->temperature, display->humidity); +} + +// Free CurrentConditionsDisplay resources +void destroyCurrentConditionsDisplay(CurrentConditionsDisplay* display, Subject* weatherData) { + if (display == NULL) { + return; + } + + // Unregister from the subject + if (weatherData != NULL) { + weatherData->removeObserver(weatherData, (Observer*)display); + } + + // Free the display itself + free(display); +} + +// Statistics Display +typedef struct { + Observer observer; + float maxTemp; + float minTemp; + float tempSum; + int numReadings; + Subject* weatherData; +} StatisticsDisplay; + +// Forward declaration +void statisticsDisplay_update(Observer* self, float temperature, float humidity, float pressure); +void statisticsDisplay_display(StatisticsDisplay* display); + +// Create a new StatisticsDisplay +StatisticsDisplay* createStatisticsDisplay(Subject* weatherData, const char* name) { + StatisticsDisplay* display = (StatisticsDisplay*)malloc(sizeof(StatisticsDisplay)); + if (display == NULL) { + return NULL; + } + + // Initialize the observer interface + display->observer.update = statisticsDisplay_update; + strncpy(display->observer.name, name, sizeof(display->observer.name) - 1); + display->observer.name[sizeof(display->observer.name) - 1] = '\0'; + + // Initialize display data + display->maxTemp = 0.0f; + display->minTemp = 200.0f; // A very low starting value + display->tempSum = 0.0f; + display->numReadings = 0; + display->weatherData = weatherData; + + // Register with the subject + weatherData->registerObserver(weatherData, (Observer*)display); + + return display; +} + +// Update method implementation +void statisticsDisplay_update(Observer* self, float temperature, float humidity, float pressure) { + StatisticsDisplay* display = (StatisticsDisplay*)self; + + display->tempSum += temperature; + display->numReadings++; + + if (temperature > display->maxTemp) { + display->maxTemp = temperature; + } + + if (temperature < display->minTemp) { + display->minTemp = temperature; + } + + statisticsDisplay_display(display); +} + +// Display statistics +void statisticsDisplay_display(StatisticsDisplay* display) { + printf("[%s] Avg/Max/Min temperature: %.1f/%.1f/%.1f\n", + display->observer.name, + display->tempSum / display->numReadings, + display->maxTemp, + display->minTemp); +} + +// Free StatisticsDisplay resources +void destroyStatisticsDisplay(StatisticsDisplay* display, Subject* weatherData) { + if (display == NULL) { + return; + } + + // Unregister from the subject + if (weatherData != NULL) { + weatherData->removeObserver(weatherData, (Observer*)display); + } + + // Free the display itself + free(display); +} + +// Forecast Display +typedef struct { + Observer observer; + float currentPressure; + float lastPressure; + Subject* weatherData; +} ForecastDisplay; + +// Forward declaration +void forecastDisplay_update(Observer* self, float temperature, float humidity, float pressure); +void forecastDisplay_display(ForecastDisplay* display); + +// Create a new ForecastDisplay +ForecastDisplay* createForecastDisplay(Subject* weatherData, const char* name) { + ForecastDisplay* display = (ForecastDisplay*)malloc(sizeof(ForecastDisplay)); + if (display == NULL) { + return NULL; + } + + // Initialize the observer interface + display->observer.update = forecastDisplay_update; + strncpy(display->observer.name, name, sizeof(display->observer.name) - 1); + display->observer.name[sizeof(display->observer.name) - 1] = '\0'; + + // Initialize display data + display->currentPressure = 29.92f; // Default starting pressure + display->lastPressure = 0.0f; + display->weatherData = weatherData; + + // Register with the subject + weatherData->registerObserver(weatherData, (Observer*)display); + + return display; +} + +// Update method implementation +void forecastDisplay_update(Observer* self, float temperature, float humidity, float pressure) { + ForecastDisplay* display = (ForecastDisplay*)self; + + display->lastPressure = display->currentPressure; + display->currentPressure = pressure; + + forecastDisplay_display(display); +} + +// Display forecast +void forecastDisplay_display(ForecastDisplay* display) { + printf("[%s] Forecast: ", display->observer.name); + + if (display->currentPressure > display->lastPressure) { + printf("Improving weather on the way!\n"); + } else if (display->currentPressure == display->lastPressure) { + printf("More of the same\n"); + } else if (display->currentPressure < display->lastPressure) { + printf("Watch out for cooler, rainy weather\n"); + } +} + +// Free ForecastDisplay resources +void destroyForecastDisplay(ForecastDisplay* display, Subject* weatherData) { + if (display == NULL) { + return; + } + + // Unregister from the subject + if (weatherData != NULL) { + weatherData->removeObserver(weatherData, (Observer*)display); + } + + // Free the display itself + free(display); +} + +// ========== Heat Index Display ========== + +typedef struct { + Observer observer; + float heatIndex; + Subject* weatherData; +} HeatIndexDisplay; + +// Forward declaration +void heatIndexDisplay_update(Observer* self, float temperature, float humidity, float pressure); +void heatIndexDisplay_display(HeatIndexDisplay* display); +float computeHeatIndex(float temperature, float humidity); + +// Create a new HeatIndexDisplay +HeatIndexDisplay* createHeatIndexDisplay(Subject* weatherData, const char* name) { + HeatIndexDisplay* display = (HeatIndexDisplay*)malloc(sizeof(HeatIndexDisplay)); + if (display == NULL) { + return NULL; + } + + // Initialize the observer interface + display->observer.update = heatIndexDisplay_update; + strncpy(display->observer.name, name, sizeof(display->observer.name) - 1); + display->observer.name[sizeof(display->observer.name) - 1] = '\0'; + + // Initialize display data + display->heatIndex = 0.0f; + display->weatherData = weatherData; + + // Register with the subject + weatherData->registerObserver(weatherData, (Observer*)display); + + return display; +} + +// Update method implementation +void heatIndexDisplay_update(Observer* self, float temperature, float humidity, float pressure) { + HeatIndexDisplay* display = (HeatIndexDisplay*)self; + + display->heatIndex = computeHeatIndex(temperature, humidity); + heatIndexDisplay_display(display); +} + +// Display heat index +void heatIndexDisplay_display(HeatIndexDisplay* display) { + printf("[%s] Heat index: %.1f\n", display->observer.name, display->heatIndex); +} + +// Compute the heat index +float computeHeatIndex(float t, float rh) { + // This is a simplified formula for heat index + return (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) + + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + + 0.000000000843296 * (t * t * rh * rh * rh)) - + (0.0000000000481975 * (t * t * t * rh * rh * rh))); +} + +// Free HeatIndexDisplay resources +void destroyHeatIndexDisplay(HeatIndexDisplay* display, Subject* weatherData) { + if (display == NULL) { + return; + } + + // Unregister from the subject + if (weatherData != NULL) { + weatherData->removeObserver(weatherData, (Observer*)display); + } + + // Free the display itself + free(display); +} + +// ========== Demo Code ========== + +void runWeatherStation() { + // Create the WeatherData subject + WeatherData* weatherData = createWeatherData(); + + // Create display devices (observers) + CurrentConditionsDisplay* currentDisplay = createCurrentConditionsDisplay((Subject*)weatherData, "Current Display"); + StatisticsDisplay* statsDisplay = createStatisticsDisplay((Subject*)weatherData, "Statistics Display"); + ForecastDisplay* forecastDisplay = createForecastDisplay((Subject*)weatherData, "Forecast Display"); + HeatIndexDisplay* heatIndexDisplay = createHeatIndexDisplay((Subject*)weatherData, "Heat Index Display"); + + printf("\n=== First Weather Update ===\n"); + // Simulate new weather measurements + weatherData_setMeasurements(weatherData, 80.0f, 65.0f, 30.4f); + + printf("\n=== Second Weather Update ===\n"); + // Simulate new weather measurements + weatherData_setMeasurements(weatherData, 82.0f, 70.0f, 29.2f); + + printf("\n=== Third Weather Update ===\n"); + // Simulate new weather measurements + weatherData_setMeasurements(weatherData, 78.0f, 90.0f, 29.2f); + + printf("\n=== Removing an Observer ===\n"); + // Remove an observer + weatherData->subject.removeObserver((Subject*)weatherData, (Observer*)forecastDisplay); + + printf("\n=== Fourth Weather Update ===\n"); + // One more measurement after removing an observer + weatherData_setMeasurements(weatherData, 75.0f, 60.0f, 30.1f); + + // Clean up resources + destroyCurrentConditionsDisplay(currentDisplay, (Subject*)weatherData); + destroyStatisticsDisplay(statsDisplay, (Subject*)weatherData); + destroyForecastDisplay(forecastDisplay, NULL); // Already removed + destroyHeatIndexDisplay(heatIndexDisplay, (Subject*)weatherData); + destroyWeatherData(weatherData); +} + +int main() { + // Run the demo + runWeatherStation(); + return 0; +} \ No newline at end of file diff --git a/snippets/design-patterns/observer/observer_pattern.go b/snippets/design-patterns/observer/observer_pattern.go new file mode 100644 index 0000000..7c9748b --- /dev/null +++ b/snippets/design-patterns/observer/observer_pattern.go @@ -0,0 +1,295 @@ +package main + +import ( + "fmt" + "math" +) + +/** + * Observer Pattern Implementation in Go + * + * This demonstrates a weather station example of the Observer pattern. + */ + +// Observer interface defines the update method +type Observer interface { + Update(subject Subject) + Display() +} + +// Subject interface defines methods for attaching, detaching, and notifying observers +type Subject interface { + RegisterObserver(observer Observer) + RemoveObserver(observer Observer) + NotifyObservers() + GetTemperature() float64 + GetHumidity() float64 + GetPressure() float64 +} + +// WeatherStation is a concrete subject +type WeatherStation struct { + observers []Observer + temperature float64 + humidity float64 + pressure float64 +} + +// NewWeatherStation creates a new WeatherStation +func NewWeatherStation() *WeatherStation { + return &WeatherStation{ + observers: make([]Observer, 0), + temperature: 0, + humidity: 0, + pressure: 0, + } +} + +// RegisterObserver adds an observer to the list +func (ws *WeatherStation) RegisterObserver(observer Observer) { + fmt.Println("Registering an observer") + ws.observers = append(ws.observers, observer) +} + +// RemoveObserver removes an observer from the list +func (ws *WeatherStation) RemoveObserver(observer Observer) { + fmt.Println("Removing an observer") + for i, obs := range ws.observers { + if obs == observer { + // Remove the observer by slicing it out + ws.observers = append(ws.observers[:i], ws.observers[i+1:]...) + break + } + } +} + +// NotifyObservers notifies all registered observers +func (ws *WeatherStation) NotifyObservers() { + fmt.Println("Notifying observers...") + for _, observer := range ws.observers { + observer.Update(ws) + } +} + +// SetMeasurements sets the measurements and notifies observers +func (ws *WeatherStation) SetMeasurements(temperature, humidity, pressure float64) { + fmt.Printf("Setting measurements: %.1f°C, %.1f%%, %.1f hPa\n", temperature, humidity, pressure) + ws.temperature = temperature + ws.humidity = humidity + ws.pressure = pressure + ws.MeasurementsChanged() +} + +// MeasurementsChanged triggers notifications +func (ws *WeatherStation) MeasurementsChanged() { + ws.NotifyObservers() +} + +// Getter methods for observers +func (ws *WeatherStation) GetTemperature() float64 { + return ws.temperature +} + +func (ws *WeatherStation) GetHumidity() float64 { + return ws.humidity +} + +func (ws *WeatherStation) GetPressure() float64 { + return ws.pressure +} + +// CurrentConditionsDisplay is a concrete observer that displays current conditions +type CurrentConditionsDisplay struct { + temperature float64 + humidity float64 + weatherStation Subject +} + +// NewCurrentConditionsDisplay creates a new CurrentConditionsDisplay +func NewCurrentConditionsDisplay(weatherStation Subject) *CurrentConditionsDisplay { + display := &CurrentConditionsDisplay{ + temperature: 0, + humidity: 0, + weatherStation: weatherStation, + } + weatherStation.RegisterObserver(display) + return display +} + +// Update is called when the subject's state changes +func (cd *CurrentConditionsDisplay) Update(subject Subject) { + cd.temperature = subject.GetTemperature() + cd.humidity = subject.GetHumidity() + cd.Display() +} + +// Display shows the current conditions +func (cd *CurrentConditionsDisplay) Display() { + fmt.Printf("Current conditions: %.1f°C and %.1f%% humidity\n", cd.temperature, cd.humidity) +} + +// StatisticsDisplay is a concrete observer that displays statistics +type StatisticsDisplay struct { + maxTemp float64 + minTemp float64 + tempSum float64 + numReadings int + weatherStation Subject +} + +// NewStatisticsDisplay creates a new StatisticsDisplay +func NewStatisticsDisplay(weatherStation Subject) *StatisticsDisplay { + display := &StatisticsDisplay{ + maxTemp: 0, + minTemp: 200, // A high starting value + tempSum: 0, + numReadings: 0, + weatherStation: weatherStation, + } + weatherStation.RegisterObserver(display) + return display +} + +// Update is called when the subject's state changes +func (sd *StatisticsDisplay) Update(subject Subject) { + temp := subject.GetTemperature() + sd.tempSum += temp + sd.numReadings++ + + sd.maxTemp = math.Max(sd.maxTemp, temp) + sd.minTemp = math.Min(sd.minTemp, temp) + + sd.Display() +} + +// Display shows the temperature statistics +func (sd *StatisticsDisplay) Display() { + avgTemp := sd.tempSum / float64(sd.numReadings) + fmt.Printf("Avg/Max/Min temperature: %.1f/%.1f/%.1f\n", avgTemp, sd.maxTemp, sd.minTemp) +} + +// ForecastDisplay is a concrete observer that displays weather forecasts +type ForecastDisplay struct { + currentPressure float64 + lastPressure float64 + weatherStation Subject +} + +// NewForecastDisplay creates a new ForecastDisplay +func NewForecastDisplay(weatherStation Subject) *ForecastDisplay { + display := &ForecastDisplay{ + currentPressure: 29.92, // Starting with a default value + lastPressure: 0, + weatherStation: weatherStation, + } + weatherStation.RegisterObserver(display) + return display +} + +// Update is called when the subject's state changes +func (fd *ForecastDisplay) Update(subject Subject) { + fd.lastPressure = fd.currentPressure + fd.currentPressure = subject.GetPressure() + fd.Display() +} + +// Display shows the weather forecast +func (fd *ForecastDisplay) Display() { + fmt.Print("Forecast: ") + if fd.currentPressure > fd.lastPressure { + fmt.Println("Improving weather on the way!") + } else if fd.currentPressure == fd.lastPressure { + fmt.Println("More of the same") + } else if fd.currentPressure < fd.lastPressure { + fmt.Println("Watch out for cooler, rainy weather") + } +} + +// ChannelObserver shows an alternative implementation using Go channels +type ChannelObserver struct { + ch chan WeatherData +} + +// WeatherData represents the data sent through channels +type WeatherData struct { + Temperature float64 + Humidity float64 + Pressure float64 +} + +// NewChannelObserver creates a new channel-based observer +func NewChannelObserver() *ChannelObserver { + return &ChannelObserver{ + ch: make(chan WeatherData, 10), // Buffered channel to avoid blocking + } +} + +// Start begins listening for weather updates +func (co *ChannelObserver) Start() { + go func() { + for data := range co.ch { + fmt.Printf("\nChannel Observer: Received weather update: %.1f°C, %.1f%%, %.1f hPa\n", + data.Temperature, data.Humidity, data.Pressure) + } + }() +} + +// Stop closes the channel and stops the goroutine +func (co *ChannelObserver) Stop() { + close(co.ch) +} + +// SendUpdate sends weather data to the observer +func (co *ChannelObserver) SendUpdate(data WeatherData) { + select { + case co.ch <- data: + // Data sent successfully + default: + fmt.Println("Channel full, update dropped") + } +} + +func main() { + fmt.Println("Observer Pattern Demonstration in Go") + fmt.Println("===================================") + + // Create the weather station (subject) + weatherStation := NewWeatherStation() + + // Create and register displays (observers) + currentDisplay := NewCurrentConditionsDisplay(weatherStation) + statisticsDisplay := NewStatisticsDisplay(weatherStation) + forecastDisplay := NewForecastDisplay(weatherStation) + + // Simulate weather changes + fmt.Println("\nFirst weather update:") + weatherStation.SetMeasurements(27.5, 65.0, 30.4) + + fmt.Println("\nSecond weather update:") + weatherStation.SetMeasurements(28.2, 70.0, 29.2) + + // Remove an observer + fmt.Println("\nRemoving current conditions display...") + weatherStation.RemoveObserver(currentDisplay) + + fmt.Println("\nThird weather update (with one less observer):") + weatherStation.SetMeasurements(26.7, 90.0, 29.2) + + // Demonstrate channel-based implementation + fmt.Println("\nChannel-based Observer Demo:") + fmt.Println("---------------------------") + channelObserver := NewChannelObserver() + channelObserver.Start() + + fmt.Println("Sending updates via channel...") + channelObserver.SendUpdate(WeatherData{27.5, 65.0, 30.4}) + channelObserver.SendUpdate(WeatherData{28.2, 70.0, 29.2}) + channelObserver.SendUpdate(WeatherData{26.7, 90.0, 29.2}) + + // Allow time for the goroutine to process + fmt.Println("\nPress Enter to exit...") + fmt.Scanln() + + // Clean up + channelObserver.Stop() +} \ No newline at end of file diff --git a/snippets/design-patterns/observer/observer_pattern.js b/snippets/design-patterns/observer/observer_pattern.js new file mode 100644 index 0000000..7401a60 --- /dev/null +++ b/snippets/design-patterns/observer/observer_pattern.js @@ -0,0 +1,280 @@ +/** + * Observer Pattern Implementation in JavaScript + * + * This demonstrates a Weather Station example of the Observer pattern. + */ + +// Subject interface +class Subject { + constructor() { + this.observers = []; + } + + // Add an observer to the list + attach(observer) { + if (this.observers.includes(observer)) { + console.log('Observer already attached'); + return; + } + console.log('Attaching an observer'); + this.observers.push(observer); + } + + // Remove an observer from the list + detach(observer) { + const observerIndex = this.observers.indexOf(observer); + if (observerIndex === -1) { + console.log('Observer not found'); + return; + } + console.log('Detaching an observer'); + this.observers.splice(observerIndex, 1); + } + + // Notify all observers about an event + notify() { + console.log('Notifying observers...'); + for (const observer of this.observers) { + observer.update(this); + } + } +} + +// Concrete Subject: WeatherStation +class WeatherStation extends Subject { + constructor() { + super(); + this.temperature = 0; + this.humidity = 0; + this.pressure = 0; + } + + // Getters for weather measurements + getTemperature() { + return this.temperature; + } + + getHumidity() { + return this.humidity; + } + + getPressure() { + return this.pressure; + } + + // Update measurements and notify observers + setMeasurements(temperature, humidity, pressure) { + console.log(`Setting measurements: ${temperature}°C, ${humidity}%, ${pressure} hPa`); + this.temperature = temperature; + this.humidity = humidity; + this.pressure = pressure; + this.measurementsChanged(); + } + + measurementsChanged() { + this.notify(); + } +} + +// Observer interface +class Observer { + update(subject) { + // This method should be overridden by concrete observers + throw new Error('Method "update" must be implemented.'); + } +} + +// Concrete Observer: CurrentConditionsDisplay +class CurrentConditionsDisplay extends Observer { + constructor(weatherStation) { + super(); + this.weatherStation = weatherStation; + this.temperature = 0; + this.humidity = 0; + + // Register this observer with the subject + this.weatherStation.attach(this); + } + + update(subject) { + if (subject instanceof WeatherStation) { + this.temperature = subject.getTemperature(); + this.humidity = subject.getHumidity(); + this.display(); + } + } + + display() { + console.log(`Current conditions: ${this.temperature}°C and ${this.humidity}% humidity`); + } +} + +// Concrete Observer: StatisticsDisplay +class StatisticsDisplay extends Observer { + constructor(weatherStation) { + super(); + this.weatherStation = weatherStation; + this.maxTemp = 0; + this.minTemp = 200; + this.tempSum = 0; + this.numReadings = 0; + + // Register this observer with the subject + this.weatherStation.attach(this); + } + + update(subject) { + if (subject instanceof WeatherStation) { + const temp = subject.getTemperature(); + this.tempSum += temp; + this.numReadings++; + + if (temp > this.maxTemp) { + this.maxTemp = temp; + } + + if (temp < this.minTemp) { + this.minTemp = temp; + } + + this.display(); + } + } + + display() { + const avgTemp = this.tempSum / this.numReadings; + console.log(`Avg/Max/Min temperature: ${avgTemp.toFixed(1)}/${this.maxTemp}/${this.minTemp}`); + } +} + +// Concrete Observer: ForecastDisplay +class ForecastDisplay extends Observer { + constructor(weatherStation) { + super(); + this.weatherStation = weatherStation; + this.currentPressure = 29.92; + this.lastPressure = 0; + + // Register this observer with the subject + this.weatherStation.attach(this); + } + + update(subject) { + if (subject instanceof WeatherStation) { + this.lastPressure = this.currentPressure; + this.currentPressure = subject.getPressure(); + this.display(); + } + } + + display() { + let forecast = 'Forecast: '; + + if (this.currentPressure > this.lastPressure) { + forecast += 'Improving weather on the way!'; + } else if (this.currentPressure === this.lastPressure) { + forecast += 'More of the same'; + } else if (this.currentPressure < this.lastPressure) { + forecast += 'Watch out for cooler, rainy weather'; + } + + console.log(forecast); + } +} + +// Example usage +function runWeatherStation() { + console.log("Weather Station Demo"); + console.log("==================="); + + // Create the weather station (subject) + const weatherStation = new WeatherStation(); + + // Create displays (observers) + const currentDisplay = new CurrentConditionsDisplay(weatherStation); + const statisticsDisplay = new StatisticsDisplay(weatherStation); + const forecastDisplay = new ForecastDisplay(weatherStation); + + // Simulate weather changes + console.log("\nFirst weather update:"); + weatherStation.setMeasurements(27.5, 65, 30.4); + + console.log("\nSecond weather update:"); + weatherStation.setMeasurements(28.2, 70, 29.2); + + // Remove an observer + console.log("\nDetaching the current conditions display..."); + weatherStation.detach(currentDisplay); + + console.log("\nThird weather update (with one less observer):"); + weatherStation.setMeasurements(26.7, 90, 29.2); +} + +// Run the demo +runWeatherStation(); + +// Event-based Observer Pattern using Node.js EventEmitter +// This is a more JavaScript-idiomatic way to implement the Observer pattern +function eventBasedObserverDemo() { + const EventEmitter = require('events'); + + // Subject as an EventEmitter + class WeatherStationEmitter extends EventEmitter { + constructor() { + super(); + this.temperature = 0; + this.humidity = 0; + this.pressure = 0; + } + + setMeasurements(temperature, humidity, pressure) { + console.log(`Setting measurements: ${temperature}°C, ${humidity}%, ${pressure} hPa`); + this.temperature = temperature; + this.humidity = humidity; + this.pressure = pressure; + + // Emit an event with the updated data + this.emit('measurementsChanged', { + temperature, + humidity, + pressure + }); + } + } + + console.log("\nEvent-Based Observer Pattern Demo"); + console.log("================================="); + + const weatherStation = new WeatherStationEmitter(); + + // Add observers using event listeners + weatherStation.on('measurementsChanged', (data) => { + console.log(`Current conditions: ${data.temperature}°C and ${data.humidity}% humidity`); + }); + + let tempSum = 0; + let numReadings = 0; + let maxTemp = 0; + let minTemp = 200; + + weatherStation.on('measurementsChanged', (data) => { + tempSum += data.temperature; + numReadings++; + + if (data.temperature > maxTemp) maxTemp = data.temperature; + if (data.temperature < minTemp) minTemp = data.temperature; + + const avgTemp = tempSum / numReadings; + console.log(`Avg/Max/Min temperature: ${avgTemp.toFixed(1)}/${maxTemp}/${minTemp}`); + }); + + // Simulate weather changes + console.log("\nFirst weather update:"); + weatherStation.setMeasurements(27.5, 65, 30.4); + + console.log("\nSecond weather update:"); + weatherStation.setMeasurements(28.2, 70, 29.2); +} + +// Run the event-based demo if running in Node.js environment +eventBasedObserverDemo(); \ No newline at end of file diff --git a/snippets/design-patterns/observer/observer_pattern.php b/snippets/design-patterns/observer/observer_pattern.php new file mode 100644 index 0000000..e416753 --- /dev/null +++ b/snippets/design-patterns/observer/observer_pattern.php @@ -0,0 +1,474 @@ +observers[] = $observer; + echo "Observer " . get_class($observer) . " registered\n"; + } + + /** + * Remove an observer from the notification list + * + * @param Observer $observer The observer to remove + * @return void + */ + public function removeObserver(Observer $observer): void { + $index = array_search($observer, $this->observers, true); + if ($index !== false) { + unset($this->observers[$index]); + $this->observers = array_values($this->observers); // Re-index array + echo "Observer " . get_class($observer) . " removed\n"; + } + } + + /** + * Notify all registered observers of state changes + * + * @return void + */ + public function notifyObservers(): void { + foreach ($this->observers as $observer) { + $observer->update($this->temperature, $this->humidity, $this->pressure); + } + } + + /** + * Called when measurements have been updated + * + * @return void + */ + public function measurementsChanged(): void { + $this->notifyObservers(); + } + + /** + * Set new weather measurements + * + * @param float $temperature New temperature value + * @param float $humidity New humidity value + * @param float $pressure New pressure value + * @return void + */ + public function setMeasurements(float $temperature, float $humidity, float $pressure): void { + $this->temperature = $temperature; + $this->humidity = $humidity; + $this->pressure = $pressure; + $this->measurementsChanged(); + } + + /** + * Get the current temperature + * + * @return float Current temperature + */ + public function getTemperature(): float { + return $this->temperature; + } + + /** + * Get the current humidity + * + * @return float Current humidity + */ + public function getHumidity(): float { + return $this->humidity; + } + + /** + * Get the current pressure + * + * @return float Current pressure + */ + public function getPressure(): float { + return $this->pressure; + } +} + +// ========== Display Implementations ========== + +/** + * CurrentConditionsDisplay class implements the Observer interface + * Displays the current weather conditions + */ +class CurrentConditionsDisplay implements Observer { + /** + * @var float Current temperature + */ + private $temperature; + + /** + * @var float Current humidity + */ + private $humidity; + + /** + * @var Subject The weather data subject + */ + private $weatherData; + + /** + * Constructor + * + * @param Subject $weatherData The weather data subject + */ + public function __construct(Subject $weatherData) { + $this->weatherData = $weatherData; + $weatherData->registerObserver($this); + } + + /** + * Update method called by the subject when state changes + * + * @param float $temperature The current temperature + * @param float $humidity The current humidity + * @param float $pressure The current pressure + * @return void + */ + public function update(float $temperature, float $humidity, float $pressure): void { + $this->temperature = $temperature; + $this->humidity = $humidity; + $this->display(); + } + + /** + * Display the current conditions + * + * @return void + */ + public function display(): void { + echo "Current conditions: {$this->temperature}°F and {$this->humidity}% humidity\n"; + } +} + +/** + * StatisticsDisplay class implements the Observer interface + * Displays average, minimum, and maximum temperatures + */ +class StatisticsDisplay implements Observer { + /** + * @var float Maximum temperature recorded + */ + private $maxTemp = 0.0; + + /** + * @var float Minimum temperature recorded + */ + private $minTemp = 200.0; + + /** + * @var float Sum of all temperature readings + */ + private $tempSum = 0.0; + + /** + * @var int Number of readings taken + */ + private $numReadings = 0; + + /** + * @var Subject The weather data subject + */ + private $weatherData; + + /** + * Constructor + * + * @param Subject $weatherData The weather data subject + */ + public function __construct(Subject $weatherData) { + $this->weatherData = $weatherData; + $weatherData->registerObserver($this); + } + + /** + * Update method called by the subject when state changes + * + * @param float $temperature The current temperature + * @param float $humidity The current humidity + * @param float $pressure The current pressure + * @return void + */ + public function update(float $temperature, float $humidity, float $pressure): void { + $this->tempSum += $temperature; + $this->numReadings++; + + if ($temperature > $this->maxTemp) { + $this->maxTemp = $temperature; + } + + if ($temperature < $this->minTemp) { + $this->minTemp = $temperature; + } + + $this->display(); + } + + /** + * Display the statistics + * + * @return void + */ + public function display(): void { + $avgTemp = $this->tempSum / $this->numReadings; + echo "Avg/Max/Min temperature: " . round($avgTemp, 1) . "/" . + $this->maxTemp . "/" . $this->minTemp . "\n"; + } +} + +/** + * ForecastDisplay class implements the Observer interface + * Displays a simple weather forecast based on pressure changes + */ +class ForecastDisplay implements Observer { + /** + * @var float Current barometric pressure + */ + private $currentPressure = 29.92; + + /** + * @var float Previous barometric pressure + */ + private $lastPressure; + + /** + * @var Subject The weather data subject + */ + private $weatherData; + + /** + * Constructor + * + * @param Subject $weatherData The weather data subject + */ + public function __construct(Subject $weatherData) { + $this->weatherData = $weatherData; + $weatherData->registerObserver($this); + } + + /** + * Update method called by the subject when state changes + * + * @param float $temperature The current temperature + * @param float $humidity The current humidity + * @param float $pressure The current pressure + * @return void + */ + public function update(float $temperature, float $humidity, float $pressure): void { + $this->lastPressure = $this->currentPressure; + $this->currentPressure = $pressure; + $this->display(); + } + + /** + * Display the forecast + * + * @return void + */ + public function display(): void { + echo "Forecast: "; + if ($this->currentPressure > $this->lastPressure) { + echo "Improving weather on the way!\n"; + } else if ($this->currentPressure == $this->lastPressure) { + echo "More of the same\n"; + } else if ($this->currentPressure < $this->lastPressure) { + echo "Watch out for cooler, rainy weather\n"; + } + } +} + +/** + * HeatIndexDisplay class implements the Observer interface + * Displays the heat index based on temperature and humidity + */ +class HeatIndexDisplay implements Observer { + /** + * @var float Current heat index + */ + private $heatIndex = 0.0; + + /** + * @var Subject The weather data subject + */ + private $weatherData; + + /** + * Constructor + * + * @param Subject $weatherData The weather data subject + */ + public function __construct(Subject $weatherData) { + $this->weatherData = $weatherData; + $weatherData->registerObserver($this); + } + + /** + * Update method called by the subject when state changes + * + * @param float $temperature The current temperature + * @param float $humidity The current humidity + * @param float $pressure The current pressure + * @return void + */ + public function update(float $temperature, float $humidity, float $pressure): void { + $this->heatIndex = $this->computeHeatIndex($temperature, $humidity); + $this->display(); + } + + /** + * Compute the heat index based on temperature and humidity + * + * @param float $t Temperature + * @param float $rh Relative humidity + * @return float Heat index + */ + private function computeHeatIndex(float $t, float $rh): float { + // This is a simplified formula for heat index + return (float)((16.923 + (0.185212 * $t) + (5.37941 * $rh) - (0.100254 * $t * $rh) + + (0.00941695 * ($t * $t)) + (0.00728898 * ($rh * $rh)) + + (0.000345372 * ($t * $t * $rh)) - (0.000814971 * ($t * $rh * $rh)) + + (0.0000102102 * ($t * $t * $rh * $rh)) - (0.000038646 * ($t * $t * $t)) + + (0.0000291583 * ($rh * $rh * $rh)) + (0.00000142721 * ($t * $t * $t * $rh)) + + (0.000000197483 * ($t * $rh * $rh * $rh)) - (0.0000000218429 * ($t * $t * $t * $rh * $rh)) + + 0.000000000843296 * ($t * $t * $rh * $rh * $rh)) - + (0.0000000000481975 * ($t * $t * $t * $rh * $rh * $rh))); + } + + /** + * Display the heat index + * + * @return void + */ + public function display(): void { + echo "Heat index: " . round($this->heatIndex, 1) . "\n"; + } +} + +// ========== Demo Code ========== + +/** + * Run the weather station demo + */ +function runWeatherStation() { + // Create the WeatherData subject + $weatherData = new WeatherData(); + + // Create display devices (observers) + $currentDisplay = new CurrentConditionsDisplay($weatherData); + $statisticsDisplay = new StatisticsDisplay($weatherData); + $forecastDisplay = new ForecastDisplay($weatherData); + $heatIndexDisplay = new HeatIndexDisplay($weatherData); + + echo "\n=== First Weather Update ===\n"; + // Simulate new weather measurements + $weatherData->setMeasurements(80, 65, 30.4); + + echo "\n=== Second Weather Update ===\n"; + // Simulate new weather measurements + $weatherData->setMeasurements(82, 70, 29.2); + + echo "\n=== Third Weather Update ===\n"; + // Simulate new weather measurements + $weatherData->setMeasurements(78, 90, 29.2); + + echo "\n=== Removing an Observer ===\n"; + // Remove an observer + $weatherData->removeObserver($forecastDisplay); + + echo "\n=== Fourth Weather Update ===\n"; + // One more measurement after removing an observer + $weatherData->setMeasurements(75, 60, 30.1); +} + +// Run the demo +runWeatherStation(); diff --git a/snippets/design-patterns/observer/observer_pattern.py b/snippets/design-patterns/observer/observer_pattern.py new file mode 100644 index 0000000..af3e7d4 --- /dev/null +++ b/snippets/design-patterns/observer/observer_pattern.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +""" +Observer Pattern Implementation in Python + +This example demonstrates a news publisher system where subscribers +can register to receive updates when new articles are published. +""" + +from abc import ABC, abstractmethod +from typing import List + + +# Observer interface +class Observer(ABC): + """ + The Observer interface declares the update method, used by subjects + to notify observers of changes. + """ + @abstractmethod + def update(self, subject) -> None: + """ + Receive update from subject. + """ + pass + + +# Subject interface +class Subject(ABC): + """ + The Subject interface declares methods for managing observers. + """ + @abstractmethod + def attach(self, observer: Observer) -> None: + """ + Attach an observer to the subject. + """ + pass + + @abstractmethod + def detach(self, observer: Observer) -> None: + """ + Detach an observer from the subject. + """ + pass + + @abstractmethod + def notify(self) -> None: + """ + Notify all observers about an event. + """ + pass + + +# Concrete Subject +class NewsPublisher(Subject): + """ + The NewsPublisher maintains a list of observers and sends notifications + when a new article is published. + """ + def __init__(self): + self._observers: List[Observer] = [] + self._latest_article: dict = {} + self._articles: List[dict] = [] + + def attach(self, observer: Observer) -> None: + print(f"NewsPublisher: Attached an observer.") + self._observers.append(observer) + + def detach(self, observer: Observer) -> None: + print(f"NewsPublisher: Detached an observer.") + self._observers.remove(observer) + + def notify(self) -> None: + """ + Trigger an update in each subscriber. + """ + print("NewsPublisher: Notifying observers...") + for observer in self._observers: + observer.update(self) + + def publish_article(self, title: str, content: str, category: str) -> None: + """ + Publishes a new article and notifies observers. + """ + article = { + "title": title, + "content": content, + "category": category + } + self._latest_article = article + self._articles.append(article) + print(f"NewsPublisher: Published new article - '{title}'") + self.notify() + + @property + def latest_article(self) -> dict: + """ + Returns the latest published article. + """ + return self._latest_article + + @property + def articles(self) -> List[dict]: + """ + Returns all articles. + """ + return self._articles + + +# Concrete Observer +class NewsSubscriber(Observer): + """ + Concrete Observers react to the updates issued by the NewsPublisher + they are attached to. + """ + def __init__(self, name: str, categories: List[str] = None): + self.name = name + self.categories = categories or [] # Categories the subscriber is interested in + + def update(self, subject: Subject) -> None: + """ + Receive update from publisher and print the latest article + if it matches the subscriber's interests. + """ + if not isinstance(subject, NewsPublisher): + return + + latest = subject.latest_article + + # If subscriber has category preferences and the article category matches + if not self.categories or latest.get("category") in self.categories: + print(f"\n{self.name} received news alert:") + print(f"Title: {latest.get('title')}") + print(f"Category: {latest.get('category')}") + print(f"Content snippet: {latest.get('content')[:50]}...") + else: + print(f"\n{self.name} ignored article in category '{latest.get('category')}' (not subscribed)") + + def subscribe_to_category(self, category: str) -> None: + """ + Add a category to subscriber's interests. + """ + if category not in self.categories: + self.categories.append(category) + print(f"{self.name} subscribed to category: {category}") + + def unsubscribe_from_category(self, category: str) -> None: + """ + Remove a category from subscriber's interests. + """ + if category in self.categories: + self.categories.remove(category) + print(f"{self.name} unsubscribed from category: {category}") + + +# Specialized Concrete Observer +class PremiumSubscriber(NewsSubscriber): + """ + A premium subscriber gets more detailed notifications. + """ + def update(self, subject: Subject) -> None: + """ + Premium subscribers receive full content regardless of category. + """ + if not isinstance(subject, NewsPublisher): + return + + latest = subject.latest_article + print(f"\n[PREMIUM] {self.name} received breaking news:") + print(f"Title: {latest.get('title')}") + print(f"Category: {latest.get('category')}") + print(f"Full content: {latest.get('content')}") + + +def main(): + """ + The client code. + """ + # Create publisher + news_publisher = NewsPublisher() + + # Create subscribers + john = NewsSubscriber("John", ["politics", "technology"]) + lisa = NewsSubscriber("Lisa", ["entertainment", "sports"]) + mike = PremiumSubscriber("Mike") # Premium subscriber gets all categories + + # Attach subscribers to publisher + news_publisher.attach(john) + news_publisher.attach(lisa) + news_publisher.attach(mike) + + # Publish articles + print("\n--- First Article ---") + news_publisher.publish_article( + "New AI Breakthrough", + "Researchers have developed a new AI model that can understand complex human emotions.", + "technology" + ) + + print("\n--- Second Article ---") + news_publisher.publish_article( + "Movie Star Announces Retirement", + "Famous actor decides to retire after 30 years in the industry.", + "entertainment" + ) + + # Detach a subscriber + news_publisher.detach(lisa) + + # John subscribes to a new category + john.subscribe_to_category("health") + + print("\n--- Third Article ---") + news_publisher.publish_article( + "New Health Guidelines Released", + "Government announces updated health guidelines for the pandemic.", + "health" + ) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/snippets/design-patterns/observer/observer_pattern.rb b/snippets/design-patterns/observer/observer_pattern.rb new file mode 100644 index 0000000..c6aa78d --- /dev/null +++ b/snippets/design-patterns/observer/observer_pattern.rb @@ -0,0 +1,244 @@ +#!/usr/bin/env ruby + +# Observer Pattern Implementation in Ruby +# +# The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency +# between objects so that when one object changes state, all its dependents are notified +# and updated automatically. +# +# This example demonstrates a simple weather station (subject) that notifies +# multiple display devices (observers) when weather data changes. + +# ========== Subject Module ========== + +# Subject module to be included by classes that need to notify observers +module Subject + # Initialize the observers array + def initialize_subject + @observers = [] + end + + # Register an observer to be notified of changes + def register_observer(observer) + @observers << observer + puts "Observer #{observer.class} registered" + end + + # Remove an observer from the notification list + def remove_observer(observer) + if @observers.delete(observer) + puts "Observer #{observer.class} removed" + end + end + + # Notify all registered observers of state changes + def notify_observers + @observers.each do |observer| + observer.update(@temperature, @humidity, @pressure) + end + end +end + +# ========== Observer Module ========== + +# Observer module to be included by classes that need to be notified of changes +module Observer + # Update method to be implemented by concrete observers + def update(temperature, humidity, pressure) + raise NotImplementedError, "#{self.class} must implement update method" + end +end + +# ========== Weather Data Implementation ========== + +# WeatherData class includes the Subject module +class WeatherData + include Subject + + attr_reader :temperature, :humidity, :pressure + + # Initialize the weather data + def initialize + initialize_subject + @temperature = 0.0 + @humidity = 0.0 + @pressure = 0.0 + end + + # Called when measurements have been updated + def measurements_changed + notify_observers + end + + # Set new weather measurements + def set_measurements(temperature, humidity, pressure) + @temperature = temperature + @humidity = humidity + @pressure = pressure + measurements_changed + end +end + +# ========== Display Implementations ========== + +# CurrentConditionsDisplay class includes the Observer module +class CurrentConditionsDisplay + include Observer + + # Initialize with weather data subject + def initialize(weather_data) + @weather_data = weather_data + @temperature = 0.0 + @humidity = 0.0 + weather_data.register_observer(self) + end + + # Update method implementation + def update(temperature, humidity, pressure) + @temperature = temperature + @humidity = humidity + display + end + + # Display the current conditions + def display + puts "Current conditions: #{@temperature}°F and #{@humidity}% humidity" + end +end + +# StatisticsDisplay class includes the Observer module +class StatisticsDisplay + include Observer + + # Initialize with weather data subject + def initialize(weather_data) + @weather_data = weather_data + @max_temp = 0.0 + @min_temp = 200.0 + @temp_sum = 0.0 + @num_readings = 0 + weather_data.register_observer(self) + end + + # Update method implementation + def update(temperature, humidity, pressure) + @temp_sum += temperature + @num_readings += 1 + + @max_temp = temperature if temperature > @max_temp + @min_temp = temperature if temperature < @min_temp + + display + end + + # Display the statistics + def display + avg_temp = @temp_sum / @num_readings + puts "Avg/Max/Min temperature: #{avg_temp.round(1)}/#{@max_temp}/#{@min_temp}" + end +end + +# ForecastDisplay class includes the Observer module +class ForecastDisplay + include Observer + + # Initialize with weather data subject + def initialize(weather_data) + @weather_data = weather_data + @current_pressure = 29.92 + @last_pressure = 0.0 + weather_data.register_observer(self) + end + + # Update method implementation + def update(temperature, humidity, pressure) + @last_pressure = @current_pressure + @current_pressure = pressure + display + end + + # Display the forecast + def display + print "Forecast: " + if @current_pressure > @last_pressure + puts "Improving weather on the way!" + elsif @current_pressure == @last_pressure + puts "More of the same" + elsif @current_pressure < @last_pressure + puts "Watch out for cooler, rainy weather" + end + end +end + +# HeatIndexDisplay class includes the Observer module +class HeatIndexDisplay + include Observer + + # Initialize with weather data subject + def initialize(weather_data) + @weather_data = weather_data + @heat_index = 0.0 + weather_data.register_observer(self) + end + + # Update method implementation + def update(temperature, humidity, pressure) + @heat_index = compute_heat_index(temperature, humidity) + display + end + + # Compute the heat index based on temperature and humidity + def compute_heat_index(t, rh) + # This is a simplified formula for heat index + (16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) + + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + + 0.000000000843296 * (t * t * rh * rh * rh)) - + (0.0000000000481975 * (t * t * t * rh * rh * rh)) + end + + # Display the heat index + def display + puts "Heat index: #{@heat_index.round(1)}" + end +end + +# ========== Demo Code ========== + +# Run the weather station demo +def run_weather_station + # Create the WeatherData subject + weather_data = WeatherData.new + + # Create display devices (observers) + current_display = CurrentConditionsDisplay.new(weather_data) + statistics_display = StatisticsDisplay.new(weather_data) + forecast_display = ForecastDisplay.new(weather_data) + heat_index_display = HeatIndexDisplay.new(weather_data) + + puts "\n=== First Weather Update ===" + # Simulate new weather measurements + weather_data.set_measurements(80, 65, 30.4) + + puts "\n=== Second Weather Update ===" + # Simulate new weather measurements + weather_data.set_measurements(82, 70, 29.2) + + puts "\n=== Third Weather Update ===" + # Simulate new weather measurements + weather_data.set_measurements(78, 90, 29.2) + + puts "\n=== Removing an Observer ===" + # Remove an observer + weather_data.remove_observer(forecast_display) + + puts "\n=== Fourth Weather Update ===" + # One more measurement after removing an observer + weather_data.set_measurements(75, 60, 30.1) +end + +# Run the demo +run_weather_station diff --git a/snippets/design-patterns/observer/observer_pattern.rs b/snippets/design-patterns/observer/observer_pattern.rs new file mode 100644 index 0000000..8870d12 --- /dev/null +++ b/snippets/design-patterns/observer/observer_pattern.rs @@ -0,0 +1,338 @@ +/** + * Observer Pattern Implementation in Rust + * + * The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency + * between objects so that when one object changes state, all its dependents are notified + * and updated automatically. + * + * This example demonstrates a simple weather station (subject) that notifies + * multiple display devices (observers) when weather data changes. + */ + +use std::cell::RefCell; +use std::rc::{Rc, Weak}; +use std::fmt; + +// ========== Observer Trait ========== + +/// Observer trait to be implemented by all display devices +trait Observer { + /// Update method called by the subject when state changes + fn update(&mut self, temperature: f32, humidity: f32, pressure: f32); + + /// Get the name of the observer for identification + fn name(&self) -> &str; +} + +// ========== Subject Trait ========== + +/// Subject trait to be implemented by objects that notify observers +trait Subject { + /// Register an observer to be notified of changes + fn register_observer(&mut self, observer: Rc>); + + /// Remove an observer from the notification list + fn remove_observer(&mut self, observer: &Rc>); + + /// Notify all registered observers of state changes + fn notify_observers(&self); +} + +// ========== Weather Data Implementation ========== + +/// WeatherData struct implements the Subject trait +struct WeatherData { + observers: Vec>>, + temperature: f32, + humidity: f32, + pressure: f32, +} + +impl WeatherData { + /// Create a new WeatherData instance + fn new() -> Self { + WeatherData { + observers: Vec::new(), + temperature: 0.0, + humidity: 0.0, + pressure: 0.0, + } + } + + /// Called when measurements have been updated + fn measurements_changed(&self) { + self.notify_observers(); + } + + /// Set new weather measurements + fn set_measurements(&mut self, temperature: f32, humidity: f32, pressure: f32) { + self.temperature = temperature; + self.humidity = humidity; + self.pressure = pressure; + self.measurements_changed(); + } +} + +impl Subject for WeatherData { + fn register_observer(&mut self, observer: Rc>) { + let observer_name = observer.borrow().name().to_string(); + self.observers.push(Rc::downgrade(&observer)); + println!("Observer '{}' registered", observer_name); + } + + fn remove_observer(&mut self, observer_to_remove: &Rc>) { + let observer_name = observer_to_remove.borrow().name().to_string(); + let initial_count = self.observers.len(); + + // Filter out the observer to remove and any weak references that can't be upgraded + self.observers.retain(|weak_observer| { + if let Some(observer) = weak_observer.upgrade() { + // Keep if it's not the one we want to remove + !Rc::ptr_eq(&observer, observer_to_remove) + } else { + // Remove if the weak reference can't be upgraded + false + } + }); + + if self.observers.len() < initial_count { + println!("Observer '{}' removed", observer_name); + } + } + + fn notify_observers(&self) { + // Create a new vector to hold valid observers + let mut valid_observers = Vec::new(); + + // Process each observer + for weak_observer in &self.observers { + if let Some(observer) = weak_observer.upgrade() { + // Notify the observer + observer.borrow_mut().update(self.temperature, self.humidity, self.pressure); + // Keep this observer + valid_observers.push(Weak::clone(weak_observer)); + } + } + } +} + +// ========== Display Implementations ========== + +/// CurrentConditionsDisplay implements the Observer trait +struct CurrentConditionsDisplay { + name: String, + temperature: f32, + humidity: f32, +} + +impl CurrentConditionsDisplay { + /// Create a new CurrentConditionsDisplay instance + fn new(name: &str) -> Self { + CurrentConditionsDisplay { + name: name.to_string(), + temperature: 0.0, + humidity: 0.0, + } + } + + /// Display the current conditions + fn display(&self) { + println!("[{}] Current conditions: {:.1}°F and {:.1}% humidity", + self.name, self.temperature, self.humidity); + } +} + +impl Observer for CurrentConditionsDisplay { + fn update(&mut self, temperature: f32, humidity: f32, _pressure: f32) { + self.temperature = temperature; + self.humidity = humidity; + self.display(); + } + + fn name(&self) -> &str { + &self.name + } +} + +/// StatisticsDisplay implements the Observer trait +struct StatisticsDisplay { + name: String, + max_temp: f32, + min_temp: f32, + temp_sum: f32, + num_readings: u32, +} + +impl StatisticsDisplay { + /// Create a new StatisticsDisplay instance + fn new(name: &str) -> Self { + StatisticsDisplay { + name: name.to_string(), + max_temp: 0.0, + min_temp: 200.0, // Start with a high value + temp_sum: 0.0, + num_readings: 0, + } + } + + /// Display the statistics + fn display(&self) { + let avg_temp = self.temp_sum / self.num_readings as f32; + println!("[{}] Avg/Max/Min temperature: {:.1}/{:.1}/{:.1}", + self.name, avg_temp, self.max_temp, self.min_temp); + } +} + +impl Observer for StatisticsDisplay { + fn update(&mut self, temperature: f32, _humidity: f32, _pressure: f32) { + self.temp_sum += temperature; + self.num_readings += 1; + + if temperature > self.max_temp { + self.max_temp = temperature; + } + + if temperature < self.min_temp { + self.min_temp = temperature; + } + + self.display(); + } + + fn name(&self) -> &str { + &self.name + } +} + +/// ForecastDisplay implements the Observer trait +struct ForecastDisplay { + name: String, + current_pressure: f32, + last_pressure: f32, +} + +impl ForecastDisplay { + /// Create a new ForecastDisplay instance + fn new(name: &str) -> Self { + ForecastDisplay { + name: name.to_string(), + current_pressure: 29.92, // Default starting pressure + last_pressure: 0.0, + } + } + + /// Display the forecast + fn display(&self) { + print!("[{}] Forecast: ", self.name); + + if self.current_pressure > self.last_pressure { + println!("Improving weather on the way!"); + } else if self.current_pressure == self.last_pressure { + println!("More of the same"); + } else { + println!("Watch out for cooler, rainy weather"); + } + } +} + +impl Observer for ForecastDisplay { + fn update(&mut self, _temperature: f32, _humidity: f32, pressure: f32) { + self.last_pressure = self.current_pressure; + self.current_pressure = pressure; + self.display(); + } + + fn name(&self) -> &str { + &self.name + } +} + +/// HeatIndexDisplay implements the Observer trait +struct HeatIndexDisplay { + name: String, + heat_index: f32, +} + +impl HeatIndexDisplay { + /// Create a new HeatIndexDisplay instance + fn new(name: &str) -> Self { + HeatIndexDisplay { + name: name.to_string(), + heat_index: 0.0, + } + } + + /// Compute the heat index based on temperature and humidity + fn compute_heat_index(t: f32, rh: f32) -> f32 { + // This is a simplified formula for heat index + (16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) + + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) + + (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) + + 0.000000000843296 * (t * t * rh * rh * rh)) - + (0.0000000000481975 * (t * t * t * rh * rh * rh)) + } + + /// Display the heat index + fn display(&self) { + println!("[{}] Heat index: {:.1}", self.name, self.heat_index); + } +} + +impl Observer for HeatIndexDisplay { + fn update(&mut self, temperature: f32, humidity: f32, _pressure: f32) { + self.heat_index = Self::compute_heat_index(temperature, humidity); + self.display(); + } + + fn name(&self) -> &str { + &self.name + } +} + +// ========== Demo Code ========== + +/// Run the weather station demo +fn run_weather_station() { + // Create the WeatherData subject + let mut weather_data = WeatherData::new(); + + // Create display devices (observers) + let current_display = Rc::new(RefCell::new(CurrentConditionsDisplay::new("Current Display"))); + let stats_display = Rc::new(RefCell::new(StatisticsDisplay::new("Statistics Display"))); + let forecast_display = Rc::new(RefCell::new(ForecastDisplay::new("Forecast Display"))); + let heat_index_display = Rc::new(RefCell::new(HeatIndexDisplay::new("Heat Index Display"))); + + // Register observers + weather_data.register_observer(Rc::clone(¤t_display)); + weather_data.register_observer(Rc::clone(&stats_display)); + weather_data.register_observer(Rc::clone(&forecast_display)); + weather_data.register_observer(Rc::clone(&heat_index_display)); + + println!("\n=== First Weather Update ==="); + // Simulate new weather measurements + weather_data.set_measurements(80.0, 65.0, 30.4); + + println!("\n=== Second Weather Update ==="); + // Simulate new weather measurements + weather_data.set_measurements(82.0, 70.0, 29.2); + + println!("\n=== Third Weather Update ==="); + // Simulate new weather measurements + weather_data.set_measurements(78.0, 90.0, 29.2); + + println!("\n=== Removing an Observer ==="); + // Remove an observer + weather_data.remove_observer(&forecast_display); + + println!("\n=== Fourth Weather Update ==="); + // One more measurement after removing an observer + weather_data.set_measurements(75.0, 60.0, 30.1); +} + +fn main() { + // Run the demo + run_weather_station(); +} diff --git a/snippets/design-patterns/singleton/SingletonPattern.cpp b/snippets/design-patterns/singleton/SingletonPattern.cpp new file mode 100644 index 0000000..e29695a --- /dev/null +++ b/snippets/design-patterns/singleton/SingletonPattern.cpp @@ -0,0 +1,369 @@ +/** + * Singleton Pattern Implementation in C++ + * + * The Singleton Pattern is a creational design pattern that ensures a class has only one instance + * and provides a global point of access to it. This is useful when exactly one object is needed + * to coordinate actions across the system. + * + * This file demonstrates several ways to implement the Singleton pattern in C++. + */ + +#include +#include +#include +#include +#include +#include +#include + +// ========== Classic Singleton Implementation ========== + +/** + * Classic Singleton implementation with lazy initialization + */ +class ClassicSingleton { +private: + // Private constructor to prevent direct instantiation + ClassicSingleton() { + timestamp_ = std::chrono::system_clock::now(); + std::cout << "ClassicSingleton instance created." << std::endl; + } + + // Delete copy constructor and assignment operator + ClassicSingleton(const ClassicSingleton&) = delete; + ClassicSingleton& operator=(const ClassicSingleton&) = delete; + + // Static instance pointer + static ClassicSingleton* instance_; + + // Member variables + std::chrono::system_clock::time_point timestamp_; + std::map config_ = { + {"api_url", "https://api.example.com"}, + {"timeout", "3000"}, + {"retries", "3"} + }; + +public: + // Static method to get the singleton instance + static ClassicSingleton* getInstance() { + if (instance_ == nullptr) { + instance_ = new ClassicSingleton(); + } + return instance_; + } + + // Clean up the singleton instance + static void destroyInstance() { + delete instance_; + instance_ = nullptr; + } + + // Get configuration + std::map getConfig() const { + return config_; + } + + // Update configuration + void updateConfig(const std::string& key, const std::string& value) { + config_[key] = value; + std::cout << "Configuration updated: " << key << " = " << value << std::endl; + } + + // Get creation timestamp + std::chrono::system_clock::time_point getTimestamp() const { + return timestamp_; + } +}; + +// Initialize the static instance pointer +ClassicSingleton* ClassicSingleton::instance_ = nullptr; + +// ========== Thread-Safe Singleton Implementation ========== + +/** + * Thread-safe Singleton implementation with double-checked locking + */ +class ThreadSafeSingleton { +private: + // Private constructor to prevent direct instantiation + ThreadSafeSingleton() { + std::cout << "ThreadSafeSingleton instance created." << std::endl; + } + + // Delete copy constructor and assignment operator + ThreadSafeSingleton(const ThreadSafeSingleton&) = delete; + ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete; + + // Static instance pointer + static ThreadSafeSingleton* instance_; + static std::mutex mutex_; + + // Member variables + std::vector logs_; + +public: + // Static method to get the singleton instance (thread-safe) + static ThreadSafeSingleton* getInstance() { + if (instance_ == nullptr) { + std::lock_guard lock(mutex_); + if (instance_ == nullptr) { + instance_ = new ThreadSafeSingleton(); + } + } + return instance_; + } + + // Clean up the singleton instance + static void destroyInstance() { + std::lock_guard lock(mutex_); + delete instance_; + instance_ = nullptr; + } + + // Log a message + void log(const std::string& message) { + std::lock_guard lock(mutex_); + auto now = std::chrono::system_clock::now(); + auto now_c = std::chrono::system_clock::to_time_t(now); + std::string timestamp = std::ctime(&now_c); + timestamp.pop_back(); // Remove trailing newline + + std::string logEntry = timestamp + ": " + message; + logs_.push_back(logEntry); + std::cout << logEntry << std::endl; + } + + // Get all logs + std::vector getLogs() const { + std::lock_guard lock(mutex_); + return logs_; + } + + // Clear logs + void clearLogs() { + std::lock_guard lock(mutex_); + logs_.clear(); + std::cout << "Logs cleared." << std::endl; + } +}; + +// Initialize static members +ThreadSafeSingleton* ThreadSafeSingleton::instance_ = nullptr; +std::mutex ThreadSafeSingleton::mutex_; + +// ========== Modern C++ Singleton Implementation (Meyers Singleton) ========== + +/** + * Modern C++ Singleton implementation using static local variable (Scott Meyers Singleton) + * This is thread-safe in C++11 and later + */ +class MeyersSingleton { +private: + // Private constructor to prevent direct instantiation + MeyersSingleton() { + std::cout << "MeyersSingleton instance created." << std::endl; + } + + // Delete copy constructor and assignment operator + MeyersSingleton(const MeyersSingleton&) = delete; + MeyersSingleton& operator=(const MeyersSingleton&) = delete; + + // Member variables + struct DatabaseConnection { + bool connected = false; + std::string connectionString; + int connectionCount = 0; + }; + + DatabaseConnection db_; + +public: + // Static method to get the singleton instance + static MeyersSingleton& getInstance() { + // Static local variable is initialized only once in a thread-safe way + static MeyersSingleton instance; + return instance; + } + + // Connect to database + bool connect(const std::string& connectionString) { + if (db_.connected) { + db_.connectionCount++; + std::cout << "Already connected to database. Connection count: " << db_.connectionCount << std::endl; + return true; + } + + // Simulate connection + db_.connectionString = connectionString; + db_.connected = true; + db_.connectionCount = 1; + std::cout << "Connected to database: " << connectionString << std::endl; + return true; + } + + // Disconnect from database + bool disconnect() { + if (!db_.connected) { + std::cout << "Not connected to any database." << std::endl; + return false; + } + + db_.connectionCount--; + if (db_.connectionCount == 0) { + db_.connected = false; + std::cout << "Disconnected from database: " << db_.connectionString << std::endl; + } else { + std::cout << "Connection count decreased. Remaining connections: " << db_.connectionCount << std::endl; + } + + return true; + } + + // Get connection status + bool isConnected() const { + return db_.connected; + } + + // Get connection count + int getConnectionCount() const { + return db_.connectionCount; + } +}; + +// ========== Singleton with Shared Pointer ========== + +/** + * Singleton implementation using std::shared_ptr for automatic memory management + */ +class SharedPtrSingleton { +private: + // Private constructor to prevent direct instantiation + SharedPtrSingleton() { + std::cout << "SharedPtrSingleton instance created." << std::endl; + } + + // Delete copy constructor and assignment operator + SharedPtrSingleton(const SharedPtrSingleton&) = delete; + SharedPtrSingleton& operator=(const SharedPtrSingleton&) = delete; + + // Static instance pointer + static std::shared_ptr instance_; + static std::mutex mutex_; + + // Member variables + std::map featureFlags_ = { + {"dark_mode", false}, + {"beta_features", false}, + {"analytics", true}, + {"notifications", true} + }; + +public: + // Static method to get the singleton instance + static std::shared_ptr getInstance() { + std::lock_guard lock(mutex_); + if (!instance_) { + instance_ = std::shared_ptr(new SharedPtrSingleton()); + } + return instance_; + } + + // Enable a feature + void enableFeature(const std::string& featureName) { + featureFlags_[featureName] = true; + std::cout << "Feature enabled: " << featureName << std::endl; + } + + // Disable a feature + void disableFeature(const std::string& featureName) { + featureFlags_[featureName] = false; + std::cout << "Feature disabled: " << featureName << std::endl; + } + + // Check if a feature is enabled + bool isFeatureEnabled(const std::string& featureName) const { + auto it = featureFlags_.find(featureName); + if (it != featureFlags_.end()) { + return it->second; + } + return false; + } + + // Get all feature flags + std::map getAllFeatureFlags() const { + return featureFlags_; + } +}; + +// Initialize static members +std::shared_ptr SharedPtrSingleton::instance_ = nullptr; +std::mutex SharedPtrSingleton::mutex_; + +// ========== Demo Code ========== + +void demonstrateSingletons() { + std::cout << "===== Classic Singleton Demo =====" << std::endl; + ClassicSingleton* singleton1 = ClassicSingleton::getInstance(); + ClassicSingleton* singleton2 = ClassicSingleton::getInstance(); + + std::cout << "Are instances the same? " << (singleton1 == singleton2 ? "Yes" : "No") << std::endl; + + auto config = singleton1->getConfig(); + std::cout << "Original config: api_url = " << config["api_url"] << std::endl; + + singleton2->updateConfig("timeout", "5000"); + config = singleton1->getConfig(); + std::cout << "Updated config from singleton1: timeout = " << config["timeout"] << std::endl; + + std::cout << "\n===== Thread-Safe Singleton Demo =====" << std::endl; + ThreadSafeSingleton* logger1 = ThreadSafeSingleton::getInstance(); + ThreadSafeSingleton* logger2 = ThreadSafeSingleton::getInstance(); + + std::cout << "Are instances the same? " << (logger1 == logger2 ? "Yes" : "No") << std::endl; + + logger1->log("Application started"); + logger1->log("Processing data"); + logger2->log("Operation completed"); + + auto logs = logger1->getLogs(); + std::cout << "Log count: " << logs.size() << std::endl; + + std::cout << "\n===== Meyers Singleton Demo =====" << std::endl; + MeyersSingleton& db1 = MeyersSingleton::getInstance(); + MeyersSingleton& db2 = MeyersSingleton::getInstance(); + + std::cout << "Are instances the same? " << (&db1 == &db2 ? "Yes" : "No") << std::endl; + + db1.connect("mysql://localhost:3306/mydb"); + db2.connect("mysql://localhost:3306/mydb"); + std::cout << "Connection count: " << db1.getConnectionCount() << std::endl; + + db1.disconnect(); + std::cout << "Is still connected? " << (db2.isConnected() ? "Yes" : "No") << std::endl; + + std::cout << "\n===== Shared Pointer Singleton Demo =====" << std::endl; + auto featureManager1 = SharedPtrSingleton::getInstance(); + auto featureManager2 = SharedPtrSingleton::getInstance(); + + std::cout << "Are instances the same? " << (featureManager1 == featureManager2 ? "Yes" : "No") << std::endl; + + std::cout << "Dark mode enabled: " << (featureManager1->isFeatureEnabled("dark_mode") ? "Yes" : "No") << std::endl; + + featureManager2->enableFeature("dark_mode"); + std::cout << "Dark mode enabled after update: " << (featureManager1->isFeatureEnabled("dark_mode") ? "Yes" : "No") << std::endl; + + // Clean up classic singleton + ClassicSingleton::destroyInstance(); + ThreadSafeSingleton::destroyInstance(); + // No need to clean up Meyers Singleton or SharedPtrSingleton +} + +// Main function +int main() { + std::cout << "Singleton Pattern Demonstration in C++\n"; + std::cout << "====================================\n\n"; + + demonstrateSingletons(); + return 0; +} \ No newline at end of file diff --git a/snippets/design-patterns/singleton/SingletonPattern.cs b/snippets/design-patterns/singleton/SingletonPattern.cs new file mode 100644 index 0000000..47bc89b --- /dev/null +++ b/snippets/design-patterns/singleton/SingletonPattern.cs @@ -0,0 +1,500 @@ +/** + * Singleton Pattern Implementation in C# + * + * The Singleton Pattern is a creational design pattern that ensures a class has only one instance + * and provides a global point of access to it. This is useful when exactly one object is needed + * to coordinate actions across the system. + * + * This file demonstrates several ways to implement the Singleton pattern in C#. + */ + +using System; +using System.Collections.Generic; +using System.Threading; + +namespace DesignPatterns.Singleton +{ + // ========== Classic Singleton Implementation ========== + + /// + /// Classic Singleton implementation using a static instance field and lazy initialization + /// + public sealed class ClassicSingleton + { + private static ClassicSingleton _instance; + private static readonly object _lock = new object(); + + private DateTime _timestamp; + private Dictionary _config; + + // Private constructor to prevent direct instantiation + private ClassicSingleton() + { + _timestamp = DateTime.Now; + _config = new Dictionary + { + { "ApiUrl", "https://api.example.com" }, + { "Timeout", 3000 }, + { "Retries", 3 } + }; + } + + public static ClassicSingleton Instance + { + get + { + if (_instance == null) + { + lock (_lock) + { + if (_instance == null) + { + _instance = new ClassicSingleton(); + } + } + } + return _instance; + } + } + + public Dictionary GetConfig() + { + return new Dictionary(_config); + } + + public void UpdateConfig(string key, object value) + { + lock (_lock) + { + _config[key] = value; + Console.WriteLine($"Configuration updated: {key} = {value}"); + } + } + + public DateTime GetTimestamp() + { + return _timestamp; + } + } + + // ========== Singleton with Static Initialization ========== + + /// + /// Singleton implementation using static initialization + /// This implementation is thread-safe without explicit synchronization + /// + public sealed class StaticSingleton + { + // Static initialization is thread-safe in C# + private static readonly StaticSingleton _instance = new StaticSingleton(); + + private int _connectionCount; + private bool _isConnected; + private string _connectionString; + + // Static constructor to tell C# compiler not to mark type as beforefieldinit + static StaticSingleton() + { + } + + // Private constructor to prevent direct instantiation + private StaticSingleton() + { + _connectionCount = 0; + _isConnected = false; + _connectionString = string.Empty; + } + + public static StaticSingleton Instance + { + get { return _instance; } + } + + public bool Connect(string connectionString) + { + if (_isConnected) + { + _connectionCount++; + Console.WriteLine($"Already connected to database. Connection count: {_connectionCount}"); + return true; + } + + // Simulate connection + _connectionString = connectionString; + _isConnected = true; + _connectionCount = 1; + Console.WriteLine($"Connected to database: {connectionString}"); + return true; + } + + public bool Disconnect() + { + if (!_isConnected) + { + Console.WriteLine("Not connected to any database."); + return false; + } + + _connectionCount--; + if (_connectionCount == 0) + { + _isConnected = false; + Console.WriteLine($"Disconnected from database: {_connectionString}"); + } + else + { + Console.WriteLine($"Connection count decreased. Remaining connections: {_connectionCount}"); + } + + return true; + } + + public bool IsConnected => _isConnected; + + public int ConnectionCount => _connectionCount; + } + + // ========== Singleton with Lazy ========== + + /// + /// Modern Singleton implementation using Lazy + /// This implementation is thread-safe and uses .NET's built-in lazy initialization + /// + public sealed class LazySingleton + { + private static readonly Lazy _lazy = + new Lazy(() => new LazySingleton()); + + private List _logs; + + private LazySingleton() + { + _logs = new List(); + } + + public static LazySingleton Instance => _lazy.Value; + + public void Log(string message) + { + string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + string logEntry = $"{timestamp}: {message}"; + _logs.Add(logEntry); + Console.WriteLine(logEntry); + } + + public void Warn(string message) + { + Log($"WARNING: {message}"); + } + + public void Error(string message) + { + Log($"ERROR: {message}"); + } + + public List GetLogs() + { + return new List(_logs); + } + + public void ClearLogs() + { + _logs.Clear(); + Console.WriteLine("Logs cleared"); + } + } + + // ========== Singleton with Dependency Injection ========== + + /// + /// Singleton implementation that can be used with dependency injection + /// This is a more modern approach that allows for better testability + /// + public interface IConfigManager + { + Dictionary GetConfig(); + void SetConfig(string key, object value); + void ResetConfig(); + } + + public sealed class ConfigManager : IConfigManager + { + private Dictionary _config; + + public ConfigManager() + { + _config = new Dictionary + { + { "Theme", "light" }, + { "Language", "en" }, + { "Notifications", true }, + { "AutoSave", true } + }; + } + + public Dictionary GetConfig() + { + return new Dictionary(_config); + } + + public void SetConfig(string key, object value) + { + _config[key] = value; + Console.WriteLine($"Configuration updated: {key} = {value}"); + } + + public void ResetConfig() + { + _config = new Dictionary + { + { "Theme", "light" }, + { "Language", "en" }, + { "Notifications", true }, + { "AutoSave", true } + }; + Console.WriteLine("Configuration reset to defaults"); + } + } + + // ========== Thread-Safe Singleton with Double-Check Locking ========== + + /// + /// Thread-safe Singleton implementation using double-check locking + /// + public sealed class UserManager + { + private static volatile UserManager _instance; + private static readonly object _lock = new object(); + + private readonly Dictionary _users; + + // Private constructor to prevent direct instantiation + private UserManager() + { + _users = new Dictionary(); + } + + public static UserManager Instance + { + get + { + if (_instance == null) + { + lock (_lock) + { + if (_instance == null) + { + _instance = new UserManager(); + } + } + } + return _instance; + } + } + + public bool AddUser(int id, UserData userData) + { + lock (_lock) + { + if (_users.ContainsKey(id)) + { + throw new InvalidOperationException($"User with ID {id} already exists"); + } + + _users[id] = new UserData + { + Name = userData.Name, + Email = userData.Email, + CreatedAt = DateTime.Now + }; + + return true; + } + } + + public UserData GetUser(int id) + { + lock (_lock) + { + if (!_users.ContainsKey(id)) + { + return null; + } + + // Return a copy to prevent modification + return new UserData + { + Name = _users[id].Name, + Email = _users[id].Email, + Role = _users[id].Role, + CreatedAt = _users[id].CreatedAt, + UpdatedAt = _users[id].UpdatedAt + }; + } + } + + public bool UpdateUser(int id, UserData userData) + { + lock (_lock) + { + if (!_users.ContainsKey(id)) + { + throw new InvalidOperationException($"User with ID {id} does not exist"); + } + + var existingUser = _users[id]; + _users[id] = new UserData + { + Name = userData.Name ?? existingUser.Name, + Email = userData.Email ?? existingUser.Email, + Role = userData.Role ?? existingUser.Role, + CreatedAt = existingUser.CreatedAt, + UpdatedAt = DateTime.Now + }; + + return true; + } + } + + public bool DeleteUser(int id) + { + lock (_lock) + { + if (!_users.ContainsKey(id)) + { + throw new InvalidOperationException($"User with ID {id} does not exist"); + } + + return _users.Remove(id); + } + } + + public List GetAllUsers() + { + lock (_lock) + { + var result = new List(); + foreach (var kvp in _users) + { + result.Add(new UserData + { + Id = kvp.Key, + Name = kvp.Value.Name, + Email = kvp.Value.Email, + Role = kvp.Value.Role, + CreatedAt = kvp.Value.CreatedAt, + UpdatedAt = kvp.Value.UpdatedAt + }); + } + return result; + } + } + + public int UserCount + { + get + { + lock (_lock) + { + return _users.Count; + } + } + } + } + + public class UserData + { + public int Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public string Role { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + } + + // ========== Demo Code ========== + + public class SingletonDemo + { + public static void DemonstrateSingletons() + { + Console.WriteLine("===== Classic Singleton Demo ====="); + ClassicSingleton singleton1 = ClassicSingleton.Instance; + ClassicSingleton singleton2 = ClassicSingleton.Instance; + + Console.WriteLine($"Are instances the same? {object.ReferenceEquals(singleton1, singleton2)}"); + Console.WriteLine($"Instance timestamp: {singleton1.GetTimestamp()}"); + + var config = singleton1.GetConfig(); + Console.WriteLine($"Original config: ApiUrl = {config["ApiUrl"]}"); + + singleton2.UpdateConfig("Timeout", 5000); + config = singleton1.GetConfig(); + Console.WriteLine($"Updated config from singleton1: Timeout = {config["Timeout"]}"); + + Console.WriteLine("\n===== Static Singleton Demo ====="); + StaticSingleton db1 = StaticSingleton.Instance; + StaticSingleton db2 = StaticSingleton.Instance; + + Console.WriteLine($"Are instances the same? {object.ReferenceEquals(db1, db2)}"); + + db1.Connect("mysql://localhost:3306/mydb"); + db2.Connect("mysql://localhost:3306/mydb"); + Console.WriteLine($"Connection count: {db1.ConnectionCount}"); + + db1.Disconnect(); + Console.WriteLine($"Still connected? {db2.IsConnected}"); + + Console.WriteLine("\n===== Lazy Singleton Demo ====="); + LazySingleton logger1 = LazySingleton.Instance; + LazySingleton logger2 = LazySingleton.Instance; + + Console.WriteLine($"Are instances the same? {object.ReferenceEquals(logger1, logger2)}"); + + logger1.Log("Application started"); + logger1.Warn("Resource usage is high"); + logger2.Error("Failed to connect to service"); + + var logs = logger2.GetLogs(); + Console.WriteLine($"Log entries: {logs.Count}"); + + Console.WriteLine("\n===== Config Manager Demo ====="); + IConfigManager configManager = new ConfigManager(); + + var configSettings = configManager.GetConfig(); + Console.WriteLine($"Config value: Theme = {configSettings["Theme"]}"); + + configManager.SetConfig("Theme", "dark"); + configSettings = configManager.GetConfig(); + Console.WriteLine($"Updated config: Theme = {configSettings["Theme"]}"); + + Console.WriteLine("\n===== User Manager Singleton Demo ====="); + UserManager userManager1 = UserManager.Instance; + UserManager userManager2 = UserManager.Instance; + + Console.WriteLine($"Are instances the same? {object.ReferenceEquals(userManager1, userManager2)}"); + + userManager1.AddUser(1, new UserData { Name = "Alice", Email = "alice@example.com" }); + userManager1.AddUser(2, new UserData { Name = "Bob", Email = "bob@example.com" }); + + Console.WriteLine($"User count: {userManager2.UserCount}"); + + var user = userManager2.GetUser(1); + Console.WriteLine($"User #1: {user.Name}, {user.Email}"); + + userManager2.UpdateUser(1, new UserData { Role = "admin" }); + user = userManager1.GetUser(1); + Console.WriteLine($"Updated User #1: {user.Name}, {user.Email}, {user.Role}"); + } + } + + // Main program, run the example + class Program + { + static void Main(string[] args) + { + SingletonDemo.DemonstrateSingletons(); + Console.ReadLine(); + } + } +} diff --git a/snippets/design-patterns/singleton/SingletonPattern.java b/snippets/design-patterns/singleton/SingletonPattern.java new file mode 100644 index 0000000..f80e61e --- /dev/null +++ b/snippets/design-patterns/singleton/SingletonPattern.java @@ -0,0 +1,296 @@ +/** + * Singleton Pattern Implementation in Java + * + * This demonstrates various implementations of the Singleton pattern in Java. + */ + +import java.util.concurrent.atomic.AtomicReference; + +// Method 1: Eager Initialization +class EagerSingleton { + // Instance is created when class is loaded + private static final EagerSingleton INSTANCE = new EagerSingleton(); + + private int value = 0; + + // Private constructor prevents instantiation from other classes + private EagerSingleton() { + System.out.println("EagerSingleton instance created"); + } + + // Public static method to provide access to the instance + public static EagerSingleton getInstance() { + return INSTANCE; + } + + public int increment() { + return ++value; + } + + public int getValue() { + return value; + } +} + +// Method 2: Lazy Initialization (not thread-safe) +class LazySingleton { + // Instance is not created until getInstance() is called + private static LazySingleton instance; + + private int value = 0; + + private LazySingleton() { + System.out.println("LazySingleton instance created"); + } + + // Not thread-safe! + public static LazySingleton getInstance() { + if (instance == null) { + instance = new LazySingleton(); + } + return instance; + } + + public int increment() { + return ++value; + } + + public int getValue() { + return value; + } +} + +// Method 3: Thread-safe Lazy Initialization with synchronized method +class ThreadSafeSingleton { + private static ThreadSafeSingleton instance; + + private int value = 0; + + private ThreadSafeSingleton() { + System.out.println("ThreadSafeSingleton instance created"); + } + + // Thread-safe but has performance overhead + public static synchronized ThreadSafeSingleton getInstance() { + if (instance == null) { + instance = new ThreadSafeSingleton(); + } + return instance; + } + + public int increment() { + return ++value; + } + + public int getValue() { + return value; + } +} + +// Method 4: Double-checked Locking +class DCLSingleton { + // volatile ensures visibility across threads + private static volatile DCLSingleton instance; + + private int value = 0; + + private DCLSingleton() { + System.out.println("DCLSingleton instance created"); + } + + // Thread-safe with better performance + public static DCLSingleton getInstance() { + // First check without synchronization + if (instance == null) { + // Synchronize only when instance is null + synchronized (DCLSingleton.class) { + // Double-check inside synchronized block + if (instance == null) { + instance = new DCLSingleton(); + } + } + } + return instance; + } + + public int increment() { + return ++value; + } + + public int getValue() { + return value; + } +} + +// Method 5: Initialization on Demand Holder idiom +class HolderSingleton { + private int value = 0; + + private HolderSingleton() { + System.out.println("HolderSingleton instance created"); + } + + // Static holder class is only initialized when getInstance() is called + private static class Holder { + private static final HolderSingleton INSTANCE = new HolderSingleton(); + } + + // Thread-safe without synchronization overhead + public static HolderSingleton getInstance() { + return Holder.INSTANCE; + } + + public int increment() { + return ++value; + } + + public int getValue() { + return value; + } +} + +// Method 6: Enum-based Singleton (serialization-safe and reflection-proof) +enum EnumSingleton { + INSTANCE; + + private int value = 0; + + EnumSingleton() { + System.out.println("EnumSingleton instance created"); + } + + public int increment() { + return ++value; + } + + public int getValue() { + return value; + } +} + +// Method 7: Using CAS (Compare-And-Swap) operations with AtomicReference +class CASSingleton { + private static final AtomicReference INSTANCE = new AtomicReference<>(); + + private int value = 0; + + private CASSingleton() { + System.out.println("CASSingleton instance created"); + } + + // Thread-safe using non-blocking synchronization + public static CASSingleton getInstance() { + CASSingleton instance = INSTANCE.get(); + if (instance == null) { + CASSingleton newInstance = new CASSingleton(); + if (INSTANCE.compareAndSet(null, newInstance)) { + instance = newInstance; + } else { + // Another thread beat us to it + instance = INSTANCE.get(); + } + } + return instance; + } + + public int increment() { + return ++value; + } + + public int getValue() { + return value; + } +} + +// Main class to demonstrate various singleton implementations +public class SingletonPattern { + public static void main(String[] args) { + System.out.println("Singleton Pattern Demonstrations"); + System.out.println("==============================="); + + demonstrateEagerSingleton(); + demonstrateLazySingleton(); + demonstrateThreadSafeSingleton(); + demonstrateDCLSingleton(); + demonstrateHolderSingleton(); + demonstrateEnumSingleton(); + demonstrateCASSingleton(); + } + + private static void demonstrateEagerSingleton() { + System.out.println("\n=== Eager Singleton ==="); + EagerSingleton s1 = EagerSingleton.getInstance(); + EagerSingleton s2 = EagerSingleton.getInstance(); + + System.out.println("s1 == s2: " + (s1 == s2)); + System.out.println("s1.increment(): " + s1.increment()); + System.out.println("s2.increment(): " + s2.increment()); + System.out.println("s1.getValue(): " + s1.getValue() + ", s2.getValue(): " + s2.getValue()); + } + + private static void demonstrateLazySingleton() { + System.out.println("\n=== Lazy Singleton (not thread-safe) ==="); + LazySingleton s1 = LazySingleton.getInstance(); + LazySingleton s2 = LazySingleton.getInstance(); + + System.out.println("s1 == s2: " + (s1 == s2)); + System.out.println("s1.increment(): " + s1.increment()); + System.out.println("s2.increment(): " + s2.increment()); + System.out.println("s1.getValue(): " + s1.getValue() + ", s2.getValue(): " + s2.getValue()); + } + + private static void demonstrateThreadSafeSingleton() { + System.out.println("\n=== Thread-Safe Singleton ==="); + ThreadSafeSingleton s1 = ThreadSafeSingleton.getInstance(); + ThreadSafeSingleton s2 = ThreadSafeSingleton.getInstance(); + + System.out.println("s1 == s2: " + (s1 == s2)); + System.out.println("s1.increment(): " + s1.increment()); + System.out.println("s2.increment(): " + s2.increment()); + System.out.println("s1.getValue(): " + s1.getValue() + ", s2.getValue(): " + s2.getValue()); + } + + private static void demonstrateDCLSingleton() { + System.out.println("\n=== Double-Checked Locking Singleton ==="); + DCLSingleton s1 = DCLSingleton.getInstance(); + DCLSingleton s2 = DCLSingleton.getInstance(); + + System.out.println("s1 == s2: " + (s1 == s2)); + System.out.println("s1.increment(): " + s1.increment()); + System.out.println("s2.increment(): " + s2.increment()); + System.out.println("s1.getValue(): " + s1.getValue() + ", s2.getValue(): " + s2.getValue()); + } + + private static void demonstrateHolderSingleton() { + System.out.println("\n=== Initialization-on-demand Holder Singleton ==="); + HolderSingleton s1 = HolderSingleton.getInstance(); + HolderSingleton s2 = HolderSingleton.getInstance(); + + System.out.println("s1 == s2: " + (s1 == s2)); + System.out.println("s1.increment(): " + s1.increment()); + System.out.println("s2.increment(): " + s2.increment()); + System.out.println("s1.getValue(): " + s1.getValue() + ", s2.getValue(): " + s2.getValue()); + } + + private static void demonstrateEnumSingleton() { + System.out.println("\n=== Enum Singleton ==="); + EnumSingleton s1 = EnumSingleton.INSTANCE; + EnumSingleton s2 = EnumSingleton.INSTANCE; + + System.out.println("s1 == s2: " + (s1 == s2)); + System.out.println("s1.increment(): " + s1.increment()); + System.out.println("s2.increment(): " + s2.increment()); + System.out.println("s1.getValue(): " + s1.getValue() + ", s2.getValue(): " + s2.getValue()); + } + + private static void demonstrateCASSingleton() { + System.out.println("\n=== CAS (AtomicReference) Singleton ==="); + CASSingleton s1 = CASSingleton.getInstance(); + CASSingleton s2 = CASSingleton.getInstance(); + + System.out.println("s1 == s2: " + (s1 == s2)); + System.out.println("s1.increment(): " + s1.increment()); + System.out.println("s2.increment(): " + s2.increment()); + System.out.println("s1.getValue(): " + s1.getValue() + ", s2.getValue(): " + s2.getValue()); + } +} \ No newline at end of file diff --git a/snippets/design-patterns/singleton/singleton_pattern.c b/snippets/design-patterns/singleton/singleton_pattern.c new file mode 100644 index 0000000..b21bcce --- /dev/null +++ b/snippets/design-patterns/singleton/singleton_pattern.c @@ -0,0 +1,599 @@ +/** + * Singleton Pattern Implementation in C + * + * The Singleton Pattern is a creational design pattern that ensures a class has only one instance + * and provides a global point of access to it. This is useful when exactly one object is needed + * to coordinate actions across the system. + * + * This file demonstrates several ways to implement the Singleton pattern in C. + * Note: Since C is not an object-oriented language, we use structs with function pointers + * to simulate classes and objects. + */ + +#include +#include +#include +#include +#include +#include + +// ========== Classic Singleton Implementation ========== + +// Configuration Manager Singleton +typedef struct { + char api_url[100]; + int timeout; + int retries; + time_t timestamp; +} ConfigManager; + +// Global static instance +static ConfigManager* config_manager_instance = NULL; +static pthread_mutex_t config_manager_mutex = PTHREAD_MUTEX_INITIALIZER; + +// Get singleton instance +ConfigManager* get_config_manager() { + if (config_manager_instance == NULL) { + pthread_mutex_lock(&config_manager_mutex); + if (config_manager_instance == NULL) { + config_manager_instance = (ConfigManager*)malloc(sizeof(ConfigManager)); + if (config_manager_instance == NULL) { + pthread_mutex_unlock(&config_manager_mutex); + return NULL; + } + + // Initialize default values + strcpy(config_manager_instance->api_url, "https://api.example.com"); + config_manager_instance->timeout = 3000; + config_manager_instance->retries = 3; + config_manager_instance->timestamp = time(NULL); + } + pthread_mutex_unlock(&config_manager_mutex); + } + + return config_manager_instance; +} + +// Get config values +void config_manager_get_config(ConfigManager* cm, char* api_url, int* timeout, int* retries) { + pthread_mutex_lock(&config_manager_mutex); + if (api_url != NULL) { + strcpy(api_url, cm->api_url); + } + if (timeout != NULL) { + *timeout = cm->timeout; + } + if (retries != NULL) { + *retries = cm->retries; + } + pthread_mutex_unlock(&config_manager_mutex); +} + +// Update config value +void config_manager_update_config(ConfigManager* cm, const char* key, const void* value) { + pthread_mutex_lock(&config_manager_mutex); + if (strcmp(key, "api_url") == 0 && value != NULL) { + strcpy(cm->api_url, (const char*)value); + printf("Configuration updated: api_url = %s\n", cm->api_url); + } else if (strcmp(key, "timeout") == 0 && value != NULL) { + cm->timeout = *((int*)value); + printf("Configuration updated: timeout = %d\n", cm->timeout); + } else if (strcmp(key, "retries") == 0 && value != NULL) { + cm->retries = *((int*)value); + printf("Configuration updated: retries = %d\n", cm->retries); + } + pthread_mutex_unlock(&config_manager_mutex); +} + +// Get timestamp +time_t config_manager_get_timestamp(ConfigManager* cm) { + return cm->timestamp; +} + +// Clean up resources +void config_manager_destroy() { + pthread_mutex_lock(&config_manager_mutex); + if (config_manager_instance != NULL) { + free(config_manager_instance); + config_manager_instance = NULL; + } + pthread_mutex_unlock(&config_manager_mutex); +} + +// ========== Database Connection Singleton ========== + +// Database Connection Singleton +typedef struct { + char connection_string[200]; + int connection_count; + bool is_connected; +} DatabaseConnection; + +// Global static instance +static DatabaseConnection* db_connection_instance = NULL; +static pthread_mutex_t db_connection_mutex = PTHREAD_MUTEX_INITIALIZER; + +// Get singleton instance +DatabaseConnection* get_database_connection() { + if (db_connection_instance == NULL) { + pthread_mutex_lock(&db_connection_mutex); + if (db_connection_instance == NULL) { + db_connection_instance = (DatabaseConnection*)malloc(sizeof(DatabaseConnection)); + if (db_connection_instance == NULL) { + pthread_mutex_unlock(&db_connection_mutex); + return NULL; + } + + // Initialize default values + db_connection_instance->connection_string[0] = '\0'; + db_connection_instance->connection_count = 0; + db_connection_instance->is_connected = false; + } + pthread_mutex_unlock(&db_connection_mutex); + } + + return db_connection_instance; +} + +// Connect to database +bool database_connection_connect(DatabaseConnection* db, const char* connection_string) { + pthread_mutex_lock(&db_connection_mutex); + if (db->is_connected) { + db->connection_count++; + printf("Already connected to database. Connection count: %d\n", db->connection_count); + pthread_mutex_unlock(&db_connection_mutex); + return true; + } + + // Simulate connection + strcpy(db->connection_string, connection_string); + db->is_connected = true; + db->connection_count = 1; + printf("Connected to database: %s\n", connection_string); + pthread_mutex_unlock(&db_connection_mutex); + return true; +} + +// Disconnect from database +bool database_connection_disconnect(DatabaseConnection* db) { + pthread_mutex_lock(&db_connection_mutex); + if (!db->is_connected) { + printf("Not connected to any database.\n"); + pthread_mutex_unlock(&db_connection_mutex); + return false; + } + + db->connection_count--; + if (db->connection_count == 0) { + db->is_connected = false; + printf("Disconnected from database: %s\n", db->connection_string); + } else { + printf("Connection count decreased. Remaining connections: %d\n", db->connection_count); + } + pthread_mutex_unlock(&db_connection_mutex); + return true; +} + +// Check if connected +bool database_connection_is_connected(DatabaseConnection* db) { + pthread_mutex_lock(&db_connection_mutex); + bool result = db->is_connected; + pthread_mutex_unlock(&db_connection_mutex); + return result; +} + +// Get connection count +int database_connection_get_count(DatabaseConnection* db) { + pthread_mutex_lock(&db_connection_mutex); + int result = db->connection_count; + pthread_mutex_unlock(&db_connection_mutex); + return result; +} + +// Clean up resources +void database_connection_destroy() { + pthread_mutex_lock(&db_connection_mutex); + if (db_connection_instance != NULL) { + free(db_connection_instance); + db_connection_instance = NULL; + } + pthread_mutex_unlock(&db_connection_mutex); +} + +// ========== Logger Singleton ========== + +// Log entry structure +typedef struct LogEntry { + char message[256]; + time_t timestamp; + struct LogEntry* next; +} LogEntry; + +// Logger Singleton +typedef struct { + LogEntry* head; + int log_count; +} Logger; + +// Global static instance +static Logger* logger_instance = NULL; +static pthread_mutex_t logger_mutex = PTHREAD_MUTEX_INITIALIZER; + +// Get singleton instance +Logger* get_logger() { + if (logger_instance == NULL) { + pthread_mutex_lock(&logger_mutex); + if (logger_instance == NULL) { + logger_instance = (Logger*)malloc(sizeof(Logger)); + if (logger_instance == NULL) { + pthread_mutex_unlock(&logger_mutex); + return NULL; + } + + // Initialize default values + logger_instance->head = NULL; + logger_instance->log_count = 0; + } + pthread_mutex_unlock(&logger_mutex); + } + + return logger_instance; +} + +// Add log entry +void logger_log(Logger* logger, const char* message) { + time_t now = time(NULL); + struct tm* tm_info = localtime(&now); + char timestamp[26]; + strftime(timestamp, 26, "%Y-%m-%d %H:%M:%S", tm_info); + + LogEntry* new_entry = (LogEntry*)malloc(sizeof(LogEntry)); + if (new_entry == NULL) { + return; + } + + snprintf(new_entry->message, sizeof(new_entry->message), "%s: %s", timestamp, message); + new_entry->timestamp = now; + new_entry->next = NULL; + + pthread_mutex_lock(&logger_mutex); + if (logger->head == NULL) { + logger->head = new_entry; + } else { + LogEntry* current = logger->head; + while (current->next != NULL) { + current = current->next; + } + current->next = new_entry; + } + logger->log_count++; + pthread_mutex_unlock(&logger_mutex); + + printf("%s\n", new_entry->message); +} + +// Add warning log entry +void logger_warn(Logger* logger, const char* message) { + char warning_message[256]; + snprintf(warning_message, sizeof(warning_message), "WARNING: %s", message); + logger_log(logger, warning_message); +} + +// Add error log entry +void logger_error(Logger* logger, const char* message) { + char error_message[256]; + snprintf(error_message, sizeof(error_message), "ERROR: %s", message); + logger_log(logger, error_message); +} + +// Get log count +int logger_get_count(Logger* logger) { + pthread_mutex_lock(&logger_mutex); + int result = logger->log_count; + pthread_mutex_unlock(&logger_mutex); + return result; +} + +// Clear logs +void logger_clear(Logger* logger) { + pthread_mutex_lock(&logger_mutex); + LogEntry* current = logger->head; + while (current != NULL) { + LogEntry* next = current->next; + free(current); + current = next; + } + logger->head = NULL; + logger->log_count = 0; + pthread_mutex_unlock(&logger_mutex); + printf("Logs cleared\n"); +} + +// Clean up resources +void logger_destroy() { + pthread_mutex_lock(&logger_mutex); + if (logger_instance != NULL) { + LogEntry* current = logger_instance->head; + while (current != NULL) { + LogEntry* next = current->next; + free(current); + current = next; + } + free(logger_instance); + logger_instance = NULL; + } + pthread_mutex_unlock(&logger_mutex); +} + +// ========== User Manager Singleton ========== + +// User structure +typedef struct { + int id; + char name[100]; + char email[100]; + char role[50]; + time_t created_at; + time_t updated_at; + bool has_role; +} User; + +// User Manager Singleton +typedef struct { + User* users; + int capacity; + int count; +} UserManager; + +// Global static instance +static UserManager* user_manager_instance = NULL; +static pthread_mutex_t user_manager_mutex = PTHREAD_MUTEX_INITIALIZER; + +// Get singleton instance +UserManager* get_user_manager() { + if (user_manager_instance == NULL) { + pthread_mutex_lock(&user_manager_mutex); + if (user_manager_instance == NULL) { + user_manager_instance = (UserManager*)malloc(sizeof(UserManager)); + if (user_manager_instance == NULL) { + pthread_mutex_unlock(&user_manager_mutex); + return NULL; + } + + // Initialize with capacity for 10 users + user_manager_instance->capacity = 10; + user_manager_instance->users = (User*)malloc(user_manager_instance->capacity * sizeof(User)); + if (user_manager_instance->users == NULL) { + free(user_manager_instance); + user_manager_instance = NULL; + pthread_mutex_unlock(&user_manager_mutex); + return NULL; + } + user_manager_instance->count = 0; + } + pthread_mutex_unlock(&user_manager_mutex); + } + + return user_manager_instance; +} + +// Find user by ID +int user_manager_find_user(UserManager* um, int id) { + for (int i = 0; i < um->count; i++) { + if (um->users[i].id == id) { + return i; + } + } + return -1; +} + +// Add user +bool user_manager_add_user(UserManager* um, int id, const char* name, const char* email) { + pthread_mutex_lock(&user_manager_mutex); + + // Check if user already exists + if (user_manager_find_user(um, id) != -1) { + printf("User with ID %d already exists\n", id); + pthread_mutex_unlock(&user_manager_mutex); + return false; + } + + // Check if we need to resize the array + if (um->count >= um->capacity) { + int new_capacity = um->capacity * 2; + User* new_users = (User*)realloc(um->users, new_capacity * sizeof(User)); + if (new_users == NULL) { + pthread_mutex_unlock(&user_manager_mutex); + return false; + } + um->users = new_users; + um->capacity = new_capacity; + } + + // Add the new user + User* new_user = &um->users[um->count]; + new_user->id = id; + strncpy(new_user->name, name, sizeof(new_user->name) - 1); + new_user->name[sizeof(new_user->name) - 1] = '\0'; + strncpy(new_user->email, email, sizeof(new_user->email) - 1); + new_user->email[sizeof(new_user->email) - 1] = '\0'; + new_user->role[0] = '\0'; + new_user->has_role = false; + new_user->created_at = time(NULL); + new_user->updated_at = 0; + + um->count++; + pthread_mutex_unlock(&user_manager_mutex); + return true; +} + +// Get user +User* user_manager_get_user(UserManager* um, int id) { + pthread_mutex_lock(&user_manager_mutex); + int index = user_manager_find_user(um, id); + if (index == -1) { + pthread_mutex_unlock(&user_manager_mutex); + return NULL; + } + + static User user_copy; + memcpy(&user_copy, &um->users[index], sizeof(User)); + pthread_mutex_unlock(&user_manager_mutex); + return &user_copy; +} + +// Update user +bool user_manager_update_user(UserManager* um, int id, const char* name, const char* email, const char* role) { + pthread_mutex_lock(&user_manager_mutex); + int index = user_manager_find_user(um, id); + if (index == -1) { + printf("User with ID %d does not exist\n", id); + pthread_mutex_unlock(&user_manager_mutex); + return false; + } + + User* user = &um->users[index]; + if (name != NULL) { + strncpy(user->name, name, sizeof(user->name) - 1); + user->name[sizeof(user->name) - 1] = '\0'; + } + if (email != NULL) { + strncpy(user->email, email, sizeof(user->email) - 1); + user->email[sizeof(user->email) - 1] = '\0'; + } + if (role != NULL) { + strncpy(user->role, role, sizeof(user->role) - 1); + user->role[sizeof(user->role) - 1] = '\0'; + user->has_role = true; + } + + user->updated_at = time(NULL); + pthread_mutex_unlock(&user_manager_mutex); + return true; +} + +// Delete user +bool user_manager_delete_user(UserManager* um, int id) { + pthread_mutex_lock(&user_manager_mutex); + int index = user_manager_find_user(um, id); + if (index == -1) { + printf("User with ID %d does not exist\n", id); + pthread_mutex_unlock(&user_manager_mutex); + return false; + } + + // Remove user by shifting all users after it + for (int i = index; i < um->count - 1; i++) { + um->users[i] = um->users[i + 1]; + } + + um->count--; + pthread_mutex_unlock(&user_manager_mutex); + return true; +} + +// Get user count +int user_manager_get_count(UserManager* um) { + pthread_mutex_lock(&user_manager_mutex); + int result = um->count; + pthread_mutex_unlock(&user_manager_mutex); + return result; +} + +// Clean up resources +void user_manager_destroy() { + pthread_mutex_lock(&user_manager_mutex); + if (user_manager_instance != NULL) { + if (user_manager_instance->users != NULL) { + free(user_manager_instance->users); + } + free(user_manager_instance); + user_manager_instance = NULL; + } + pthread_mutex_unlock(&user_manager_mutex); +} + +// ========== Demo Code ========== + +void demonstrate_singletons() { + printf("===== Config Manager Singleton Demo =====\n"); + ConfigManager* config1 = get_config_manager(); + ConfigManager* config2 = get_config_manager(); + + printf("Are instances the same? %s\n", (config1 == config2) ? "Yes" : "No"); + printf("Instance timestamp: %ld\n", config_manager_get_timestamp(config1)); + + char api_url[100]; + int timeout; + int retries; + config_manager_get_config(config1, api_url, &timeout, &retries); + printf("Original config: api_url = %s, timeout = %d, retries = %d\n", api_url, timeout, retries); + + int new_timeout = 5000; + config_manager_update_config(config2, "timeout", &new_timeout); + + config_manager_get_config(config1, api_url, &timeout, &retries); + printf("Updated config from config1: timeout = %d\n", timeout); + + printf("\n===== Database Connection Singleton Demo =====\n"); + DatabaseConnection* db1 = get_database_connection(); + DatabaseConnection* db2 = get_database_connection(); + + printf("Are instances the same? %s\n", (db1 == db2) ? "Yes" : "No"); + + database_connection_connect(db1, "mysql://localhost:3306/mydb"); + database_connection_connect(db2, "mysql://localhost:3306/mydb"); + printf("Connection count: %d\n", database_connection_get_count(db1)); + + database_connection_disconnect(db1); + printf("Still connected? %s\n", database_connection_is_connected(db2) ? "Yes" : "No"); + + printf("\n===== Logger Singleton Demo =====\n"); + Logger* logger1 = get_logger(); + Logger* logger2 = get_logger(); + + printf("Are instances the same? %s\n", (logger1 == logger2) ? "Yes" : "No"); + + logger_log(logger1, "Application started"); + logger_warn(logger1, "Resource usage is high"); + logger_error(logger2, "Failed to connect to service"); + + printf("Log entries: %d\n", logger_get_count(logger2)); + + printf("\n===== User Manager Singleton Demo =====\n"); + UserManager* user_manager1 = get_user_manager(); + UserManager* user_manager2 = get_user_manager(); + + printf("Are instances the same? %s\n", (user_manager1 == user_manager2) ? "Yes" : "No"); + + user_manager_add_user(user_manager1, 1, "Alice", "alice@example.com"); + user_manager_add_user(user_manager1, 2, "Bob", "bob@example.com"); + + printf("User count: %d\n", user_manager_get_count(user_manager2)); + + User* user = user_manager_get_user(user_manager2, 1); + if (user != NULL) { + printf("User #1: %s, %s\n", user->name, user->email); + } + + user_manager_update_user(user_manager2, 1, NULL, NULL, "admin"); + user = user_manager_get_user(user_manager1, 1); + if (user != NULL) { + printf("Updated User #1: %s, %s, %s\n", user->name, user->email, user->role); + } +} + +// Clean up all resources +void cleanup_all() { + config_manager_destroy(); + database_connection_destroy(); + logger_destroy(); + user_manager_destroy(); +} + +int main() { + // Run the demo + demonstrate_singletons(); + cleanup_all(); + return 0; +} diff --git a/snippets/design-patterns/singleton/singleton_pattern.go b/snippets/design-patterns/singleton/singleton_pattern.go new file mode 100644 index 0000000..2c1978b --- /dev/null +++ b/snippets/design-patterns/singleton/singleton_pattern.go @@ -0,0 +1,376 @@ +/** + * Singleton Pattern Implementation in Go + * + * The Singleton Pattern is a creational design pattern that ensures a class has only one instance + * and provides a global point of access to it. This is useful when exactly one object is needed + * to coordinate actions across the system. + * + * This file demonstrates several ways to implement the Singleton pattern in Go. + */ + +package main + +import ( + "fmt" + "sync" + "time" +) + +// ========== Simple Singleton Implementation ========== + +// Singleton is a simple singleton instance +type Singleton struct { + data map[string]interface{} + createdTime time.Time +} + +var ( + instance *Singleton + once sync.Once +) + +// GetInstance returns the singleton instance +func GetInstance() *Singleton { + once.Do(func() { + instance = &Singleton{ + data: make(map[string]interface{}), + createdTime: time.Now(), + } + fmt.Println("Singleton instance created") + }) + return instance +} + +// SetData sets data in the singleton +func (s *Singleton) SetData(key string, value interface{}) { + s.data[key] = value +} + +// GetData gets data from the singleton +func (s *Singleton) GetData(key string) (interface{}, bool) { + val, exists := s.data[key] + return val, exists +} + +// GetCreationTime returns the time when the singleton was created +func (s *Singleton) GetCreationTime() time.Time { + return s.createdTime +} + +// ========== Thread-Safe Database Connection Singleton ========== + +// DatabaseConnection represents a database connection singleton +type DatabaseConnection struct { + isConnected bool + connectionStr string + connectionCount int + mu sync.Mutex +} + +var ( + dbInstance *DatabaseConnection + dbOnce sync.Once +) + +// GetDatabaseInstance returns the database connection singleton instance +func GetDatabaseInstance() *DatabaseConnection { + dbOnce.Do(func() { + dbInstance = &DatabaseConnection{ + isConnected: false, + connectionStr: "", + connectionCount: 0, + } + fmt.Println("Database connection instance created") + }) + return dbInstance +} + +// Connect connects to the database +func (db *DatabaseConnection) Connect(connectionStr string) bool { + db.mu.Lock() + defer db.mu.Unlock() + + if db.isConnected { + db.connectionCount++ + fmt.Printf("Already connected to database. Connection count: %d\n", db.connectionCount) + return true + } + + // Simulate connection + db.connectionStr = connectionStr + db.isConnected = true + db.connectionCount = 1 + fmt.Printf("Connected to database: %s\n", connectionStr) + return true +} + +// Disconnect disconnects from the database +func (db *DatabaseConnection) Disconnect() bool { + db.mu.Lock() + defer db.mu.Unlock() + + if !db.isConnected { + fmt.Println("Not connected to any database") + return false + } + + db.connectionCount-- + if db.connectionCount == 0 { + db.isConnected = false + fmt.Printf("Disconnected from database: %s\n", db.connectionStr) + } else { + fmt.Printf("Connection count decreased. Remaining connections: %d\n", db.connectionCount) + } + + return true +} + +// IsConnected returns whether the database is connected +func (db *DatabaseConnection) IsConnected() bool { + db.mu.Lock() + defer db.mu.Unlock() + return db.isConnected +} + +// GetConnectionCount returns the number of active connections +func (db *DatabaseConnection) GetConnectionCount() int { + db.mu.Lock() + defer db.mu.Unlock() + return db.connectionCount +} + +// ========== Configuration Manager Singleton ========== + +// ConfigManager represents a configuration manager singleton +type ConfigManager struct { + config map[string]interface{} + mu sync.RWMutex +} + +var ( + configInstance *ConfigManager + configOnce sync.Once +) + +// GetConfigManager returns the configuration manager singleton instance +func GetConfigManager() *ConfigManager { + configOnce.Do(func() { + configInstance = &ConfigManager{ + config: map[string]interface{}{ + "app_name": "Singleton Demo", + "version": "1.0.0", + "debug_mode": false, + "max_retries": 3, + "timeout": 30, + }, + } + fmt.Println("Configuration manager instance created") + }) + return configInstance +} + +// GetConfig returns the entire configuration +func (cm *ConfigManager) GetConfig() map[string]interface{} { + cm.mu.RLock() + defer cm.mu.RUnlock() + + // Return a copy to prevent direct modification + configCopy := make(map[string]interface{}) + for k, v := range cm.config { + configCopy[k] = v + } + return configCopy +} + +// GetValue returns a specific configuration value +func (cm *ConfigManager) GetValue(key string) (interface{}, bool) { + cm.mu.RLock() + defer cm.mu.RUnlock() + + val, exists := cm.config[key] + return val, exists +} + +// SetValue sets a specific configuration value +func (cm *ConfigManager) SetValue(key string, value interface{}) { + cm.mu.Lock() + defer cm.mu.Unlock() + + cm.config[key] = value + fmt.Printf("Configuration updated: %s = %v\n", key, value) +} + +// ========== Logger Singleton ========== + +// LogLevel represents the level of a log message +type LogLevel int + +const ( + DEBUG LogLevel = iota + INFO + WARNING + ERROR +) + +// LogEntry represents a log entry +type LogEntry struct { + Timestamp time.Time + Level LogLevel + Message string +} + +// Logger represents a logger singleton +type Logger struct { + logs []LogEntry + mu sync.Mutex +} + +var ( + loggerInstance *Logger + loggerOnce sync.Once +) + +// GetLogger returns the logger singleton instance +func GetLogger() *Logger { + loggerOnce.Do(func() { + loggerInstance = &Logger{ + logs: make([]LogEntry, 0), + } + fmt.Println("Logger instance created") + }) + return loggerInstance +} + +// Log logs a message with the specified level +func (l *Logger) Log(level LogLevel, message string) { + l.mu.Lock() + defer l.mu.Unlock() + + entry := LogEntry{ + Timestamp: time.Now(), + Level: level, + Message: message, + } + l.logs = append(l.logs, entry) + + // Print to console + levelStr := "UNKNOWN" + switch level { + case DEBUG: + levelStr = "DEBUG" + case INFO: + levelStr = "INFO" + case WARNING: + levelStr = "WARNING" + case ERROR: + levelStr = "ERROR" + } + + fmt.Printf("[%s] %s: %s\n", entry.Timestamp.Format("2006-01-02 15:04:05"), levelStr, message) +} + +// Debug logs a debug message +func (l *Logger) Debug(message string) { + l.Log(DEBUG, message) +} + +// Info logs an info message +func (l *Logger) Info(message string) { + l.Log(INFO, message) +} + +// Warning logs a warning message +func (l *Logger) Warning(message string) { + l.Log(WARNING, message) +} + +// Error logs an error message +func (l *Logger) Error(message string) { + l.Log(ERROR, message) +} + +// GetLogs returns all log entries +func (l *Logger) GetLogs() []LogEntry { + l.mu.Lock() + defer l.mu.Unlock() + + // Return a copy to prevent direct modification + logsCopy := make([]LogEntry, len(l.logs)) + copy(logsCopy, l.logs) + return logsCopy +} + +// ClearLogs clears all log entries +func (l *Logger) ClearLogs() { + l.mu.Lock() + defer l.mu.Unlock() + + l.logs = make([]LogEntry, 0) + fmt.Println("Logs cleared") +} + +// ========== Demo Code ========== + +func demonstrateSingletons() { + fmt.Println("===== Simple Singleton Demo =====") + singleton1 := GetInstance() + singleton2 := GetInstance() + + fmt.Printf("Are instances the same? %v\n", singleton1 == singleton2) + fmt.Printf("Creation time: %v\n", singleton1.GetCreationTime().Format("2006-01-02 15:04:05")) + + singleton1.SetData("name", "Singleton Pattern") + singleton1.SetData("language", "Go") + + name, exists := singleton2.GetData("name") + if exists { + fmt.Printf("Data retrieved from singleton2: name = %v\n", name) + } + + fmt.Println("\n===== Database Connection Singleton Demo =====") + db1 := GetDatabaseInstance() + db2 := GetDatabaseInstance() + + fmt.Printf("Are instances the same? %v\n", db1 == db2) + + db1.Connect("postgres://localhost:5432/mydb") + db2.Connect("postgres://localhost:5432/mydb") + fmt.Printf("Connection count: %d\n", db1.GetConnectionCount()) + + db1.Disconnect() + fmt.Printf("Still connected? %v\n", db2.IsConnected()) + + fmt.Println("\n===== Configuration Manager Demo =====") + config1 := GetConfigManager() + config2 := GetConfigManager() + + fmt.Printf("Are instances the same? %v\n", config1 == config2) + + if val, exists := config1.GetValue("app_name"); exists { + fmt.Printf("Config value: app_name = %v\n", val) + } + + config2.SetValue("debug_mode", true) + if val, exists := config1.GetValue("debug_mode"); exists { + fmt.Printf("Updated config from config1: debug_mode = %v\n", val) + } + + fmt.Println("\n===== Logger Demo =====") + logger1 := GetLogger() + logger2 := GetLogger() + + fmt.Printf("Are instances the same? %v\n", logger1 == logger2) + + logger1.Info("Application started") + logger1.Debug("Initializing components") + logger2.Warning("Resource usage is high") + logger1.Error("Failed to connect to service") + + logs := logger2.GetLogs() + fmt.Printf("Log entries: %d\n", len(logs)) +} + +func main() { + // Run the demo + demonstrateSingletons() +} diff --git a/snippets/design-patterns/singleton/singleton_pattern.js b/snippets/design-patterns/singleton/singleton_pattern.js new file mode 100644 index 0000000..e6e7ab8 --- /dev/null +++ b/snippets/design-patterns/singleton/singleton_pattern.js @@ -0,0 +1,396 @@ +/** + * Singleton Pattern Implementation in JavaScript + * + * The Singleton Pattern is a creational design pattern that ensures a class has only one instance + * and provides a global point of access to it. This is useful when exactly one object is needed + * to coordinate actions across the system. + * + * This file demonstrates several ways to implement the Singleton pattern in JavaScript. + */ + +// ========== Classic Singleton Implementation ========== + +/** + * Classic Singleton implementation using a class with a static instance property + */ +class ClassicSingleton { + constructor() { + if (ClassicSingleton._instance) { + return ClassicSingleton._instance; + } + + this.timestamp = new Date().getTime(); + this.config = { + apiUrl: 'https://api.example.com', + timeout: 3000, + retries: 3 + }; + + ClassicSingleton._instance = this; + } + + getConfig() { + return this.config; + } + + updateConfig(newConfig) { + this.config = { ...this.config, ...newConfig }; + console.log('Configuration updated:', this.config); + return this.config; + } + + getTimestamp() { + return this.timestamp; + } + + static getInstance() { + if (!ClassicSingleton._instance) { + ClassicSingleton._instance = new ClassicSingleton(); + } + return ClassicSingleton._instance; + } +} + +// Initialize the static property +ClassicSingleton._instance = null; + +// ========== Module Pattern Singleton Implementation ========== + +/** + * Singleton implementation using the Module pattern with an IIFE + * (Immediately Invoked Function Expression) + */ +const DatabaseConnection = (function() { + // Private variables and methods + let instance; + let connectionCount = 0; + + function createConnection() { + // Private methods + function connect() { + connectionCount++; + return `Connected to database (Connection #${connectionCount})`; + } + + function disconnect() { + if (connectionCount > 0) { + connectionCount--; + return `Disconnected from database (Remaining connections: ${connectionCount})`; + } + return 'No active connections to disconnect'; + } + + // Public interface + return { + connect, + disconnect, + getConnectionStatus: () => connectionCount > 0 ? 'Connected' : 'Disconnected', + getConnectionCount: () => connectionCount + }; + } + + return { + // Public method to get the instance + getInstance: function() { + if (!instance) { + instance = createConnection(); + } + return instance; + } + }; +})(); + +// ========== ES6 Module Singleton Implementation ========== + +/** + * Singleton implementation using ES6 module pattern + * In a real application, this would be in its own file and imported where needed + */ +class Logger { + constructor() { + this.logs = []; + } + + log(message) { + const timestamp = new Date().toISOString(); + const logEntry = `${timestamp}: ${message}`; + this.logs.push(logEntry); + console.log(logEntry); + return logEntry; + } + + warn(message) { + return this.log(`WARNING: ${message}`); + } + + error(message) { + return this.log(`ERROR: ${message}`); + } + + getLogs() { + return this.logs; + } + + clearLogs() { + this.logs = []; + return 'Logs cleared'; + } +} + +// Create the singleton instance +const loggerInstance = new Logger(); + +// Export the instance (not the class) +// In a real ES6 module, we would use: +// export default loggerInstance; + +// ========== Lazy Initialization Singleton ========== + +/** + * Singleton with lazy initialization using a closure + */ +const ConfigManager = (function() { + let instance; + + // Private constructor function + function ConfigManagerConstructor() { + let config = { + theme: 'light', + language: 'en', + notifications: true, + autoSave: true + }; + + return { + getConfig: () => ({ ...config }), + setConfig: (newConfig) => { + config = { ...config, ...newConfig }; + return config; + }, + resetConfig: () => { + config = { + theme: 'light', + language: 'en', + notifications: true, + autoSave: true + }; + return config; + } + }; + } + + return { + // This is the public method to get the singleton instance + getInstance: function() { + if (!instance) { + instance = new ConfigManagerConstructor(); + } + return instance; + } + }; +})(); + +// ========== Singleton with Object Literal ========== + +/** + * Simplest singleton implementation using an object literal + * This is a singleton by nature as objects are passed by reference in JavaScript + */ +const AppState = { + user: null, + isLoggedIn: false, + preferences: {}, + + login(userData) { + this.user = userData; + this.isLoggedIn = true; + console.log(`User ${userData.name} logged in`); + }, + + logout() { + const username = this.user ? this.user.name : 'Unknown'; + this.user = null; + this.isLoggedIn = false; + console.log(`User ${username} logged out`); + }, + + updatePreferences(prefs) { + this.preferences = { ...this.preferences, ...prefs }; + console.log('Preferences updated:', this.preferences); + }, + + getState() { + return { + user: this.user, + isLoggedIn: this.isLoggedIn, + preferences: { ...this.preferences } + }; + } +}; + +// ========== Thread-Safe Singleton with Symbol ========== + +/** + * A more advanced singleton implementation using Symbol to make the instance truly private + */ +const UserManager = (function() { + // Using Symbol to create a truly private instance holder + const _instance = Symbol('UserManagerInstance'); + + // Private data store + const _users = Symbol('users'); + + class UserManagerClass { + constructor() { + // Initialize private data + this[_users] = new Map(); + } + + addUser(id, userData) { + if (this[_users].has(id)) { + throw new Error(`User with ID ${id} already exists`); + } + + this[_users].set(id, { + ...userData, + createdAt: new Date() + }); + + return true; + } + + getUser(id) { + if (!this[_users].has(id)) { + return null; + } + + return { ...this[_users].get(id) }; + } + + updateUser(id, userData) { + if (!this[_users].has(id)) { + throw new Error(`User with ID ${id} does not exist`); + } + + const existingUser = this[_users].get(id); + this[_users].set(id, { + ...existingUser, + ...userData, + updatedAt: new Date() + }); + + return true; + } + + deleteUser(id) { + if (!this[_users].has(id)) { + throw new Error(`User with ID ${id} does not exist`); + } + + return this[_users].delete(id); + } + + getAllUsers() { + return Array.from(this[_users].entries()).map(([id, userData]) => ({ + id, + ...userData + })); + } + + getUserCount() { + return this[_users].size; + } + } + + // Create a container for the singleton instance + const SingletonContainer = { + [_instance]: null, + + getInstance() { + if (!this[_instance]) { + this[_instance] = new UserManagerClass(); + } + + return this[_instance]; + } + }; + + return SingletonContainer; +})(); + +// ========== Demo Code ========== + +function demonstrateSingletons() { + console.log('===== Classic Singleton Demo ====='); + const singleton1 = ClassicSingleton.getInstance(); + const singleton2 = ClassicSingleton.getInstance(); + + console.log('Are instances the same?', singleton1 === singleton2); + console.log('Instance timestamp:', singleton1.getTimestamp()); + console.log('Original config:', singleton1.getConfig()); + + singleton2.updateConfig({ timeout: 5000 }); + console.log('Updated config from singleton1:', singleton1.getConfig()); + + console.log('\n===== Database Connection Singleton Demo ====='); + const db1 = DatabaseConnection.getInstance(); + const db2 = DatabaseConnection.getInstance(); + + console.log('Are instances the same?', db1 === db2); + console.log(db1.connect()); + console.log(db1.connect()); + console.log('Connection count:', db2.getConnectionCount()); + console.log(db2.disconnect()); + console.log('Connection status:', db1.getConnectionStatus()); + + console.log('\n===== Logger Singleton Demo ====='); + loggerInstance.log('Application started'); + loggerInstance.warn('Resource usage is high'); + loggerInstance.error('Failed to connect to service'); + console.log('Log entries:', loggerInstance.getLogs().length); + + console.log('\n===== Config Manager Singleton Demo ====='); + const config1 = ConfigManager.getInstance(); + const config2 = ConfigManager.getInstance(); + + console.log('Are instances the same?', config1 === config2); + console.log('Original config:', config1.getConfig()); + + config2.setConfig({ theme: 'dark', autoSave: false }); + console.log('Updated config from config1:', config1.getConfig()); + + console.log('\n===== App State Singleton Demo ====='); + console.log('Initial state:', AppState.getState()); + + AppState.login({ name: 'John Doe', email: 'john@example.com' }); + AppState.updatePreferences({ theme: 'dark', notifications: false }); + console.log('Updated state:', AppState.getState()); + + console.log('\n===== User Manager Singleton Demo ====='); + const userManager1 = UserManager.getInstance(); + const userManager2 = UserManager.getInstance(); + + console.log('Are instances the same?', userManager1 === userManager2); + + userManager1.addUser(1, { name: 'Alice', email: 'alice@example.com' }); + userManager1.addUser(2, { name: 'Bob', email: 'bob@example.com' }); + + console.log('User count:', userManager2.getUserCount()); + console.log('User #1:', userManager2.getUser(1)); + + userManager2.updateUser(1, { role: 'admin' }); + console.log('Updated User #1:', userManager1.getUser(1)); +} + +// Run the demo +demonstrateSingletons(); + +// Export the singleton classes and instances for use in other modules +module.exports = { + ClassicSingleton, + DatabaseConnection, + Logger: loggerInstance, + ConfigManager, + AppState, + UserManager +}; diff --git a/snippets/design-patterns/singleton/singleton_pattern.php b/snippets/design-patterns/singleton/singleton_pattern.php new file mode 100644 index 0000000..bcaf015 --- /dev/null +++ b/snippets/design-patterns/singleton/singleton_pattern.php @@ -0,0 +1,388 @@ +timestamp = time(); + $this->config = [ + 'api_url' => 'https://api.example.com', + 'timeout' => 3000, + 'retries' => 3 + ]; + } + + // Prevent cloning of the instance + private function __clone() {} + + // Prevent unserialization of the instance + private function __wakeup() {} + + public static function getInstance() { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + public function getConfig() { + return $this->config; + } + + public function updateConfig($key, $value) { + $this->config[$key] = $value; + echo "Configuration updated: {$key} = {$value}\n"; + } + + public function getTimestamp() { + return $this->timestamp; + } +} + +// ========== Database Connection Singleton ========== + +/** + * Singleton implementation for database connection + */ +class DatabaseConnection { + private static $instance = null; + private $isConnected = false; + private $connectionString = ''; + private $connectionCount = 0; + + // Private constructor to prevent direct instantiation + private function __construct() {} + + // Prevent cloning of the instance + private function __clone() {} + + // Prevent unserialization of the instance + private function __wakeup() {} + + public static function getInstance() { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + public function connect($connectionString) { + if ($this->isConnected) { + $this->connectionCount++; + echo "Already connected to database. Connection count: {$this->connectionCount}\n"; + return true; + } + + // Simulate connection + $this->connectionString = $connectionString; + $this->isConnected = true; + $this->connectionCount = 1; + echo "Connected to database: {$connectionString}\n"; + return true; + } + + public function disconnect() { + if (!$this->isConnected) { + echo "Not connected to any database.\n"; + return false; + } + + $this->connectionCount--; + if ($this->connectionCount === 0) { + $this->isConnected = false; + echo "Disconnected from database: {$this->connectionString}\n"; + } else { + echo "Connection count decreased. Remaining connections: {$this->connectionCount}\n"; + } + + return true; + } + + public function isConnected() { + return $this->isConnected; + } + + public function getConnectionCount() { + return $this->connectionCount; + } +} + +// ========== Logger Singleton ========== + +/** + * Singleton implementation for logging + */ +class Logger { + private static $instance = null; + private $logs = []; + + // Private constructor to prevent direct instantiation + private function __construct() {} + + // Prevent cloning of the instance + private function __clone() {} + + // Prevent unserialization of the instance + private function __wakeup() {} + + public static function getInstance() { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + public function log($message) { + $timestamp = date('Y-m-d H:i:s'); + $logEntry = "{$timestamp}: {$message}"; + $this->logs[] = $logEntry; + echo $logEntry . PHP_EOL; + return $logEntry; + } + + public function warn($message) { + return $this->log("WARNING: {$message}"); + } + + public function error($message) { + return $this->log("ERROR: {$message}"); + } + + public function getLogs() { + return $this->logs; + } + + public function clearLogs() { + $this->logs = []; + echo "Logs cleared" . PHP_EOL; + return 'Logs cleared'; + } +} + +// ========== Configuration Manager Singleton ========== + +/** + * Singleton implementation for configuration management + */ +class ConfigManager { + private static $instance = null; + private $config; + + // Private constructor to prevent direct instantiation + private function __construct() { + $this->config = [ + 'theme' => 'light', + 'language' => 'en', + 'notifications' => true, + 'auto_save' => true + ]; + } + + // Prevent cloning of the instance + private function __clone() {} + + // Prevent unserialization of the instance + private function __wakeup() {} + + public static function getInstance() { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + public function getConfig() { + return $this->config; + } + + public function setConfig($key, $value) { + $this->config[$key] = $value; + echo "Configuration updated: {$key} = {$value}" . PHP_EOL; + return $this->config; + } + + public function resetConfig() { + $this->config = [ + 'theme' => 'light', + 'language' => 'en', + 'notifications' => true, + 'auto_save' => true + ]; + echo "Configuration reset to defaults" . PHP_EOL; + return $this->config; + } +} + +// ========== User Manager Singleton ========== + +/** + * Singleton implementation for user management + */ +class UserManager { + private static $instance = null; + private $users = []; + + // Private constructor to prevent direct instantiation + private function __construct() {} + + // Prevent cloning of the instance + private function __clone() {} + + // Prevent unserialization of the instance + private function __wakeup() {} + + public static function getInstance() { + if (self::$instance === null) { + self::$instance = new self(); + } + return self::$instance; + } + + public function addUser($id, $userData) { + if (isset($this->users[$id])) { + throw new Exception("User with ID {$id} already exists"); + } + + $this->users[$id] = array_merge($userData, [ + 'created_at' => date('Y-m-d H:i:s') + ]); + + return true; + } + + public function getUser($id) { + if (!isset($this->users[$id])) { + return null; + } + + return $this->users[$id]; + } + + public function updateUser($id, $userData) { + if (!isset($this->users[$id])) { + throw new Exception("User with ID {$id} does not exist"); + } + + $this->users[$id] = array_merge($this->users[$id], $userData, [ + 'updated_at' => date('Y-m-d H:i:s') + ]); + + return true; + } + + public function deleteUser($id) { + if (!isset($this->users[$id])) { + throw new Exception("User with ID {$id} does not exist"); + } + + unset($this->users[$id]); + return true; + } + + public function getAllUsers() { + $result = []; + foreach ($this->users as $id => $userData) { + $result[] = array_merge(['id' => $id], $userData); + } + return $result; + } + + public function getUserCount() { + return count($this->users); + } +} + +// ========== Demo Code ========== + +function demonstrateSingletons() { + echo "===== Classic Singleton Demo =====" . PHP_EOL; + $singleton1 = ClassicSingleton::getInstance(); + $singleton2 = ClassicSingleton::getInstance(); + + echo "Are instances the same? " . ($singleton1 === $singleton2 ? "Yes" : "No") . PHP_EOL; + echo "Instance timestamp: " . date('Y-m-d H:i:s', $singleton1->getTimestamp()) . PHP_EOL; + + $config = $singleton1->getConfig(); + echo "Original config: api_url = " . $config['api_url'] . PHP_EOL; + + $singleton2->updateConfig('timeout', 5000); + $config = $singleton1->getConfig(); + echo "Updated config from singleton1: timeout = " . $config['timeout'] . PHP_EOL; + + echo PHP_EOL . "===== Database Connection Singleton Demo =====" . PHP_EOL; + $db1 = DatabaseConnection::getInstance(); + $db2 = DatabaseConnection::getInstance(); + + echo "Are instances the same? " . ($db1 === $db2 ? "Yes" : "No") . PHP_EOL; + + $db1->connect("mysql://localhost:3306/mydb"); + $db2->connect("mysql://localhost:3306/mydb"); + echo "Connection count: " . $db1->getConnectionCount() . PHP_EOL; + + $db1->disconnect(); + echo "Still connected? " . ($db2->isConnected() ? "Yes" : "No") . PHP_EOL; + + echo PHP_EOL . "===== Logger Singleton Demo =====" . PHP_EOL; + $logger1 = Logger::getInstance(); + $logger2 = Logger::getInstance(); + + echo "Are instances the same? " . ($logger1 === $logger2 ? "Yes" : "No") . PHP_EOL; + + $logger1->log("Application started"); + $logger1->warn("Resource usage is high"); + $logger2->error("Failed to connect to service"); + + $logs = $logger1->getLogs(); + echo "Log entries: " . count($logs) . PHP_EOL; + + echo PHP_EOL . "===== Config Manager Singleton Demo =====" . PHP_EOL; + $config1 = ConfigManager::getInstance(); + $config2 = ConfigManager::getInstance(); + + echo "Are instances the same? " . ($config1 === $config2 ? "Yes" : "No") . PHP_EOL; + + $configSettings = $config1->getConfig(); + echo "Config value: theme = " . $configSettings['theme'] . PHP_EOL; + + $config2->setConfig('theme', 'dark'); + $configSettings = $config1->getConfig(); + echo "Updated config from config1: theme = " . $configSettings['theme'] . PHP_EOL; + + echo PHP_EOL . "===== User Manager Singleton Demo =====" . PHP_EOL; + $userManager1 = UserManager::getInstance(); + $userManager2 = UserManager::getInstance(); + + echo "Are instances the same? " . ($userManager1 === $userManager2 ? "Yes" : "No") . PHP_EOL; + + $userManager1->addUser(1, ['name' => 'Alice', 'email' => 'alice@example.com']); + $userManager1->addUser(2, ['name' => 'Bob', 'email' => 'bob@example.com']); + + echo "User count: " . $userManager2->getUserCount() . PHP_EOL; + + $user = $userManager2->getUser(1); + echo "User #1: " . $user['name'] . ", " . $user['email'] . PHP_EOL; + + $userManager2->updateUser(1, ['role' => 'admin']); + $user = $userManager1->getUser(1); + echo "Updated User #1: " . $user['name'] . ", " . $user['email'] . ", " . $user['role'] . PHP_EOL; +} + +// Run the demo +demonstrateSingletons(); diff --git a/snippets/design-patterns/singleton/singleton_pattern.py b/snippets/design-patterns/singleton/singleton_pattern.py new file mode 100644 index 0000000..daee333 --- /dev/null +++ b/snippets/design-patterns/singleton/singleton_pattern.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +""" +Singleton Pattern Implementation in Python + +This demonstrates various implementations of the Singleton pattern in Python. +""" + + +# Method 1: Classic Implementation (Not Thread-Safe) +class ClassicSingleton: + """ + Classic implementation of the Singleton pattern. + Not thread-safe, but simple and effective for single-threaded applications. + """ + _instance = None + + def __new__(cls): + if cls._instance is None: + print("Creating new instance of ClassicSingleton") + cls._instance = super(ClassicSingleton, cls).__new__(cls) + cls._instance.initialize() + return cls._instance + + def initialize(self): + """Initialize the singleton instance""" + self.value = 0 + + def increment(self): + """Increment the value and return it""" + self.value += 1 + return self.value + + +# Method 2: Decorator-based implementation +def singleton(cls): + """ + Singleton decorator. + Makes any class a Singleton by replacing its __new__ method. + """ + instances = {} + + def get_instance(*args, **kwargs): + if cls not in instances: + print(f"Creating new instance of {cls.__name__} via decorator") + instances[cls] = cls(*args, **kwargs) + return instances[cls] + + return get_instance + + +@singleton +class DecoratedSingleton: + """Singleton class implemented using a decorator""" + + def __init__(self): + self.value = 0 + + def increment(self): + """Increment the value and return it""" + self.value += 1 + return self.value + + +# Method 3: Metaclass-based implementation +class SingletonMeta(type): + """ + Metaclass for implementing the Singleton pattern. + More advanced and flexible than other implementations. + """ + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + print(f"Creating new instance of {cls.__name__} via metaclass") + cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +class MetaclassSingleton(metaclass=SingletonMeta): + """Singleton class implemented using a metaclass""" + + def __init__(self): + self.value = 0 + + def increment(self): + """Increment the value and return it""" + self.value += 1 + return self.value + + +# Method 4: Module-level Singleton +# This approach leverages Python's module import system to create singletons +# Since modules are imported only once, objects defined at module level are singletons + +class _ModuleSingleton: + """ + Private class for module-level singleton. + The actual instance is exposed as a module-level variable. + """ + def __init__(self): + print("Creating module-level Singleton") + self.value = 0 + + def increment(self): + """Increment the value and return it""" + self.value += 1 + return self.value + + +# The single instance that will be imported elsewhere +module_singleton = _ModuleSingleton() + + +# Method 5: Borg pattern (Monostate) +# All instances share state, but are not the same object +class BorgSingleton: + """ + Implementation of the Borg pattern. + All instances share state but are not the same object. + """ + _shared_state = {} + + def __init__(self): + # Share the __dict__ (state) among all instances + self.__dict__ = self._shared_state + if not self._shared_state: + print("Initializing Borg Singleton shared state") + self.value = 0 + + def increment(self): + """Increment the value and return it""" + self.value += 1 + return self.value + + +# Method 6: Thread-safe Singleton with double-checked locking +import threading + +class ThreadSafeSingleton: + """ + Thread-safe implementation of the Singleton pattern. + Uses a lock to prevent race conditions. + """ + _instance = None + _lock = threading.Lock() + + def __new__(cls): + # Double-checked locking pattern + if cls._instance is None: + with cls._lock: + if cls._instance is None: # Check again after acquiring the lock + print("Creating thread-safe Singleton instance") + cls._instance = super(ThreadSafeSingleton, cls).__new__(cls) + cls._instance.initialize() + return cls._instance + + def initialize(self): + """Initialize the singleton instance""" + self.value = 0 + + def increment(self): + """Increment the value and return it""" + self.value += 1 + return self.value + + +# Example usage +def demonstrate_classic_singleton(): + """Demonstrate classic singleton pattern""" + print("\n=== Classic Singleton ===") + s1 = ClassicSingleton() + s2 = ClassicSingleton() + + print(f"s1 is s2: {s1 is s2}") + print(f"s1.increment(): {s1.increment()}") + print(f"s2.increment(): {s2.increment()}") + print(f"s1.value: {s1.value}, s2.value: {s2.value}") + + +def demonstrate_decorated_singleton(): + """Demonstrate decorator-based singleton pattern""" + print("\n=== Decorated Singleton ===") + s1 = DecoratedSingleton() + s2 = DecoratedSingleton() + + print(f"s1 is s2: {s1 is s2}") + print(f"s1.increment(): {s1.increment()}") + print(f"s2.increment(): {s2.increment()}") + print(f"s1.value: {s1.value}, s2.value: {s2.value}") + + +def demonstrate_metaclass_singleton(): + """Demonstrate metaclass-based singleton pattern""" + print("\n=== Metaclass Singleton ===") + s1 = MetaclassSingleton() + s2 = MetaclassSingleton() + + print(f"s1 is s2: {s1 is s2}") + print(f"s1.increment(): {s1.increment()}") + print(f"s2.increment(): {s2.increment()}") + print(f"s1.value: {s1.value}, s2.value: {s2.value}") + + +def demonstrate_module_singleton(): + """Demonstrate module-level singleton pattern""" + print("\n=== Module Singleton ===") + # We would normally import this, but here we use the one defined above + s1 = module_singleton + s2 = module_singleton + + print(f"s1 is s2: {s1 is s2}") + print(f"s1.increment(): {s1.increment()}") + print(f"s2.increment(): {s2.increment()}") + print(f"s1.value: {s1.value}, s2.value: {s2.value}") + + +def demonstrate_borg_singleton(): + """Demonstrate Borg pattern (monostate)""" + print("\n=== Borg Singleton (Monostate) ===") + s1 = BorgSingleton() + s2 = BorgSingleton() + + print(f"s1 is s2: {s1 is s2}") # Will be False! + print(f"s1.increment(): {s1.increment()}") + print(f"s2.increment(): {s2.increment()}") + print(f"s1.value: {s1.value}, s2.value: {s2.value}") + + +def demonstrate_thread_safe_singleton(): + """Demonstrate thread-safe singleton pattern""" + print("\n=== Thread-Safe Singleton ===") + s1 = ThreadSafeSingleton() + s2 = ThreadSafeSingleton() + + print(f"s1 is s2: {s1 is s2}") + print(f"s1.increment(): {s1.increment()}") + print(f"s2.increment(): {s2.increment()}") + print(f"s1.value: {s1.value}, s2.value: {s2.value}") + + +def main(): + """Run demonstrations of all singleton implementations""" + print("Singleton Pattern Demonstrations") + print("===============================") + + demonstrate_classic_singleton() + demonstrate_decorated_singleton() + demonstrate_metaclass_singleton() + demonstrate_module_singleton() + demonstrate_borg_singleton() + demonstrate_thread_safe_singleton() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/snippets/design-patterns/singleton/singleton_pattern.rb b/snippets/design-patterns/singleton/singleton_pattern.rb new file mode 100644 index 0000000..282c414 --- /dev/null +++ b/snippets/design-patterns/singleton/singleton_pattern.rb @@ -0,0 +1,364 @@ +#!/usr/bin/env ruby + +# Singleton Pattern Implementation in Ruby +# +# The Singleton Pattern is a creational design pattern that ensures a class has only one instance +# and provides a global point of access to it. This is useful when exactly one object is needed +# to coordinate actions across the system. +# +# This file demonstrates several ways to implement the Singleton pattern in Ruby. + +require 'singleton' +require 'time' + +# ========== Classic Singleton Implementation ========== + +# Classic Singleton implementation using Ruby's built-in Singleton module +class ClassicSingleton + include Singleton + + attr_reader :timestamp + + def initialize + @timestamp = Time.now + @config = { + 'api_url' => 'https://api.example.com', + 'timeout' => 3000, + 'retries' => 3 + } + end + + def get_config + @config.clone + end + + def update_config(key, value) + @config[key] = value + puts "Configuration updated: #{key} = #{value}" + end +end + +# ========== Thread-Safe Singleton Implementation ========== + +# Thread-safe Singleton implementation using a mutex for synchronization +class ThreadSafeSingleton + # Class variable to store the singleton instance + @@instance = nil + @@mutex = Mutex.new + + # Private class method to create the instance + def self.instance + return @@instance if @@instance + + @@mutex.synchronize do + # Check again in case another thread created the instance while we were waiting + @@instance ||= new + end + + @@instance + end + + # Make new and clone private to prevent creating new instances + private_class_method :new + private :clone + + attr_reader :connection_count, :connected + alias_method :connected?, :connected + + def initialize + @connection_count = 0 + @connected = false + @connection_string = '' + end + + def connect(connection_string) + if @connected + @connection_count += 1 + puts "Already connected to database. Connection count: #{@connection_count}" + return true + end + + # Simulate connection + @connection_string = connection_string + @connected = true + @connection_count = 1 + puts "Connected to database: #{connection_string}" + true + end + + def disconnect + unless @connected + puts "Not connected to any database." + return false + end + + @connection_count -= 1 + if @connection_count == 0 + @connected = false + puts "Disconnected from database: #{@connection_string}" + else + puts "Connection count decreased. Remaining connections: #{@connection_count}" + end + + true + end +end + +# ========== Logger Singleton ========== + +# Logger Singleton implementation +class Logger + # Class variable to store the singleton instance + @@instance = nil + + # Class method to access the singleton instance + def self.instance + @@instance ||= new + end + + # Make new private to prevent creating new instances + private_class_method :new + + def initialize + @logs = [] + end + + def log(message) + timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S") + log_entry = "#{timestamp}: #{message}" + @logs << log_entry + puts log_entry + log_entry + end + + def warn(message) + log("WARNING: #{message}") + end + + def error(message) + log("ERROR: #{message}") + end + + def get_logs + @logs.clone + end + + def clear_logs + @logs = [] + puts "Logs cleared" + "Logs cleared" + end +end + +# ========== Configuration Manager Singleton ========== + +# Configuration Manager Singleton implementation +class ConfigManager + # Class variable to store the singleton instance + @@instance = nil + + # Class method to access the singleton instance + def self.instance + @@instance ||= new + end + + # Make new private to prevent creating new instances + private_class_method :new + + def initialize + @config = { + 'theme' => 'light', + 'language' => 'en', + 'notifications' => true, + 'auto_save' => true + } + end + + def get_config + @config.clone + end + + def set_config(key, value) + @config[key] = value + puts "Configuration updated: #{key} = #{value}" + @config + end + + def reset_config + @config = { + 'theme' => 'light', + 'language' => 'en', + 'notifications' => true, + 'auto_save' => true + } + puts "Configuration reset to defaults" + @config + end +end + +# ========== User Manager Singleton ========== + +# User Manager Singleton implementation +class UserManager + # Class variable to store the singleton instance + @@instance = nil + @@mutex = Mutex.new + + # Class method to access the singleton instance + def self.instance + return @@instance if @@instance + + @@mutex.synchronize do + @@instance ||= new + end + + @@instance + end + + # Make new private to prevent creating new instances + private_class_method :new + + def initialize + @users = {} + end + + def add_user(id, user_data) + @@mutex.synchronize do + if @users.key?(id) + raise "User with ID #{id} already exists" + end + + @users[id] = user_data.merge({ + 'created_at' => Time.now + }) + + true + end + end + + def get_user(id) + @@mutex.synchronize do + return nil unless @users.key?(id) + @users[id].clone + end + end + + def update_user(id, user_data) + @@mutex.synchronize do + unless @users.key?(id) + raise "User with ID #{id} does not exist" + end + + @users[id] = @users[id].merge(user_data).merge({ + 'updated_at' => Time.now + }) + + true + end + end + + def delete_user(id) + @@mutex.synchronize do + unless @users.key?(id) + raise "User with ID #{id} does not exist" + end + + @users.delete(id) + true + end + end + + def get_all_users + @@mutex.synchronize do + result = [] + @users.each do |id, user_data| + result << { 'id' => id }.merge(user_data) + end + result + end + end + + def user_count + @@mutex.synchronize do + @users.size + end + end +end + +# ========== Demo Code ========== + +def demonstrate_singletons + puts "===== Classic Singleton Demo =====" + singleton1 = ClassicSingleton.instance + singleton2 = ClassicSingleton.instance + + puts "Are instances the same? #{singleton1.equal?(singleton2) ? 'Yes' : 'No'}" + puts "Instance timestamp: #{singleton1.timestamp}" + + config = singleton1.get_config + puts "Original config: api_url = #{config['api_url']}" + + singleton2.update_config('timeout', 5000) + config = singleton1.get_config + puts "Updated config from singleton1: timeout = #{config['timeout']}" + + puts "\n===== Thread-Safe Singleton Demo =====" + db1 = ThreadSafeSingleton.instance + db2 = ThreadSafeSingleton.instance + + puts "Are instances the same? #{db1.equal?(db2) ? 'Yes' : 'No'}" + + db1.connect("mysql://localhost:3306/mydb") + db2.connect("mysql://localhost:3306/mydb") + puts "Connection count: #{db1.connection_count}" + + db1.disconnect + puts "Still connected? #{db2.connected? ? 'Yes' : 'No'}" + + puts "\n===== Logger Singleton Demo =====" + logger1 = Logger.instance + logger2 = Logger.instance + + puts "Are instances the same? #{logger1.equal?(logger2) ? 'Yes' : 'No'}" + + logger1.log("Application started") + logger1.warn("Resource usage is high") + logger2.error("Failed to connect to service") + + logs = logger2.get_logs + puts "Log entries: #{logs.size}" + + puts "\n===== Config Manager Singleton Demo =====" + config1 = ConfigManager.instance + config2 = ConfigManager.instance + + puts "Are instances the same? #{config1.equal?(config2) ? 'Yes' : 'No'}" + + config_settings = config1.get_config + puts "Config value: theme = #{config_settings['theme']}" + + config2.set_config('theme', 'dark') + config_settings = config1.get_config + puts "Updated config from config1: theme = #{config_settings['theme']}" + + puts "\n===== User Manager Singleton Demo =====" + user_manager1 = UserManager.instance + user_manager2 = UserManager.instance + + puts "Are instances the same? #{user_manager1.equal?(user_manager2) ? 'Yes' : 'No'}" + + user_manager1.add_user(1, { 'name' => 'Alice', 'email' => 'alice@example.com' }) + user_manager1.add_user(2, { 'name' => 'Bob', 'email' => 'bob@example.com' }) + + puts "User count: #{user_manager2.user_count}" + + user = user_manager2.get_user(1) + puts "User #1: #{user['name']}, #{user['email']}" + + user_manager2.update_user(1, { 'role' => 'admin' }) + user = user_manager1.get_user(1) + puts "Updated User #1: #{user['name']}, #{user['email']}, #{user['role']}" +end + +# Run the demo +demonstrate_singletons diff --git a/snippets/design-patterns/singleton/singleton_pattern.rs b/snippets/design-patterns/singleton/singleton_pattern.rs new file mode 100644 index 0000000..44ba206 --- /dev/null +++ b/snippets/design-patterns/singleton/singleton_pattern.rs @@ -0,0 +1,466 @@ +/** + * Singleton Pattern Implementation in Rust + * + * The Singleton Pattern is a creational design pattern that ensures a class has only one instance + * and provides a global point of access to it. This is useful when exactly one object is needed + * to coordinate actions across the system. + * + * This file demonstrates several ways to implement the Singleton pattern in Rust. + */ + +use std::collections::HashMap; +use std::sync::{Arc, Mutex, Once}; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::fmt; + +// ========== Lazy Static Singleton Implementation ========== + +// Lazy static is a common way to implement singletons in Rust +// This requires the lazy_static crate +#[cfg(feature = "lazy_static")] +mod lazy_static_singleton { + use super::*; + use lazy_static::lazy_static; + + #[derive(Debug)] + pub struct ClassicSingleton { + timestamp: SystemTime, + config: Mutex>, + } + + impl ClassicSingleton { + fn new() -> Self { + let mut config = HashMap::new(); + config.insert("api_url".to_string(), "https://api.example.com".to_string()); + config.insert("timeout".to_string(), "3000".to_string()); + config.insert("retries".to_string(), "3".to_string()); + + ClassicSingleton { + timestamp: SystemTime::now(), + config: Mutex::new(config), + } + } + + pub fn get_config(&self) -> HashMap { + let config = self.config.lock().unwrap(); + config.clone() + } + + pub fn update_config(&self, key: &str, value: &str) { + let mut config = self.config.lock().unwrap(); + config.insert(key.to_string(), value.to_string()); + println!("Configuration updated: {} = {}", key, value); + } + + pub fn get_timestamp(&self) -> SystemTime { + self.timestamp + } + } + + lazy_static! { + pub static ref INSTANCE: ClassicSingleton = ClassicSingleton::new(); + } +} + +// ========== Once Cell Singleton Implementation ========== + +// Once Cell is a more modern approach in Rust's standard library +mod once_cell_singleton { + use super::*; + use std::sync::OnceLock; + + #[derive(Debug)] + pub struct DatabaseConnection { + connection_count: Mutex, + is_connected: Mutex, + connection_string: Mutex, + } + + impl DatabaseConnection { + fn new() -> Self { + DatabaseConnection { + connection_count: Mutex::new(0), + is_connected: Mutex::new(false), + connection_string: Mutex::new(String::new()), + } + } + + pub fn connect(&self, connection_string: &str) -> bool { + let mut is_connected = self.is_connected.lock().unwrap(); + let mut count = self.connection_count.lock().unwrap(); + let mut conn_str = self.connection_string.lock().unwrap(); + + if *is_connected { + *count += 1; + println!("Already connected to database. Connection count: {}", *count); + return true; + } + + // Simulate connection + *conn_str = connection_string.to_string(); + *is_connected = true; + *count = 1; + println!("Connected to database: {}", connection_string); + true + } + + pub fn disconnect(&self) -> bool { + let mut is_connected = self.is_connected.lock().unwrap(); + let mut count = self.connection_count.lock().unwrap(); + let conn_str = self.connection_string.lock().unwrap(); + + if !*is_connected { + println!("Not connected to any database."); + return false; + } + + *count -= 1; + if *count == 0 { + *is_connected = false; + println!("Disconnected from database: {}", *conn_str); + } else { + println!("Connection count decreased. Remaining connections: {}", *count); + } + + true + } + + pub fn is_connected(&self) -> bool { + let is_connected = self.is_connected.lock().unwrap(); + *is_connected + } + + pub fn connection_count(&self) -> i32 { + let count = self.connection_count.lock().unwrap(); + *count + } + } + + pub fn instance() -> &'static DatabaseConnection { + static INSTANCE: OnceLock = OnceLock::new(); + INSTANCE.get_or_init(|| DatabaseConnection::new()) + } +} + +// ========== Thread-Safe Singleton with Once ========== + +// Traditional thread-safe singleton using Once +mod thread_safe_singleton { + use super::*; + + pub struct Logger { + logs: Mutex>, + } + + impl Logger { + fn new() -> Self { + Logger { + logs: Mutex::new(Vec::new()), + } + } + + pub fn log(&self, message: &str) -> String { + let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); + let log_entry = format!("{}: {}", timestamp, message); + + let mut logs = self.logs.lock().unwrap(); + logs.push(log_entry.clone()); + println!("{}", log_entry); + + log_entry + } + + pub fn warn(&self, message: &str) -> String { + self.log(&format!("WARNING: {}", message)) + } + + pub fn error(&self, message: &str) -> String { + self.log(&format!("ERROR: {}", message)) + } + + pub fn get_logs(&self) -> Vec { + let logs = self.logs.lock().unwrap(); + logs.clone() + } + + pub fn clear_logs(&self) -> &str { + let mut logs = self.logs.lock().unwrap(); + logs.clear(); + println!("Logs cleared"); + "Logs cleared" + } + } + + // Static instance with Once initialization + pub fn get_instance() -> &'static Logger { + static mut INSTANCE: Option = None; + static ONCE: Once = Once::new(); + + unsafe { + ONCE.call_once(|| { + INSTANCE = Some(Logger::new()); + }); + + INSTANCE.as_ref().unwrap() + } + } +} + +// ========== Arc-Mutex Singleton Implementation ========== + +// A more idiomatic Rust approach using Arc and Mutex +mod arc_mutex_singleton { + use super::*; + + #[derive(Debug, Clone)] + pub struct ConfigManager { + config: Arc>>, + } + + impl ConfigManager { + fn new() -> Self { + let mut config = HashMap::new(); + config.insert("theme".to_string(), "light".to_string()); + config.insert("language".to_string(), "en".to_string()); + config.insert("notifications".to_string(), "true".to_string()); + config.insert("auto_save".to_string(), "true".to_string()); + + ConfigManager { + config: Arc::new(Mutex::new(config)), + } + } + + pub fn get_config(&self) -> HashMap { + let config = self.config.lock().unwrap(); + config.clone() + } + + pub fn set_config(&self, key: &str, value: &str) -> HashMap { + let mut config = self.config.lock().unwrap(); + config.insert(key.to_string(), value.to_string()); + println!("Configuration updated: {} = {}", key, value); + config.clone() + } + + pub fn reset_config(&self) -> HashMap { + let mut config = self.config.lock().unwrap(); + config.clear(); + config.insert("theme".to_string(), "light".to_string()); + config.insert("language".to_string(), "en".to_string()); + config.insert("notifications".to_string(), "true".to_string()); + config.insert("auto_save".to_string(), "true".to_string()); + println!("Configuration reset to defaults"); + config.clone() + } + } + + // Singleton instance using lazy_static or once_cell + use std::sync::OnceLock; + + pub fn instance() -> &'static ConfigManager { + static INSTANCE: OnceLock = OnceLock::new(); + INSTANCE.get_or_init(|| ConfigManager::new()) + } +} + +// ========== User Manager Singleton ========== + +// User Manager Singleton implementation +mod user_manager_singleton { + use super::*; + use std::collections::HashMap; + use chrono::{DateTime, Local}; + + #[derive(Debug, Clone)] + pub struct UserData { + pub name: String, + pub email: String, + pub role: Option, + pub created_at: DateTime, + pub updated_at: Option>, + } + + impl fmt::Display for UserData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "User {{ name: {}, email: {}, role: {:?} }}", + self.name, self.email, self.role) + } + } + + #[derive(Debug)] + pub struct UserManager { + users: Mutex>, + } + + impl UserManager { + fn new() -> Self { + UserManager { + users: Mutex::new(HashMap::new()), + } + } + + pub fn add_user(&self, id: i32, name: &str, email: &str) -> Result<(), String> { + let mut users = self.users.lock().unwrap(); + + if users.contains_key(&id) { + return Err(format!("User with ID {} already exists", id)); + } + + users.insert(id, UserData { + name: name.to_string(), + email: email.to_string(), + role: None, + created_at: Local::now(), + updated_at: None, + }); + + Ok(()) + } + + pub fn get_user(&self, id: i32) -> Option { + let users = self.users.lock().unwrap(); + users.get(&id).cloned() + } + + pub fn update_user(&self, id: i32, name: Option<&str>, email: Option<&str>, role: Option<&str>) -> Result<(), String> { + let mut users = self.users.lock().unwrap(); + + if !users.contains_key(&id) { + return Err(format!("User with ID {} does not exist", id)); + } + + let user = users.get_mut(&id).unwrap(); + + if let Some(name_val) = name { + user.name = name_val.to_string(); + } + + if let Some(email_val) = email { + user.email = email_val.to_string(); + } + + if let Some(role_val) = role { + user.role = Some(role_val.to_string()); + } + + user.updated_at = Some(Local::now()); + + Ok(()) + } + + pub fn delete_user(&self, id: i32) -> Result<(), String> { + let mut users = self.users.lock().unwrap(); + + if !users.contains_key(&id) { + return Err(format!("User with ID {} does not exist", id)); + } + + users.remove(&id); + Ok(()) + } + + pub fn get_all_users(&self) -> Vec<(i32, UserData)> { + let users = self.users.lock().unwrap(); + users.iter().map(|(&id, user)| (id, user.clone())).collect() + } + + pub fn user_count(&self) -> usize { + let users = self.users.lock().unwrap(); + users.len() + } + } + + // Singleton instance using OnceLock + use std::sync::OnceLock; + + pub fn instance() -> &'static UserManager { + static INSTANCE: OnceLock = OnceLock::new(); + INSTANCE.get_or_init(|| UserManager::new()) + } +} + +// ========== Demo Code ========== + +fn demonstrate_singletons() { + // Note: In a real application, you'd need to add the lazy_static crate + // to your Cargo.toml and uncomment this section + /* + println!("===== Classic Singleton Demo ====="); + let singleton1 = &lazy_static_singleton::INSTANCE; + let singleton2 = &lazy_static_singleton::INSTANCE; + + println!("Are instances the same? {}", std::ptr::eq(singleton1, singleton2)); + println!("Instance timestamp: {:?}", singleton1.get_timestamp()); + + let config = singleton1.get_config(); + println!("Original config: api_url = {}", config.get("api_url").unwrap()); + + singleton2.update_config("timeout", "5000"); + let config = singleton1.get_config(); + println!("Updated config from singleton1: timeout = {}", config.get("timeout").unwrap()); + */ + + println!("\n===== Once Cell Singleton Demo ====="); + let db1 = once_cell_singleton::instance(); + let db2 = once_cell_singleton::instance(); + + println!("Are instances the same? {}", std::ptr::eq(db1, db2)); + + db1.connect("mysql://localhost:3306/mydb"); + db2.connect("mysql://localhost:3306/mydb"); + println!("Connection count: {}", db1.connection_count()); + + db1.disconnect(); + println!("Still connected? {}", db2.is_connected()); + + println!("\n===== Thread-Safe Singleton Demo ====="); + let logger1 = thread_safe_singleton::get_instance(); + let logger2 = thread_safe_singleton::get_instance(); + + println!("Are instances the same? {}", std::ptr::eq(logger1, logger2)); + + logger1.log("Application started"); + logger1.warn("Resource usage is high"); + logger2.error("Failed to connect to service"); + + let logs = logger2.get_logs(); + println!("Log entries: {}", logs.len()); + + println!("\n===== Arc-Mutex Singleton Demo ====="); + let config1 = arc_mutex_singleton::instance(); + let config2 = arc_mutex_singleton::instance(); + + println!("Are instances the same? {}", std::ptr::eq(config1, config2)); + + let config_settings = config1.get_config(); + println!("Config value: theme = {}", config_settings.get("theme").unwrap()); + + config2.set_config("theme", "dark"); + let config_settings = config1.get_config(); + println!("Updated config from config1: theme = {}", config_settings.get("theme").unwrap()); + + println!("\n===== User Manager Singleton Demo ====="); + let user_manager1 = user_manager_singleton::instance(); + let user_manager2 = user_manager_singleton::instance(); + + println!("Are instances the same? {}", std::ptr::eq(user_manager1, user_manager2)); + + user_manager1.add_user(1, "Alice", "alice@example.com").unwrap(); + user_manager1.add_user(2, "Bob", "bob@example.com").unwrap(); + + println!("User count: {}", user_manager2.user_count()); + + if let Some(user) = user_manager2.get_user(1) { + println!("User #1: {}, {}", user.name, user.email); + } + + user_manager2.update_user(1, None, None, Some("admin")).unwrap(); + if let Some(user) = user_manager1.get_user(1) { + println!("Updated User #1: {}, {}, {:?}", user.name, user.email, user.role); + } +} + +fn main() { + // Run the demo + demonstrate_singletons(); +} diff --git a/snippets/devops/ci_cd_pipeline.sh b/snippets/devops/ci_cd_pipeline.sh new file mode 100644 index 0000000..af80d42 --- /dev/null +++ b/snippets/devops/ci_cd_pipeline.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# +# Simple CI/CD Pipeline Script +# This script demonstrates a basic CI/CD pipeline for a Node.js application +# + +# Exit immediately if a command exits with a non-zero status +set -e + +# Print commands and their arguments as they are executed +set -x + +# Define variables +APP_NAME="my-nodejs-app" +REPO_URL="https://github.com/username/my-nodejs-app.git" +DEPLOY_DIR="/var/www/my-nodejs-app" +LOG_FILE="pipeline.log" + +# Function to log messages +log_message() { + local timestamp=$(date +"%Y-%m-%d %H:%M:%S") + echo "[$timestamp] $1" | tee -a $LOG_FILE +} + +# Function to handle errors +handle_error() { + log_message "ERROR: An error occurred on line $1" + exit 1 +} + +# Set up error handling +trap 'handle_error $LINENO' ERR + +# Start the pipeline +log_message "Starting CI/CD pipeline for $APP_NAME" + +# Step 1: Clean workspace +log_message "Step 1: Cleaning workspace" +rm -rf build +mkdir -p build +cd build + +# Step 2: Clone the repository +log_message "Step 2: Cloning repository" +git clone $REPO_URL . +git checkout main + +# Step 3: Install dependencies +log_message "Step 3: Installing dependencies" +npm ci + +# Step 4: Run linting +log_message "Step 4: Running linting" +npm run lint + +# Step 5: Run tests +log_message "Step 5: Running tests" +npm test + +# Step 6: Build the application +log_message "Step 6: Building the application" +npm run build + +# Step 7: Run security audit +log_message "Step 7: Running security audit" +npm audit --production + +# Step 8: Deploy to production +log_message "Step 8: Deploying to production" +# Check if deploy directory exists +if [ ! -d "$DEPLOY_DIR" ]; then + mkdir -p $DEPLOY_DIR +fi + +# Copy build files to deploy directory +cp -r dist/* $DEPLOY_DIR + +# Step 9: Restart the application +log_message "Step 9: Restarting the application" +# This is a simplified example - in reality, you might use systemd, PM2, etc. +cd $DEPLOY_DIR +npm run stop || true # Don't fail if the app isn't running +npm run start + +# Step 10: Run smoke tests +log_message "Step 10: Running smoke tests" +# Simple curl test to verify the application is responding +sleep 5 # Wait for the app to start +if curl -s http://localhost:3000/health | grep -q "ok"; then + log_message "Smoke test passed!" +else + log_message "Smoke test failed!" + exit 1 +fi + +# Pipeline completed successfully +log_message "CI/CD pipeline completed successfully!" diff --git a/snippets/linux/system_monitor.sh b/snippets/linux/system_monitor.sh new file mode 100644 index 0000000..ca584e9 --- /dev/null +++ b/snippets/linux/system_monitor.sh @@ -0,0 +1,204 @@ +#!/bin/bash +# +# System Monitoring Script +# This script collects and displays system information +# + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print section headers +print_header() { + echo -e "\n${BLUE}==== $1 ====${NC}" +} + +# Function to print success/warning/error messages +print_status() { + if [ "$2" == "ok" ]; then + echo -e "${GREEN}$1${NC}" + elif [ "$2" == "warning" ]; then + echo -e "${YELLOW}$1${NC}" + else + echo -e "${RED}$1${NC}" + fi +} + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + print_status "Warning: This script should be run as root for full functionality" "warning" +fi + +# System Information +print_header "System Information" +echo "Hostname: $(hostname)" +echo "Kernel: $(uname -r)" +echo "Uptime: $(uptime -p)" +echo "Last Boot: $(who -b | awk '{print $3, $4}')" + +# CPU Information +print_header "CPU Information" +cpu_model=$(grep "model name" /proc/cpuinfo | head -n 1 | cut -d ':' -f 2 | sed 's/^[ \t]*//') +cpu_cores=$(grep -c "processor" /proc/cpuinfo) +cpu_load=$(uptime | awk -F'load average:' '{ print $2 }' | cut -d, -f1 | sed 's/^[ \t]*//') + +echo "CPU Model: $cpu_model" +echo "CPU Cores: $cpu_cores" +echo -n "CPU Load: $cpu_load " + +# Check CPU load +cpu_load_float=$(echo $cpu_load | sed 's/,/./g') +if (( $(echo "$cpu_load_float < 1" | bc -l) )); then + print_status "(Normal)" "ok" +elif (( $(echo "$cpu_load_float < 2" | bc -l) )); then + print_status "(Moderate)" "warning" +else + print_status "(High)" "error" +fi + +# Memory Information +print_header "Memory Information" +total_mem=$(free -h | grep "Mem:" | awk '{print $2}') +used_mem=$(free -h | grep "Mem:" | awk '{print $3}') +free_mem=$(free -h | grep "Mem:" | awk '{print $4}') +mem_usage_percent=$(free | grep Mem | awk '{print $3/$2 * 100.0}' | cut -d. -f1) + +echo "Total Memory: $total_mem" +echo "Used Memory: $used_mem" +echo "Free Memory: $free_mem" +echo -n "Memory Usage: $mem_usage_percent% " + +# Check memory usage +if [ "$mem_usage_percent" -lt 70 ]; then + print_status "(Normal)" "ok" +elif [ "$mem_usage_percent" -lt 85 ]; then + print_status "(Moderate)" "warning" +else + print_status "(High)" "error" +fi + +# Disk Usage +print_header "Disk Usage" +echo "Filesystem Size Used Avail Use% Mounted on" +df -h | grep -v "tmpfs" | grep -v "udev" | grep -v "loop" | tail -n +2 | while read line; do + usage_percent=$(echo $line | awk '{print $5}' | sed 's/%//') + if [ "$usage_percent" -lt 70 ]; then + status="ok" + elif [ "$usage_percent" -lt 85 ]; then + status="warning" + else + status="error" + fi + + filesystem=$(echo $line | awk '{print $1}') + size=$(echo $line | awk '{print $2}') + used=$(echo $line | awk '{print $3}') + avail=$(echo $line | awk '{print $4}') + use_percent=$(echo $line | awk '{print $5}') + mounted=$(echo $line | awk '{print $6}') + + printf "%-15s %-5s %-5s %-6s " "$filesystem" "$size" "$used" "$avail" + print_status "$use_percent" "$status" + echo " $mounted" +done + +# Network Information +print_header "Network Information" +echo "Interface IP Address MAC Address Status" +ip -o addr show | grep 'inet ' | grep -v '127.0.0.1' | while read line; do + interface=$(echo $line | awk '{print $2}') + ip_address=$(echo $line | awk '{print $4}') + mac_address=$(ip link show $interface | grep link/ether | awk '{print $2}') + status=$(ip link show $interface | grep -o "state [A-Z]*" | cut -d ' ' -f 2) + + printf "%-12s %-18s %-18s " "$interface" "$ip_address" "$mac_address" + if [ "$status" == "UP" ]; then + print_status "$status" "ok" + else + print_status "$status" "error" + fi +done + +# Process Information +print_header "Top 5 CPU-Consuming Processes" +ps aux --sort=-%cpu | head -6 + +print_header "Top 5 Memory-Consuming Processes" +ps aux --sort=-%mem | head -6 + +# System Load +print_header "System Load Average (1, 5, 15 min)" +load_avg=$(cat /proc/loadavg | awk '{print $1, $2, $3}') +echo $load_avg + +# Check for failed services +print_header "Failed Services" +if command -v systemctl &> /dev/null; then + failed_services=$(systemctl --failed | grep "failed" | wc -l) + if [ "$failed_services" -eq 0 ]; then + print_status "No failed services found" "ok" + else + print_status "$failed_services failed services found:" "error" + systemctl --failed | grep "failed" + fi +else + echo "systemctl not found, skipping service check" +fi + +# Check for system updates +print_header "System Updates" +if command -v apt &> /dev/null; then + # Debian/Ubuntu + updates=$(apt list --upgradable 2>/dev/null | grep -v "Listing..." | wc -l) + if [ "$updates" -eq 0 ]; then + print_status "System is up to date" "ok" + else + print_status "$updates updates available" "warning" + fi +elif command -v yum &> /dev/null; then + # CentOS/RHEL + updates=$(yum check-update --quiet | grep -v "^$" | wc -l) + if [ "$updates" -eq 0 ]; then + print_status "System is up to date" "ok" + else + print_status "$updates updates available" "warning" + fi +else + echo "Package manager not detected, skipping update check" +fi + +# Security Checks +print_header "Security Checks" + +# Check SSH root login +if [ -f /etc/ssh/sshd_config ]; then + if grep -q "PermitRootLogin yes" /etc/ssh/sshd_config; then + print_status "SSH root login is enabled (security risk)" "error" + else + print_status "SSH root login is disabled" "ok" + fi +fi + +# Check for listening ports +print_header "Open Ports" +if command -v netstat &> /dev/null; then + netstat -tuln | grep LISTEN +elif command -v ss &> /dev/null; then + ss -tuln | grep LISTEN +else + echo "Neither netstat nor ss found, skipping port check" +fi + +# Summary +print_header "System Health Summary" +# Check overall health based on previous checks +if [ "$mem_usage_percent" -lt 85 ] && [ "$failed_services" -eq 0 ]; then + print_status "System appears to be healthy" "ok" +else + print_status "System needs attention" "warning" +fi + +echo -e "\nReport generated on $(date)" diff --git a/snippets/system-design/microservices_example.js b/snippets/system-design/microservices_example.js new file mode 100644 index 0000000..92e0d24 --- /dev/null +++ b/snippets/system-design/microservices_example.js @@ -0,0 +1,595 @@ +/** + * Microservices Architecture Example + * + * This file demonstrates a simple implementation of microservices + * for an e-commerce application using Node.js and Express. + */ + +// Product Service +const productServiceCode = ` +const express = require('express'); +const mongoose = require('mongoose'); +const app = express(); +const PORT = process.env.PORT || 3001; + +// MongoDB connection +mongoose.connect('mongodb://product-db:27017/productdb', { + useNewUrlParser: true, + useUnifiedTopology: true +}); + +// Product schema +const productSchema = new mongoose.Schema({ + id: String, + name: String, + description: String, + price: Number, + category: String, + inventory: Number +}); + +const Product = mongoose.model('Product', productSchema); + +app.use(express.json()); + +// Health check endpoint +app.get('/health', (req, res) => { + res.status(200).json({ status: 'ok' }); +}); + +// Get all products +app.get('/api/products', async (req, res) => { + try { + const products = await Product.find(); + res.json(products); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// Get product by ID +app.get('/api/products/:id', async (req, res) => { + try { + const product = await Product.findOne({ id: req.params.id }); + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + res.json(product); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// Create product +app.post('/api/products', async (req, res) => { + try { + const product = new Product(req.body); + await product.save(); + + // Publish event to message broker (RabbitMQ) + const amqp = require('amqplib'); + const connection = await amqp.connect('amqp://rabbitmq'); + const channel = await connection.createChannel(); + const exchange = 'product-events'; + + await channel.assertExchange(exchange, 'topic', { durable: true }); + channel.publish( + exchange, + 'product.created', + Buffer.from(JSON.stringify({ + event: 'PRODUCT_CREATED', + data: product + })) + ); + + await channel.close(); + await connection.close(); + + res.status(201).json(product); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// Update product inventory +app.patch('/api/products/:id/inventory', async (req, res) => { + try { + const { quantity } = req.body; + const product = await Product.findOne({ id: req.params.id }); + + if (!product) { + return res.status(404).json({ error: 'Product not found' }); + } + + product.inventory += quantity; + await product.save(); + res.json(product); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +app.listen(PORT, () => { + console.log(\`Product service running on port \${PORT}\`); +}); +`; + +// Order Service +const orderServiceCode = ` +const express = require('express'); +const mongoose = require('mongoose'); +const axios = require('axios'); +const app = express(); +const PORT = process.env.PORT || 3002; + +// MongoDB connection +mongoose.connect('mongodb://order-db:27017/orderdb', { + useNewUrlParser: true, + useUnifiedTopology: true +}); + +// Order schema +const orderSchema = new mongoose.Schema({ + id: String, + customerId: String, + items: [{ + productId: String, + quantity: Number, + price: Number + }], + totalAmount: Number, + status: String, + createdAt: { type: Date, default: Date.now } +}); + +const Order = mongoose.model('Order', orderSchema); + +app.use(express.json()); + +// Health check endpoint +app.get('/health', (req, res) => { + res.status(200).json({ status: 'ok' }); +}); + +// Create order +app.post('/api/orders', async (req, res) => { + try { + const { customerId, items } = req.body; + + // Validate customer via Customer Service + try { + await axios.get(\`http://customer-service:3003/api/customers/\${customerId}\`); + } catch (error) { + return res.status(400).json({ error: 'Invalid customer ID' }); + } + + // Check product availability and calculate total + let totalAmount = 0; + for (const item of items) { + try { + const productResponse = await axios.get( + \`http://product-service:3001/api/products/\${item.productId}\` + ); + const product = productResponse.data; + + if (product.inventory < item.quantity) { + return res.status(400).json({ + error: \`Insufficient inventory for product \${item.productId}\` + }); + } + + item.price = product.price; + totalAmount += product.price * item.quantity; + } catch (error) { + return res.status(400).json({ + error: \`Invalid product ID: \${item.productId}\` + }); + } + } + + // Create order + const order = new Order({ + id: \`ORD-\${Date.now()}\`, + customerId, + items, + totalAmount, + status: 'PENDING' + }); + + await order.save(); + + // Update inventory + for (const item of items) { + await axios.patch( + \`http://product-service:3001/api/products/\${item.productId}/inventory\`, + { quantity: -item.quantity } + ); + } + + // Publish event to message broker (RabbitMQ) + const amqp = require('amqplib'); + const connection = await amqp.connect('amqp://rabbitmq'); + const channel = await connection.createChannel(); + const exchange = 'order-events'; + + await channel.assertExchange(exchange, 'topic', { durable: true }); + channel.publish( + exchange, + 'order.created', + Buffer.from(JSON.stringify({ + event: 'ORDER_CREATED', + data: order + })) + ); + + await channel.close(); + await connection.close(); + + res.status(201).json(order); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// Get order by ID +app.get('/api/orders/:id', async (req, res) => { + try { + const order = await Order.findOne({ id: req.params.id }); + if (!order) { + return res.status(404).json({ error: 'Order not found' }); + } + res.json(order); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// Get customer orders +app.get('/api/orders/customer/:customerId', async (req, res) => { + try { + const orders = await Order.find({ customerId: req.params.customerId }); + res.json(orders); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +app.listen(PORT, () => { + console.log(\`Order service running on port \${PORT}\`); +}); +`; + +// API Gateway +const apiGatewayCode = ` +const express = require('express'); +const { createProxyMiddleware } = require('http-proxy-middleware'); +const app = express(); +const PORT = process.env.PORT || 3000; + +// Authentication middleware +const authenticate = (req, res, next) => { + const authHeader = req.headers.authorization; + + if (!authHeader) { + return res.status(401).json({ error: 'Authorization header missing' }); + } + + // In a real application, you would validate the token + // For this example, we'll just check if it exists + const token = authHeader.split(' ')[1]; + if (!token) { + return res.status(401).json({ error: 'Invalid token format' }); + } + + // Add user info to request for downstream services + req.user = { id: 'user-123' }; // This would come from token validation + next(); +}; + +// Rate limiting middleware +const rateLimit = require('express-rate-limit'); +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100 // limit each IP to 100 requests per windowMs +}); + +// Apply rate limiting to all requests +app.use(limiter); + +// Health check endpoint +app.get('/health', (req, res) => { + res.status(200).json({ status: 'ok' }); +}); + +// Product Service routes +app.use( + '/api/products', + authenticate, + createProxyMiddleware({ + target: 'http://product-service:3001', + changeOrigin: true, + pathRewrite: { + '^/api/products': '/api/products' + } + }) +); + +// Order Service routes +app.use( + '/api/orders', + authenticate, + createProxyMiddleware({ + target: 'http://order-service:3002', + changeOrigin: true, + pathRewrite: { + '^/api/orders': '/api/orders' + } + }) +); + +// Customer Service routes +app.use( + '/api/customers', + authenticate, + createProxyMiddleware({ + target: 'http://customer-service:3003', + changeOrigin: true, + pathRewrite: { + '^/api/customers': '/api/customers' + } + }) +); + +app.listen(PORT, () => { + console.log(\`API Gateway running on port \${PORT}\`); +}); +`; + +// Notification Service (Event Consumer) +const notificationServiceCode = ` +const amqp = require('amqplib'); +const nodemailer = require('nodemailer'); + +// Create a test SMTP service +const transporter = nodemailer.createTransport({ + host: 'smtp.ethereal.email', + port: 587, + auth: { + user: 'testuser@ethereal.email', + pass: 'testpassword' + } +}); + +async function startConsumer() { + try { + // Connect to RabbitMQ + const connection = await amqp.connect('amqp://rabbitmq'); + const channel = await connection.createChannel(); + + // Set up exchanges + const orderExchange = 'order-events'; + const productExchange = 'product-events'; + + await channel.assertExchange(orderExchange, 'topic', { durable: true }); + await channel.assertExchange(productExchange, 'topic', { durable: true }); + + // Create queue for notification service + const queue = 'notification-service'; + await channel.assertQueue(queue, { durable: true }); + + // Bind queue to exchanges with routing keys + await channel.bindQueue(queue, orderExchange, 'order.*'); + await channel.bindQueue(queue, productExchange, 'product.*'); + + console.log('Notification service waiting for messages...'); + + // Consume messages + channel.consume(queue, async (msg) => { + if (msg !== null) { + const content = JSON.parse(msg.content.toString()); + console.log(\`Received event: \${content.event}\`); + + switch (content.event) { + case 'ORDER_CREATED': + await sendOrderConfirmation(content.data); + break; + case 'PRODUCT_CREATED': + await notifyAdminAboutNewProduct(content.data); + break; + default: + console.log(\`Unknown event type: \${content.event}\`); + } + + channel.ack(msg); + } + }); + } catch (error) { + console.error('Error in notification service:', error); + } +} + +async function sendOrderConfirmation(order) { + try { + const mailOptions = { + from: 'noreply@example.com', + to: \`customer-\${order.customerId}@example.com\`, // In a real app, get from customer service + subject: \`Order Confirmation: \${order.id}\`, + text: \` + Thank you for your order! + + Order ID: \${order.id} + Total Amount: $\${order.totalAmount.toFixed(2)} + Status: \${order.status} + + Items: + \${order.items.map(item => \`- Product \${item.productId}: \${item.quantity} x $\${item.price.toFixed(2)}\`).join('\\n')} + + Thank you for shopping with us! + \` + }; + + await transporter.sendMail(mailOptions); + console.log(\`Order confirmation email sent for order \${order.id}\`); + } catch (error) { + console.error('Error sending order confirmation:', error); + } +} + +async function notifyAdminAboutNewProduct(product) { + try { + const mailOptions = { + from: 'noreply@example.com', + to: 'admin@example.com', + subject: 'New Product Added', + text: \` + A new product has been added to the catalog: + + ID: \${product.id} + Name: \${product.name} + Price: $\${product.price.toFixed(2)} + Category: \${product.category} + Initial Inventory: \${product.inventory} + \` + }; + + await transporter.sendMail(mailOptions); + console.log(\`Admin notification sent for new product \${product.id}\`); + } catch (error) { + console.error('Error sending admin notification:', error); + } +} + +// Start the consumer +startConsumer(); +`; + +// Docker Compose file for the microservices +const dockerComposeCode = ` +version: '3' + +services: + # API Gateway + api-gateway: + build: ./api-gateway + ports: + - "3000:3000" + depends_on: + - product-service + - order-service + - customer-service + environment: + - PORT=3000 + networks: + - microservices-network + + # Product Service + product-service: + build: ./product-service + ports: + - "3001:3001" + depends_on: + - product-db + - rabbitmq + environment: + - PORT=3001 + - MONGODB_URI=mongodb://product-db:27017/productdb + - RABBITMQ_URI=amqp://rabbitmq + networks: + - microservices-network + + # Order Service + order-service: + build: ./order-service + ports: + - "3002:3002" + depends_on: + - order-db + - product-service + - customer-service + - rabbitmq + environment: + - PORT=3002 + - MONGODB_URI=mongodb://order-db:27017/orderdb + - RABBITMQ_URI=amqp://rabbitmq + networks: + - microservices-network + + # Customer Service + customer-service: + build: ./customer-service + ports: + - "3003:3003" + depends_on: + - customer-db + environment: + - PORT=3003 + - MONGODB_URI=mongodb://customer-db:27017/customerdb + networks: + - microservices-network + + # Notification Service + notification-service: + build: ./notification-service + depends_on: + - rabbitmq + environment: + - RABBITMQ_URI=amqp://rabbitmq + - SMTP_HOST=smtp.ethereal.email + - SMTP_PORT=587 + - SMTP_USER=testuser@ethereal.email + - SMTP_PASS=testpassword + networks: + - microservices-network + + # Databases + product-db: + image: mongo:4.4 + volumes: + - product-db-data:/data/db + networks: + - microservices-network + + order-db: + image: mongo:4.4 + volumes: + - order-db-data:/data/db + networks: + - microservices-network + + customer-db: + image: mongo:4.4 + volumes: + - customer-db-data:/data/db + networks: + - microservices-network + + # Message Broker + rabbitmq: + image: rabbitmq:3-management + ports: + - "5672:5672" # AMQP port + - "15672:15672" # Management UI + networks: + - microservices-network + +networks: + microservices-network: + driver: bridge + +volumes: + product-db-data: + order-db-data: + customer-db-data: +`; + +// Export the code examples +module.exports = { + productServiceCode, + orderServiceCode, + apiGatewayCode, + notificationServiceCode, + dockerComposeCode +}; + +// This file is just a demonstration of microservices code examples +// In a real project, each service would be in its own directory diff --git a/snippets/testing/unit_testing_example.js b/snippets/testing/unit_testing_example.js new file mode 100644 index 0000000..fe33bcc --- /dev/null +++ b/snippets/testing/unit_testing_example.js @@ -0,0 +1,459 @@ +/** + * Unit Testing Example with Jest + * + * This file demonstrates unit testing principles using Jest for a simple + * shopping cart implementation. + */ + +// ShoppingCart.js - The module we're testing +class ShoppingCart { + constructor() { + this.items = []; + } + + addItem(item) { + if (!item.id || !item.name || typeof item.price !== 'number' || item.price <= 0) { + throw new Error('Invalid item format'); + } + + const existingItem = this.items.find(i => i.id === item.id); + + if (existingItem) { + existingItem.quantity += item.quantity || 1; + } else { + this.items.push({ + ...item, + quantity: item.quantity || 1 + }); + } + + return this.items; + } + + removeItem(itemId) { + const initialLength = this.items.length; + this.items = this.items.filter(item => item.id !== itemId); + + return initialLength !== this.items.length; + } + + updateQuantity(itemId, quantity) { + if (typeof quantity !== 'number' || quantity <= 0) { + throw new Error('Quantity must be a positive number'); + } + + const item = this.items.find(item => item.id === itemId); + + if (!item) { + return false; + } + + item.quantity = quantity; + return true; + } + + getTotal() { + return this.items.reduce((total, item) => { + return total + (item.price * item.quantity); + }, 0); + } + + getItemCount() { + return this.items.reduce((count, item) => count + item.quantity, 0); + } + + clear() { + this.items = []; + } + + applyDiscount(percentage) { + if (typeof percentage !== 'number' || percentage < 0 || percentage > 100) { + throw new Error('Discount percentage must be between 0 and 100'); + } + + const discountFactor = 1 - (percentage / 100); + return this.getTotal() * discountFactor; + } +} + +// ShoppingCart.test.js - Unit tests for the ShoppingCart class +describe('ShoppingCart', () => { + let cart; + + // Setup - Runs before each test + beforeEach(() => { + cart = new ShoppingCart(); + }); + + // Test adding items + describe('addItem', () => { + test('should add a new item to the cart', () => { + // Arrange + const item = { id: '1', name: 'Product 1', price: 10 }; + + // Act + cart.addItem(item); + + // Assert + expect(cart.items).toHaveLength(1); + expect(cart.items[0]).toEqual({ id: '1', name: 'Product 1', price: 10, quantity: 1 }); + }); + + test('should increase quantity when adding an existing item', () => { + // Arrange + const item = { id: '1', name: 'Product 1', price: 10 }; + + // Act + cart.addItem(item); + cart.addItem(item); + + // Assert + expect(cart.items).toHaveLength(1); + expect(cart.items[0].quantity).toBe(2); + }); + + test('should respect the quantity property when provided', () => { + // Arrange + const item = { id: '1', name: 'Product 1', price: 10, quantity: 5 }; + + // Act + cart.addItem(item); + + // Assert + expect(cart.items).toHaveLength(1); + expect(cart.items[0].quantity).toBe(5); + }); + + test('should throw an error for invalid item format', () => { + // Arrange + const invalidItems = [ + { name: 'Missing ID', price: 10 }, + { id: '1', price: 10 }, + { id: '1', name: 'Invalid Price', price: -5 }, + { id: '1', name: 'Invalid Price Type', price: '10' } + ]; + + // Act & Assert + invalidItems.forEach(item => { + expect(() => cart.addItem(item)).toThrow('Invalid item format'); + }); + }); + }); + + // Test removing items + describe('removeItem', () => { + test('should remove an item from the cart', () => { + // Arrange + cart.addItem({ id: '1', name: 'Product 1', price: 10 }); + + // Act + const result = cart.removeItem('1'); + + // Assert + expect(result).toBe(true); + expect(cart.items).toHaveLength(0); + }); + + test('should return false when trying to remove a non-existent item', () => { + // Act + const result = cart.removeItem('nonexistent'); + + // Assert + expect(result).toBe(false); + }); + }); + + // Test updating quantities + describe('updateQuantity', () => { + test('should update the quantity of an existing item', () => { + // Arrange + cart.addItem({ id: '1', name: 'Product 1', price: 10 }); + + // Act + const result = cart.updateQuantity('1', 3); + + // Assert + expect(result).toBe(true); + expect(cart.items[0].quantity).toBe(3); + }); + + test('should return false when trying to update a non-existent item', () => { + // Act + const result = cart.updateQuantity('nonexistent', 3); + + // Assert + expect(result).toBe(false); + }); + + test('should throw an error for invalid quantity', () => { + // Arrange + cart.addItem({ id: '1', name: 'Product 1', price: 10 }); + + // Act & Assert + expect(() => cart.updateQuantity('1', -1)).toThrow('Quantity must be a positive number'); + expect(() => cart.updateQuantity('1', 'invalid')).toThrow('Quantity must be a positive number'); + }); + }); + + // Test calculating totals + describe('getTotal', () => { + test('should calculate the total price of all items', () => { + // Arrange + cart.addItem({ id: '1', name: 'Product 1', price: 10, quantity: 2 }); + cart.addItem({ id: '2', name: 'Product 2', price: 15, quantity: 1 }); + + // Act + const total = cart.getTotal(); + + // Assert + expect(total).toBe(35); // (10 * 2) + (15 * 1) = 35 + }); + + test('should return 0 for an empty cart', () => { + // Act + const total = cart.getTotal(); + + // Assert + expect(total).toBe(0); + }); + }); + + // Test item count + describe('getItemCount', () => { + test('should return the total number of items in the cart', () => { + // Arrange + cart.addItem({ id: '1', name: 'Product 1', price: 10, quantity: 2 }); + cart.addItem({ id: '2', name: 'Product 2', price: 15, quantity: 3 }); + + // Act + const count = cart.getItemCount(); + + // Assert + expect(count).toBe(5); // 2 + 3 = 5 + }); + + test('should return 0 for an empty cart', () => { + // Act + const count = cart.getItemCount(); + + // Assert + expect(count).toBe(0); + }); + }); + + // Test clearing the cart + describe('clear', () => { + test('should remove all items from the cart', () => { + // Arrange + cart.addItem({ id: '1', name: 'Product 1', price: 10 }); + cart.addItem({ id: '2', name: 'Product 2', price: 15 }); + + // Act + cart.clear(); + + // Assert + expect(cart.items).toHaveLength(0); + expect(cart.getTotal()).toBe(0); + }); + }); + + // Test applying discounts + describe('applyDiscount', () => { + test('should apply the correct discount to the total', () => { + // Arrange + cart.addItem({ id: '1', name: 'Product 1', price: 100 }); + + // Act + const discountedTotal = cart.applyDiscount(20); // 20% discount + + // Assert + expect(discountedTotal).toBe(80); + }); + + test('should throw an error for invalid discount percentage', () => { + // Arrange + cart.addItem({ id: '1', name: 'Product 1', price: 100 }); + + // Act & Assert + expect(() => cart.applyDiscount(-10)).toThrow('Discount percentage must be between 0 and 100'); + expect(() => cart.applyDiscount(110)).toThrow('Discount percentage must be between 0 and 100'); + expect(() => cart.applyDiscount('20')).toThrow('Discount percentage must be between 0 and 100'); + }); + }); + + // Integration test for multiple operations + describe('integration', () => { + test('should handle a sequence of cart operations correctly', () => { + // Add items + cart.addItem({ id: '1', name: 'Product 1', price: 10, quantity: 2 }); + cart.addItem({ id: '2', name: 'Product 2', price: 15 }); + expect(cart.getItemCount()).toBe(3); + expect(cart.getTotal()).toBe(35); + + // Update quantity + cart.updateQuantity('2', 3); + expect(cart.getItemCount()).toBe(5); + expect(cart.getTotal()).toBe(65); // (10 * 2) + (15 * 3) = 65 + + // Remove an item + cart.removeItem('1'); + expect(cart.getItemCount()).toBe(3); + expect(cart.getTotal()).toBe(45); // 15 * 3 = 45 + + // Apply discount + const discountedTotal = cart.applyDiscount(10); + expect(discountedTotal).toBe(40.5); // 45 - 10% = 40.5 + }); + }); +}); + +// Example of using test doubles +describe('ShoppingCart with test doubles', () => { + // Example of using a spy + test('should call external service when adding item (spy example)', () => { + // Arrange + const notificationService = { + notifyItemAdded: jest.fn() + }; + + class ShoppingCartWithNotifications extends ShoppingCart { + addItem(item) { + const result = super.addItem(item); + notificationService.notifyItemAdded(item); + return result; + } + } + + const cart = new ShoppingCartWithNotifications(); + const item = { id: '1', name: 'Product 1', price: 10 }; + + // Act + cart.addItem(item); + + // Assert + expect(notificationService.notifyItemAdded).toHaveBeenCalledWith(item); + }); + + // Example of using a stub + test('should apply tax rate from tax service (stub example)', () => { + // Arrange + const taxServiceStub = { + getTaxRate: () => 0.1 // 10% tax rate + }; + + class ShoppingCartWithTax extends ShoppingCart { + constructor(taxService) { + super(); + this.taxService = taxService; + } + + getTotalWithTax() { + const subtotal = this.getTotal(); + const taxRate = this.taxService.getTaxRate(); + return subtotal * (1 + taxRate); + } + } + + const cart = new ShoppingCartWithTax(taxServiceStub); + cart.addItem({ id: '1', name: 'Product 1', price: 100 }); + + // Act + const totalWithTax = cart.getTotalWithTax(); + + // Assert + expect(totalWithTax).toBe(110); // 100 + 10% tax = 110 + }); + + // Example of using a mock + test('should validate inventory before adding item (mock example)', () => { + // Arrange + const inventoryServiceMock = { + checkAvailability: jest.fn() + }; + + class ShoppingCartWithInventory extends ShoppingCart { + constructor(inventoryService) { + super(); + this.inventoryService = inventoryService; + } + + addItemWithInventoryCheck(item) { + const isAvailable = this.inventoryService.checkAvailability(item.id, item.quantity || 1); + if (isAvailable) { + return super.addItem(item); + } + throw new Error('Item not available in requested quantity'); + } + } + + const cart = new ShoppingCartWithInventory(inventoryServiceMock); + const item = { id: '1', name: 'Product 1', price: 10, quantity: 2 }; + + // Setup mock behavior + inventoryServiceMock.checkAvailability.mockReturnValueOnce(true); + + // Act + cart.addItemWithInventoryCheck(item); + + // Assert + expect(inventoryServiceMock.checkAvailability).toHaveBeenCalledWith('1', 2); + }); + + // Example of using a fake + test('should persist cart items to storage (fake example)', () => { + // Arrange + class FakeStorage { + constructor() { + this.data = {}; + } + + setItem(key, value) { + this.data[key] = value; + } + + getItem(key) { + return this.data[key] || null; + } + } + + class ShoppingCartWithStorage extends ShoppingCart { + constructor(storage) { + super(); + this.storage = storage; + this.loadFromStorage(); + } + + loadFromStorage() { + const savedItems = this.storage.getItem('cart-items'); + if (savedItems) { + this.items = JSON.parse(savedItems); + } + } + + saveToStorage() { + this.storage.setItem('cart-items', JSON.stringify(this.items)); + } + + addItem(item) { + const result = super.addItem(item); + this.saveToStorage(); + return result; + } + } + + const fakeStorage = new FakeStorage(); + const cart = new ShoppingCartWithStorage(fakeStorage); + const item = { id: '1', name: 'Product 1', price: 10 }; + + // Act + cart.addItem(item); + + // Assert + expect(JSON.parse(fakeStorage.getItem('cart-items'))).toEqual([ + { id: '1', name: 'Product 1', price: 10, quantity: 1 } + ]); + }); +}); + +// Export the ShoppingCart class +module.exports = ShoppingCart; \ No newline at end of file diff --git a/tools/check_links.sh b/tools/check_links.sh new file mode 100755 index 0000000..7d7daad --- /dev/null +++ b/tools/check_links.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# +# Link Checker Script +# +# This script checks for broken links in markdown files within the repository. +# It uses markdown-link-check to validate both internal and external links. +# +# Usage: +# ./check_links.sh [directory] +# +# If no directory is specified, it will check all markdown files in the repo. +# +# Requirements: +# - npm (Node Package Manager) +# - markdown-link-check (will be installed if not present) +# + +set -e + +# Default search directory is the repo root +SEARCH_DIR=${1:-$(git rev-parse --show-toplevel)} + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}=== Tech Notes Hub Link Checker ===${NC}" + +# Check if markdown-link-check is installed, if not install it +if ! command -v markdown-link-check &> /dev/null; then + echo -e "${YELLOW}markdown-link-check not found. Installing...${NC}" + npm install -g markdown-link-check +fi + +# Configuration file for markdown-link-check +CONFIG_FILE=$(mktemp) +cat > "$CONFIG_FILE" << EOF +{ + "ignorePatterns": [ + { + "pattern": "^#" + }, + { + "pattern": "^mailto:" + } + ], + "replacementPatterns": [ + { + "pattern": "^/", + "replacement": "file://$(pwd)/" + } + ], + "timeout": "5s", + "retryOn429": true, + "retryCount": 3, + "fallbackRetryDelay": "30s" +} +EOF + +# Find all markdown files in the specified directory +echo -e "${YELLOW}Searching for markdown files in ${SEARCH_DIR}...${NC}" +FILES=$(find "$SEARCH_DIR" -name "*.md" | sort) + +if [ -z "$FILES" ]; then + echo -e "${RED}No markdown files found in ${SEARCH_DIR}${NC}" + rm "$CONFIG_FILE" + exit 1 +fi + +TOTAL_FILES=$(echo "$FILES" | wc -l) +TOTAL_FILES=$(echo "$TOTAL_FILES" | tr -d '[:space:]') +echo -e "${GREEN}Found ${TOTAL_FILES} markdown files to check${NC}" + +# Variables to track results +PASSED=0 +FAILED=0 +FAILED_FILES="" + +# Process each file +for FILE in $FILES; do + echo -e "${YELLOW}Checking links in ${FILE}...${NC}" + + # Run markdown-link-check + if markdown-link-check --config "$CONFIG_FILE" "$FILE" | grep -q "ERROR"; then + echo -e "${RED}❌ Failed: ${FILE}${NC}" + FAILED=$((FAILED+1)) + FAILED_FILES="${FAILED_FILES}\n${FILE}" + else + echo -e "${GREEN}✅ Passed: ${FILE}${NC}" + PASSED=$((PASSED+1)) + fi +done + +# Clean up +rm "$CONFIG_FILE" + +# Print summary +echo -e "\n${YELLOW}=== Summary ===${NC}" +echo -e "${GREEN}✅ ${PASSED} files passed${NC}" +if [ $FAILED -gt 0 ]; then + echo -e "${RED}❌ ${FAILED} files failed:${FAILED_FILES}${NC}" + exit 1 +else + echo -e "${GREEN}All links are valid!${NC}" + exit 0 +fi \ No newline at end of file diff --git a/tools/generate_summary.py b/tools/generate_summary.py new file mode 100755 index 0000000..25e8f07 --- /dev/null +++ b/tools/generate_summary.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Script to automatically generate SUMMARY.md by scanning the docs/ directory. +""" + +import os +import re +from pathlib import Path + +def generate_summary(): + """Generate summary based on docs structure""" + docs_dir = Path("docs") + summary_path = Path("SUMMARY.md") + + # Start with header + summary_content = "# Tech Notes Hub\n\n" + summary_content += "## Table of Contents\n\n" + + # Process each directory in docs/ + for path in sorted(docs_dir.glob("*")): + if path.is_dir(): + dir_name = path.name + pretty_dir_name = dir_name.replace('-', ' ').title() + summary_content += f"### {pretty_dir_name}\n\n" + + # Process files in the directory + for file in sorted(path.glob("*.md")): + file_name = file.stem + + # Skip files that start with underscore (like _category_.json files) + if file_name.startswith('_'): + continue + + # Get the title from the file's first heading + title = get_title_from_file(file) + if not title: + title = file_name.replace('-', ' ').title() + + relative_path = os.path.join(path.name, file.name) + summary_content += f"- [{title}](docs/{relative_path})\n" + + summary_content += "\n" + + # Write the summary file + with open(summary_path, "w", encoding="utf-8") as f: + f.write(summary_content) + + print(f"Generated {summary_path}") + +def get_title_from_file(file_path): + """Extract the title from the first heading in the file""" + try: + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + # Look for first # heading + match = re.search(r'^# (.+)$', content, re.MULTILINE) + if match: + return match.group(1) + except Exception as e: + print(f"Warning: Could not extract title from {file_path}: {e}") + + return None + +if __name__ == "__main__": + generate_summary() \ No newline at end of file From aebc3bfd57dea86032f1efda6c557a0c7a415fdb Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 5 Jun 2025 01:09:57 +0700 Subject: [PATCH 29/46] remove: remove markdownlint --- .markdownlint.yml | 86 ----------------------------------------------- 1 file changed, 86 deletions(-) delete mode 100644 .markdownlint.yml diff --git a/.markdownlint.yml b/.markdownlint.yml deleted file mode 100644 index ea0a6a3..0000000 --- a/.markdownlint.yml +++ /dev/null @@ -1,86 +0,0 @@ -# Markdown linting rules for Tech Notes Hub -# See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md for details - -# Default state for all rules -default: true - -# MD001 header-increment - Headers should increment by one level at a time -MD001: true - -# MD003 header-style - Header style -MD003: - style: "atx" # Use # style headers - -# MD004 ul-style - Unordered list style -MD004: - style: "consistent" - -# MD007 ul-indent - Unordered list indentation -MD007: - indent: 2 # Use 2 spaces for indentation - -# MD009 no-trailing-spaces - No trailing spaces -MD009: true - -# MD010 no-hard-tabs - No hard tabs -MD010: true - -# MD012 no-multiple-blanks - Multiple consecutive blank lines -MD012: - maximum: 1 # Allow at most 1 blank line - -# MD013 line-length - Line length -MD013: - line_length: 120 # Allow longer lines for code blocks - code_blocks: false - tables: false - -# MD022 blanks-around-headers - Headers should be surrounded by blank lines -MD022: true - -# MD024 no-duplicate-header - Multiple headers with the same content -MD024: - siblings_only: true # Allow same title in different nesting - -# MD025 single-title/single-h1 - Only one top-level header -MD025: true - -# MD026 no-trailing-punctuation - No trailing punctuation in header -MD026: - punctuation: ".,;:!。,;:!" - -# MD029 ol-prefix - Ordered list item prefix -MD029: - style: "one_or_ordered" # Use 1. or ordered numbers - -# MD031 blanks-around-fences - Fenced code blocks should be surrounded by blank lines -MD031: true - -# MD032 blanks-around-lists - Lists should be surrounded by blank lines -MD032: true - -# MD033 no-inline-html - No inline HTML -MD033: - allowed_elements: ["br", "img", "a", "details", "summary"] - -# MD034 no-bare-urls - No bare URLs -MD034: true - -# MD036 no-emphasis-as-header - No emphasis as header -MD036: true - -# MD037 no-space-in-emphasis - No spaces inside emphasis markers -MD037: true - -# MD038 no-space-in-code - No spaces inside code span markers -MD038: true - -# MD040 fenced-code-language - Fenced code blocks should have a language specified -MD040: true - -# MD041 first-line-heading - First line should be a top-level header -MD041: true - -# MD046 code-block-style - Code block style -MD046: - style: "fenced" # Use ```code``` style From 19fe87436a7ad2b86977128cdbedc62fe00cf5b8 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 5 Jun 2025 01:11:33 +0700 Subject: [PATCH 30/46] remove: remove markdownlint job CI workflow --- .github/workflows/ci.yml | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 917f537..75e1465 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,39 +7,23 @@ on: branches: [ main ] jobs: - markdown-lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: 16 - - - name: Install markdownlint-cli - run: npm install -g markdownlint-cli - - - name: Run markdownlint - run: markdownlint '**/*.md' --ignore node_modules - check-links: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - + - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '16' - + - name: Install dependencies run: npm install -g markdown-link-check - + - name: Check links run: ./tools/check_links.sh - + - name: Generate TOC run: | python3 ./tools/generate_summary.py - git diff --exit-code SUMMARY.md || (echo "SUMMARY.md is out of date. Please run ./tools/generate_summary.py and commit the changes" && exit 1) \ No newline at end of file + git diff --exit-code SUMMARY.md || (echo "SUMMARY.md is out of date. Please run ./tools/generate_summary.py and commit the changes" && exit 1) \ No newline at end of file From 3f68001c425082f8706b154b6f604ee7c6cce147 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 5 Jun 2025 01:12:53 +0700 Subject: [PATCH 31/46] update: update md --- PULL_REQUEST_RULES.md | 1 - PULL_REQUEST_RULES_vi.md | 1 - 2 files changed, 2 deletions(-) diff --git a/PULL_REQUEST_RULES.md b/PULL_REQUEST_RULES.md index 94bb9b8..b25ef90 100644 --- a/PULL_REQUEST_RULES.md +++ b/PULL_REQUEST_RULES.md @@ -31,7 +31,6 @@ Select the appropriate options: - [ ] I have performed a self-review of my own content/code - [ ] I have included references/links where appropriate - [ ] My changes generate no new warnings or errors -- [ ] I have checked formatting with markdownlint or similar tools - [ ] I have checked that all links are valid - [ ] For translations: I've followed the i18n folder structure (i18n/[language_code]/...) diff --git a/PULL_REQUEST_RULES_vi.md b/PULL_REQUEST_RULES_vi.md index 910239c..a8d0fec 100644 --- a/PULL_REQUEST_RULES_vi.md +++ b/PULL_REQUEST_RULES_vi.md @@ -31,7 +31,6 @@ Chọn các tùy chọn thích hợp: - [ ] Tôi đã tự đánh giá nội dung/mã của mình - [ ] Tôi đã bao gồm tài liệu tham khảo/liên kết khi thích hợp - [ ] Các thay đổi của tôi không tạo ra cảnh báo hoặc lỗi mới -- [ ] Tôi đã kiểm tra định dạng bằng markdownlint hoặc công cụ tương tự - [ ] Tôi đã kiểm tra rằng tất cả các liên kết đều hợp lệ - [ ] Đối với bản dịch: Tôi đã tuân theo cấu trúc thư mục i18n (i18n/[language_code]/...) From 4631acffc206942e208650bc8ef4f4b54932bbc1 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 5 Jun 2025 08:41:53 +0700 Subject: [PATCH 32/46] docs: change name readme in docs --- docs/{_index.md => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{_index.md => README.md} (100%) diff --git a/docs/_index.md b/docs/README.md similarity index 100% rename from docs/_index.md rename to docs/README.md From d682dd45f764b6f57c1db0703155aa9db365bfdb Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Thu, 5 Jun 2025 11:34:46 +0700 Subject: [PATCH 33/46] Create PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..fc5488f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## Description + + +## Related Issue + +Fixes # (issue) + +## Type of Change + +- [ ] New content (notes, snippets) +- [ ] Content improvement (updates, fixes, expansion) +- [ ] Documentation update +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] Infrastructure/tooling (CI, scripts, etc.) +- [ ] Translation (i18n) - specify language: ______ +- [ ] Other (please describe): + +## Checklist + +- [ ] My content follows the style guidelines of this project +- [ ] I have performed a self-review of my own content/code +- [ ] I have included references/links where appropriate +- [ ] My changes generate no new warnings or errors +- [ ] I have checked formatting with markdownlint or similar tools +- [ ] I have checked that all links are valid +- [ ] For translations: I've followed the i18n folder structure (i18n/[language_code]/...) + +## Additional Notes + From 67282c35ac43a8d23d3f3ef0e6c69ad8eb8ffb99 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 5 Jun 2025 15:58:55 +0700 Subject: [PATCH 34/46] feature: added support for running sample code using Docker and docker-compose --- .gitignore | 18 +- Makefile | 48 ++++++ README.md | 10 ++ README_vi.md | 10 ++ docker-compose.yml | 112 +++++++++++++ docker/DATABASE_GUIDE.md | 158 ++++++++++++++++++ docker/DATABASE_GUIDE_vi.md | 157 +++++++++++++++++ docker/QUICK_START.md | 61 +++++++ docker/QUICK_START_vi.md | 61 +++++++ docker/README.md | 110 ++++++++++++ docker/README_vi.md | 110 ++++++++++++ docker/environments/cpp/Dockerfile | 17 ++ docker/environments/cpp/entrypoint.sh | 20 +++ docker/environments/csharp/Dockerfile | 17 ++ docker/environments/csharp/entrypoint.sh | 12 ++ docker/environments/databases/.env.example | 39 +++++ .../environments/databases/docker-compose.yml | 76 +++++++++ .../databases/mongodb/docker-compose.yml | 19 +++ .../databases/mongodb/init/01-sample-data.js | 70 ++++++++ .../databases/mysql/docker-compose.yml | 21 +++ .../databases/mysql/init/01-sample-data.sql | 28 ++++ .../databases/postgresql/docker-compose.yml | 19 +++ .../postgresql/init/01-sample-data.sql | 28 ++++ .../databases/redis/docker-compose.yml | 16 ++ .../environments/databases/redis/redis.conf | 51 ++++++ .../environments/databases/sqlite/Dockerfile | 20 +++ .../databases/sqlite/docker-compose.yml | 12 ++ docker/environments/databases/sqlite/init.sh | 15 ++ docker/environments/databases/sqlite/init.sql | 28 ++++ docker/environments/databricks/Dockerfile | 36 ++++ docker/environments/go/Dockerfile | 11 ++ docker/environments/java/Dockerfile | 15 ++ docker/environments/java/entrypoint.sh | 12 ++ docker/environments/javascript/Dockerfile | 13 ++ docker/environments/php/Dockerfile | 23 +++ docker/environments/python/Dockerfile | 15 ++ docker/environments/ruby/Dockerfile | 14 ++ docker/environments/rust/Dockerfile | 13 ++ docker/environments/rust/entrypoint.sh | 9 + docker/environments/shell/Dockerfile | 28 ++++ docker/redis/redis.conf | 4 + docker/run-db-snippet.sh | 141 ++++++++++++++++ docker/run-snippet.sh | 69 ++++++++ snippets/databases/.env.example | 2 +- 44 files changed, 1761 insertions(+), 7 deletions(-) create mode 100644 Makefile create mode 100644 docker-compose.yml create mode 100644 docker/DATABASE_GUIDE.md create mode 100644 docker/DATABASE_GUIDE_vi.md create mode 100644 docker/QUICK_START.md create mode 100644 docker/QUICK_START_vi.md create mode 100644 docker/README.md create mode 100644 docker/README_vi.md create mode 100644 docker/environments/cpp/Dockerfile create mode 100644 docker/environments/cpp/entrypoint.sh create mode 100644 docker/environments/csharp/Dockerfile create mode 100644 docker/environments/csharp/entrypoint.sh create mode 100644 docker/environments/databases/.env.example create mode 100644 docker/environments/databases/docker-compose.yml create mode 100644 docker/environments/databases/mongodb/docker-compose.yml create mode 100644 docker/environments/databases/mongodb/init/01-sample-data.js create mode 100644 docker/environments/databases/mysql/docker-compose.yml create mode 100644 docker/environments/databases/mysql/init/01-sample-data.sql create mode 100644 docker/environments/databases/postgresql/docker-compose.yml create mode 100644 docker/environments/databases/postgresql/init/01-sample-data.sql create mode 100644 docker/environments/databases/redis/docker-compose.yml create mode 100644 docker/environments/databases/redis/redis.conf create mode 100644 docker/environments/databases/sqlite/Dockerfile create mode 100644 docker/environments/databases/sqlite/docker-compose.yml create mode 100644 docker/environments/databases/sqlite/init.sh create mode 100644 docker/environments/databases/sqlite/init.sql create mode 100644 docker/environments/databricks/Dockerfile create mode 100644 docker/environments/go/Dockerfile create mode 100644 docker/environments/java/Dockerfile create mode 100644 docker/environments/java/entrypoint.sh create mode 100644 docker/environments/javascript/Dockerfile create mode 100644 docker/environments/php/Dockerfile create mode 100644 docker/environments/python/Dockerfile create mode 100644 docker/environments/ruby/Dockerfile create mode 100644 docker/environments/rust/Dockerfile create mode 100644 docker/environments/rust/entrypoint.sh create mode 100644 docker/environments/shell/Dockerfile create mode 100644 docker/redis/redis.conf create mode 100755 docker/run-db-snippet.sh create mode 100755 docker/run-snippet.sh diff --git a/.gitignore b/.gitignore index df28da4..078c693 100644 --- a/.gitignore +++ b/.gitignore @@ -62,12 +62,6 @@ htmlcov/ coverage.xml *.cover -# dotenv environment files -.env.local -.env.* -.env -!.env.example - # Ignore generated files and backups *.orig *.rej @@ -82,3 +76,15 @@ public/ # Ignore personal notes or drafts private-notes/ drafts/ + +# Docker +.docker-env +.docker-cache +docker-compose.override.yml + +# Environment variables +.env.local +.env.* +.env +!.env.example +docker/environments/databases/.env diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b28b29e --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +.PHONY: build run-snippet help db-start db-stop db-run-snippet + +help: + @echo "Tech Notes Hub Docker Environment" + @echo "=================================" + @echo "" + @echo "Usage:" + @echo " make build Build all Docker environments" + @echo " make run-snippet FILE=path Run a specific code snippet" + @echo " make db-start DB=type Start a specific database (mysql, postgres, mongodb, redis, sqlite)" + @echo " make db-stop DB=type Stop a specific database" + @echo " make db-run-snippet DB=type FILE=path Run a code snippet with database connection" + @echo " make help Show this help message" + +build: + docker-compose build + +run-snippet: + @if [ -z "$(FILE)" ]; then \ + echo "Error: Please specify a file path. Example: make run-snippet FILE=snippets/algorithms/graph-traversal/graph_traversal.py"; \ + exit 1; \ + fi + ./docker/run-snippet.sh $(FILE) + +db-start: + @if [ -z "$(DB)" ]; then \ + echo "Error: Please specify a database type. Example: make db-start DB=mysql"; \ + exit 1; \ + fi + docker-compose -f docker/environments/databases/docker-compose.yml up -d $(DB) + +db-stop: + @if [ -z "$(DB)" ]; then \ + echo "Error: Please specify a database type. Example: make db-stop DB=mysql"; \ + exit 1; \ + fi + docker-compose -f docker/environments/databases/docker-compose.yml stop $(DB) + +db-run-snippet: + @if [ -z "$(DB)" ]; then \ + echo "Error: Please specify a database type. Example: make db-run-snippet DB=mysql FILE=path/to/file.py"; \ + exit 1; \ + fi + @if [ -z "$(FILE)" ]; then \ + echo "Error: Please specify a file path. Example: make db-run-snippet DB=mysql FILE=path/to/file.py"; \ + exit 1; \ + fi + ./docker/run-db-snippet.sh $(DB) $(FILE) diff --git a/README.md b/README.md index 3a87c67..97b5e37 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,16 @@ Simply browse the folders or use GitHub's search feature to find the topic or pa **For a complete table of contents with all available notes and resources, check out the [SUMMARY.md](SUMMARY.md) file.** +### 🐳 Docker Environments + +This repository includes Docker configurations for running code snippets in various programming languages. To run a code snippet: + +```bash +./docker/run-snippet.sh snippets/path/to/your/snippet.py +``` + +For more information on Docker usage, see the [Docker README](docker/README.md). + ## 🤝 Contribution Contributions are highly welcome! If you want to: diff --git a/README_vi.md b/README_vi.md index 1ff2b93..2663314 100644 --- a/README_vi.md +++ b/README_vi.md @@ -54,6 +54,16 @@ Mỗi ghi chú đều độc lập, bao gồm lý thuyết và mã ví dụ th **Để xem danh mục đầy đủ với tất cả các ghi chú và tài nguyên có sẵn, hãy xem file [SUMMARY.md](SUMMARY.md).** +### 🐳 Môi Trường Docker + +Kho lưu trữ này bao gồm cấu hình Docker để chạy đoạn mã trong nhiều ngôn ngữ lập trình khác nhau. Để chạy một đoạn mã: + +```bash +./docker/run-snippet.sh snippets/path/to/your/snippet.py +``` + +Để biết thêm thông tin về cách sử dụng Docker, hãy xem [Docker README](docker/README_vi.md). + ## 🤝 Đóng góp Mọi đóng góp đều rất hoan nghênh! Nếu bạn muốn: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..51e758a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,112 @@ +version: '1.0' + +services: + # Python environment + python: + build: + context: . + dockerfile: docker/environments/python/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["--version"] + + # JavaScript/Node.js environment + javascript: + build: + context: . + dockerfile: docker/environments/javascript/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["--version"] + + # Java environment + java: + build: + context: . + dockerfile: docker/environments/java/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["/bin/bash", "-c", "java -version"] + + # C/C++ environment + cpp: + build: + context: . + dockerfile: docker/environments/cpp/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["/bin/bash", "-c", "g++ --version"] + + # Go environment + go: + build: + context: . + dockerfile: docker/environments/go/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["version"] + + # Rust environment + rust: + build: + context: . + dockerfile: docker/environments/rust/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["/bin/bash", "-c", "rustc --version"] + + # PHP environment + php: + build: + context: . + dockerfile: docker/environments/php/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["--version"] + + # C# environment + csharp: + build: + context: . + dockerfile: docker/environments/csharp/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["/bin/bash", "-c", "dotnet --version"] + + # Ruby environment + ruby: + build: + context: . + dockerfile: docker/environments/ruby/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["--version"] + + # Shell environment for Linux and DevOps scripts + shell: + build: + context: . + dockerfile: docker/environments/shell/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["-c", "echo 'Shell environment ready'"] + + # Databricks/PySpark environment + databricks: + build: + context: . + dockerfile: docker/environments/databricks/Dockerfile + volumes: + - ./snippets:/app/snippets + working_dir: /app + command: ["--version"] diff --git a/docker/DATABASE_GUIDE.md b/docker/DATABASE_GUIDE.md new file mode 100644 index 0000000..cd26fd0 --- /dev/null +++ b/docker/DATABASE_GUIDE.md @@ -0,0 +1,158 @@ +# Database Usage Guide with Docker + +This guide helps you run code snippets with connections to databases in Docker environments. + +## Supported Databases + +- **MySQL** (8.0) +- **PostgreSQL** (15) +- **MongoDB** (6) +- **Redis** (7) +- **SQLite** (3.x) + +## How to Use + +### 1. Start a Specific Database + +```bash +# Using Docker Compose directly +docker-compose -f docker/environments/databases/docker-compose.yml up -d mysql + +# Or using the Makefile +make db-start DB=mysql +``` + +Valid values for DB are: `mysql`, `postgres`, `mongodb`, `redis`, `sqlite` + +### 2. Run a Code Snippet with Database Connection + +```bash +# Using the script directly +./docker/run-db-snippet.sh mysql snippets/databases/mysql_example.py + +# Or using the Makefile +make db-run-snippet DB=mysql FILE=snippets/databases/mysql_example.py +``` + +### 3. Stop a Database When Not in Use + +```bash +# Using Docker Compose directly +docker-compose -f docker/environments/databases/docker-compose.yml stop mysql + +# Or using the Makefile +make db-stop DB=mysql +``` + +## Database Connection Information + +When running a code snippet with a database, the following environment variables will be automatically passed to the container: + +- `DB_HOST`: Database hostname +- `DB_PORT`: Database port +- `DB_USER`: Username for connection +- `DB_PASS`: Password for connection +- `DB_NAME`: Database name +- `DB_CONN_STR`: Full connection string + +### Default Connection Strings + +- **MySQL**: `mysql://user:password@tech-notes-mysql:3306/tech_notes` +- **PostgreSQL**: `postgresql://user:password@tech-notes-postgres:5432/tech_notes` +- **MongoDB**: `mongodb://user:password@tech-notes-mongodb:27017/tech_notes` +- **Redis**: `redis://tech-notes-redis:6379` +- **SQLite**: `sqlite:///data/tech_notes.db` + +### Environment Variables Configuration + +To change the default connection information, you can create a `.env` file in the `docker/environments/databases/` directory: + +1. Copy the `.env.example` file to `.env`: + ```bash + cp docker/environments/databases/.env.example docker/environments/databases/.env + ``` + +2. Edit the `.env` file to change the connection information (usernames, passwords, database names, etc.) + +3. When running the `run-db-snippet.sh` command or `make db-run-snippet`, the environment variables from the `.env` file will be automatically used. + +Note: The `.env` file has been added to `.gitignore` to prevent it from being tracked by Git, ensuring sensitive information is not pushed to the repository. + +## Example Connection Code + +### Python with MySQL + +```python +import os +import mysql.connector + +# Get connection info from environment variables +db_host = os.environ.get('DB_HOST', 'localhost') +db_port = os.environ.get('DB_PORT', '3306') +db_user = os.environ.get('DB_USER', 'user') +db_pass = os.environ.get('DB_PASS', 'password') +db_name = os.environ.get('DB_NAME', 'tech_notes') + +# Connect to the database +conn = mysql.connector.connect( + host=db_host, + port=db_port, + user=db_user, + password=db_pass, + database=db_name +) + +cursor = conn.cursor() +cursor.execute("SELECT * FROM users") +users = cursor.fetchall() + +for user in users: + print(user) + +conn.close() +``` + +### JavaScript with MongoDB + +```javascript +const { MongoClient } = require('mongodb'); + +// Get connection string from environment variable +const uri = process.env.DB_CONN_STR || 'mongodb://user:password@localhost:27017/tech_notes'; + +async function main() { + const client = new MongoClient(uri); + + try { + await client.connect(); + const database = client.db('tech_notes'); + const users = database.collection('users'); + + const query = {}; + const cursor = users.find(query); + + if ((await cursor.count()) === 0) { + console.log("No documents found!"); + } + + await cursor.forEach(user => { + console.log(user); + }); + + } finally { + await client.close(); + } +} + +main().catch(console.error); +``` + +## Sample Data + +Each database has been configured with the following sample data: + +- `users` table/collection with 3 users +- `posts` table/collection with 4 posts linked to users + +You can see the details of the sample data in the init files in the respective directory of each database. + diff --git a/docker/DATABASE_GUIDE_vi.md b/docker/DATABASE_GUIDE_vi.md new file mode 100644 index 0000000..7c1f513 --- /dev/null +++ b/docker/DATABASE_GUIDE_vi.md @@ -0,0 +1,157 @@ +# Hướng Dẫn Sử Dụng Database với Docker + +Hướng dẫn này giúp bạn chạy code snippets có kết nối tới các database trong môi trường Docker. + +## Các Database Được Hỗ Trợ + +- **MySQL** (8.0) +- **PostgreSQL** (15) +- **MongoDB** (6) +- **Redis** (7) +- **SQLite** (3.x) + +## Cách Sử Dụng + +### 1. Khởi động một Database cụ thể + +```bash +# Sử dụng Docker Compose trực tiếp +docker-compose -f docker/environments/databases/docker-compose.yml up -d mysql + +# Hoặc sử dụng Makefile +make db-start DB=mysql +``` + +Các giá trị hợp lệ cho DB là: `mysql`, `postgres`, `mongodb`, `redis`, `sqlite` + +### 2. Chạy Code Snippet với kết nối Database + +```bash +# Sử dụng script trực tiếp +./docker/run-db-snippet.sh mysql snippets/databases/mysql_example.py + +# Hoặc sử dụng Makefile +make db-run-snippet DB=mysql FILE=snippets/databases/mysql_example.py +``` + +### 3. Tắt Database khi không sử dụng + +```bash +# Sử dụng Docker Compose trực tiếp +docker-compose -f docker/environments/databases/docker-compose.yml stop mysql + +# Hoặc sử dụng Makefile +make db-stop DB=mysql +``` + +## Thông Tin Kết Nối Database + +Khi chạy code snippet với database, các biến môi trường sau sẽ được tự động truyền vào container: + +- `DB_HOST`: Hostname của database +- `DB_PORT`: Port của database +- `DB_USER`: Username để kết nối +- `DB_PASS`: Password để kết nối +- `DB_NAME`: Tên database +- `DB_CONN_STR`: Connection string đầy đủ + +### Các Connection String Mặc Định + +- **MySQL**: `mysql://user:password@tech-notes-mysql:3306/tech_notes` +- **PostgreSQL**: `postgresql://user:password@tech-notes-postgres:5432/tech_notes` +- **MongoDB**: `mongodb://user:password@tech-notes-mongodb:27017/tech_notes` +- **Redis**: `redis://tech-notes-redis:6379` +- **SQLite**: `sqlite:///data/tech_notes.db` + +### Cấu Hình Biến Môi Trường + +Để thay đổi các thông tin kết nối mặc định, bạn có thể tạo một file `.env` trong thư mục `docker/environments/databases/`: + +1. Sao chép file `.env.example` thành `.env`: + ```bash + cp docker/environments/databases/.env.example docker/environments/databases/.env + ``` + +2. Chỉnh sửa file `.env` để thay đổi các thông tin kết nối (tên người dùng, mật khẩu, tên database, v.v.) + +3. Khi chạy lệnh `run-db-snippet.sh` hoặc `make db-run-snippet`, các biến môi trường từ file `.env` sẽ được tự động sử dụng. + +Lưu ý: File `.env` đã được thêm vào `.gitignore` để không theo dõi bởi Git, đảm bảo thông tin nhạy cảm không bị đưa lên repository. + +## Ví Dụ Code Kết Nối + +### Python với MySQL + +```python +import os +import mysql.connector + +# Lấy thông tin kết nối từ biến môi trường +db_host = os.environ.get('DB_HOST', 'localhost') +db_port = os.environ.get('DB_PORT', '3306') +db_user = os.environ.get('DB_USER', 'user') +db_pass = os.environ.get('DB_PASS', 'password') +db_name = os.environ.get('DB_NAME', 'tech_notes') + +# Kết nối tới database +conn = mysql.connector.connect( + host=db_host, + port=db_port, + user=db_user, + password=db_pass, + database=db_name +) + +cursor = conn.cursor() +cursor.execute("SELECT * FROM users") +users = cursor.fetchall() + +for user in users: + print(user) + +conn.close() +``` + +### JavaScript với MongoDB + +```javascript +const { MongoClient } = require('mongodb'); + +// Lấy connection string từ biến môi trường +const uri = process.env.DB_CONN_STR || 'mongodb://user:password@localhost:27017/tech_notes'; + +async function main() { + const client = new MongoClient(uri); + + try { + await client.connect(); + const database = client.db('tech_notes'); + const users = database.collection('users'); + + const query = {}; + const cursor = users.find(query); + + if ((await cursor.count()) === 0) { + console.log("No documents found!"); + } + + await cursor.forEach(user => { + console.log(user); + }); + + } finally { + await client.close(); + } +} + +main().catch(console.error); +``` + +## Dữ Liệu Mẫu + +Mỗi database đã được cấu hình với dữ liệu mẫu sau: + +- Bảng/Collection `users` với 3 người dùng +- Bảng/Collection `posts` với 4 bài viết liên kết với người dùng + +Bạn có thể xem chi tiết dữ liệu mẫu trong các file init trong thư mục tương ứng của mỗi database. diff --git a/docker/QUICK_START.md b/docker/QUICK_START.md new file mode 100644 index 0000000..c2e9a5e --- /dev/null +++ b/docker/QUICK_START.md @@ -0,0 +1,61 @@ +# Docker Quick Start Guide + +This guide will help you get started with running code snippets in Docker environments. + +## Prerequisites + +1. Docker installed on your machine +2. Docker Compose installed on your machine + +## Getting Started + +### 1. Build the Docker Environments + +First, build all the Docker environments: + +```bash +# Using Docker Compose directly +docker-compose build + +# Or using the Makefile +make build +``` + +### 2. Run a Code Snippet + +You can run any code snippet from the repository using the provided script: + +```bash +# Using the script directly +./docker/run-snippet.sh snippets/algorithms/graph-traversal/graph_traversal.py + +# Or using the Makefile +make run-snippet FILE=snippets/algorithms/graph-traversal/graph_traversal.py +``` + +The script automatically detects the file extension and uses the appropriate Docker container for the language. + +### 3. Running Different Languages + +The setup supports multiple programming languages: + +- **Python**: `.py` files +- **JavaScript**: `.js` files +- **Java**: `.java` files +- **C/C++**: `.c` and `.cpp` files +- **Go**: `.go` files +- **Rust**: `.rs` files +- **PHP**: `.php` files +- **C#**: `.cs` files +- **Ruby**: `.rb` files + +### 4. Troubleshooting + +If you encounter any issues: + +1. Make sure Docker and Docker Compose are installed and running +2. Verify that the Docker daemon is running +3. Check the file path provided to the run-snippet script +4. Ensure the file extension is supported + +For more detailed information, see the [Docker README](README.md). diff --git a/docker/QUICK_START_vi.md b/docker/QUICK_START_vi.md new file mode 100644 index 0000000..cf05b0e --- /dev/null +++ b/docker/QUICK_START_vi.md @@ -0,0 +1,61 @@ +# Hướng Dẫn Nhanh Docker + +Hướng dẫn này sẽ giúp bạn bắt đầu chạy các đoạn mã trong môi trường Docker. + +## Yêu Cầu Tiên Quyết + +1. Docker đã được cài đặt trên máy của bạn +2. Docker Compose đã được cài đặt trên máy của bạn + +## Bắt Đầu + +### 1. Xây Dựng Môi Trường Docker + +Đầu tiên, xây dựng tất cả các môi trường Docker: + +```bash +# Sử dụng Docker Compose trực tiếp +docker-compose build + +# Hoặc sử dụng Makefile +make build +``` + +### 2. Chạy Một Đoạn Mã + +Bạn có thể chạy bất kỳ đoạn mã nào từ kho lưu trữ bằng script được cung cấp: + +```bash +# Sử dụng script trực tiếp +./docker/run-snippet.sh snippets/algorithms/graph-traversal/graph_traversal.py + +# Hoặc sử dụng Makefile +make run-snippet FILE=snippets/algorithms/graph-traversal/graph_traversal.py +``` + +Script tự động phát hiện phần mở rộng tệp và sử dụng container Docker thích hợp cho ngôn ngữ đó. + +### 3. Chạy Các Ngôn Ngữ Khác Nhau + +Thiết lập hỗ trợ nhiều ngôn ngữ lập trình: + +- **Python**: Tệp `.py` +- **JavaScript**: Tệp `.js` +- **Java**: Tệp `.java` +- **C/C++**: Tệp `.c` và `.cpp` +- **Go**: Tệp `.go` +- **Rust**: Tệp `.rs` +- **PHP**: Tệp `.php` +- **C#**: Tệp `.cs` +- **Ruby**: Tệp `.rb` + +### 4. Xử Lý Sự Cố + +Nếu bạn gặp bất kỳ vấn đề nào: + +1. Đảm bảo Docker và Docker Compose đã được cài đặt và đang chạy +2. Xác minh rằng Docker daemon đang chạy +3. Kiểm tra đường dẫn tệp được cung cấp cho script run-snippet +4. Đảm bảo phần mở rộng tệp được hỗ trợ + +Để biết thông tin chi tiết hơn, hãy xem [Docker README](README_vi.md). diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..737bd7f --- /dev/null +++ b/docker/README.md @@ -0,0 +1,110 @@ +# Docker Environment for Tech Notes Hub + +This directory contains Docker configurations for running code snippets in various programming languages and environments. + +## Directory Structure + +- `environments/` - Contains Dockerfiles and setup scripts for each language + - `python/` - Python environment + - `javascript/` - JavaScript/Node.js environment + - `java/` - Java environment + - `cpp/` - C/C++ environment + - `go/` - Go environment + - `rust/` - Rust environment + - `php/` - PHP environment + - `csharp/` - C# environment + - `ruby/` - Ruby environment + - `databases/` - Database environments + - `mysql/` - MySQL environment + - `postgresql/` - PostgreSQL environment + - `mongodb/` - MongoDB environment + - `redis/` - Redis environment + - `sqlite/` - SQLite environment + - `shell/` - Shell environment for Linux scripts and DevOps + - `databricks/` - Databricks/PySpark environment for data processing + +## Usage + +### Running a Snippet + +Use the provided `run-snippet.sh` script to run a code snippet in the appropriate Docker environment: + +```bash +./docker/run-snippet.sh snippets/algorithms/graph-traversal/graph_traversal.py +``` + +The script automatically detects the file extension and uses the appropriate Docker container. + +### Running a Snippet with Database Connection + +Use the provided `run-db-snippet.sh` script to run a code snippet with a database connection: + +```bash +./docker/run-db-snippet.sh mysql snippets/databases/mysql_example.py +``` + +The script starts the specified database, connects it to your code environment, and runs the code with the appropriate connection parameters. + +For more information on using databases, see the [Database Guide](DATABASE_GUIDE.md). + +### Building All Environments + +To build all Docker environments without running them: + +```bash +docker-compose build +``` + +### Running a Specific Environment + +To run a specific environment: + +```bash +docker-compose run --rm python snippets/path/to/your/script.py +docker-compose run --rm javascript snippets/path/to/your/script.js +docker-compose run --rm java snippets/path/to/your/script.java +# etc. +``` + +### Database Operations + +To start a specific database: + +```bash +docker-compose -f docker/environments/databases/docker-compose.yml up -d mysql +# Or using the Makefile +make db-start DB=mysql +``` + +To stop a database: + +```bash +docker-compose -f docker/environments/databases/docker-compose.yml stop mysql +# Or using the Makefile +make db-stop DB=mysql +``` + +### Database Environment Variables + +The database configurations use environment variables for sensitive information. A `.env.example` file is provided in the `docker/environments/databases/` directory as a template. + +To use custom database credentials: + +1. Copy the example file to create a `.env` file: + ```bash + cp docker/environments/databases/.env.example docker/environments/databases/.env + ``` + +2. Edit the `.env` file to set your own credentials. + +3. The `.env` file is included in `.gitignore` to ensure sensitive data isn't committed to the repository. + +## Adding New Languages + +To add support for a new language: + +1. Create a new directory in `environments/` for your language +2. Add a `Dockerfile` for the language +3. If needed, add an `entrypoint.sh` script for handling compilation/execution +4. Update the `docker-compose.yml` file to include your new service +5. Update the `run-snippet.sh` script to recognize the new file extension diff --git a/docker/README_vi.md b/docker/README_vi.md new file mode 100644 index 0000000..97afd17 --- /dev/null +++ b/docker/README_vi.md @@ -0,0 +1,110 @@ +# Môi Trường Docker cho Tech Notes Hub + +Thư mục này chứa các cấu hình Docker để chạy các đoạn mã trong nhiều ngôn ngữ lập trình và môi trường khác nhau. + +## Cấu Trúc Thư Mục + +- `environments/` - Chứa Dockerfiles và scripts cài đặt cho mỗi ngôn ngữ + - `python/` - Môi trường Python + - `javascript/` - Môi trường JavaScript/Node.js + - `java/` - Môi trường Java + - `cpp/` - Môi trường C/C++ + - `go/` - Môi trường Go + - `rust/` - Môi trường Rust + - `php/` - Môi trường PHP + - `csharp/` - Môi trường C# + - `ruby/` - Môi trường Ruby + - `databases/` - Môi trường Cơ sở dữ liệu + - `mysql/` - Môi trường MySQL + - `postgresql/` - Môi trường PostgreSQL + - `mongodb/` - Môi trường MongoDB + - `redis/` - Môi trường Redis + - `sqlite/` - Môi trường SQLite + - `shell/` - Môi trường Shell cho scripts Linux và DevOps + - `databricks/` - Môi trường Databricks/PySpark cho xử lý dữ liệu + +## Cách Sử Dụng + +### Chạy Một Đoạn Mã + +Sử dụng script `run-snippet.sh` được cung cấp để chạy một đoạn mã trong môi trường Docker phù hợp: + +```bash +./docker/run-snippet.sh snippets/algorithms/graph-traversal/graph_traversal.py +``` + +Script tự động phát hiện phần mở rộng tệp và sử dụng container Docker thích hợp. + +### Chạy Đoạn Mã với Kết Nối Cơ Sở Dữ Liệu + +Sử dụng script `run-db-snippet.sh` được cung cấp để chạy một đoạn mã với kết nối cơ sở dữ liệu: + +```bash +./docker/run-db-snippet.sh mysql snippets/databases/mysql_example.py +``` + +Script sẽ khởi động cơ sở dữ liệu được chỉ định, kết nối nó với môi trường mã của bạn, và chạy mã với các tham số kết nối thích hợp. + +Để biết thêm thông tin về việc sử dụng cơ sở dữ liệu, xem [Hướng Dẫn Cơ Sở Dữ Liệu](DATABASE_GUIDE_vi.md). + +### Xây Dựng Tất Cả Các Môi Trường + +Để xây dựng tất cả các môi trường Docker mà không chạy chúng: + +```bash +docker-compose build +``` + +### Chạy Một Môi Trường Cụ Thể + +Để chạy một môi trường cụ thể: + +```bash +docker-compose run --rm python snippets/path/to/your/script.py +docker-compose run --rm javascript snippets/path/to/your/script.js +docker-compose run --rm java snippets/path/to/your/script.java +# v.v. +``` + +### Thao Tác Cơ Sở Dữ Liệu + +Để khởi động một cơ sở dữ liệu cụ thể: + +```bash +docker-compose -f docker/environments/databases/docker-compose.yml up -d mysql +# Hoặc sử dụng Makefile +make db-start DB=mysql +``` + +Để dừng một cơ sở dữ liệu: + +```bash +docker-compose -f docker/environments/databases/docker-compose.yml stop mysql +# Hoặc sử dụng Makefile +make db-stop DB=mysql +``` + +### Biến Môi Trường Cơ Sở Dữ Liệu + +Cấu hình cơ sở dữ liệu sử dụng biến môi trường cho thông tin nhạy cảm. Một file `.env.example` được cung cấp trong thư mục `docker/environments/databases/` làm mẫu. + +Để sử dụng thông tin đăng nhập cơ sở dữ liệu tùy chỉnh: + +1. Sao chép file mẫu để tạo file `.env`: + ```bash + cp docker/environments/databases/.env.example docker/environments/databases/.env + ``` + +2. Chỉnh sửa file `.env` để đặt thông tin đăng nhập của riêng bạn. + +3. File `.env` được bao gồm trong `.gitignore` để đảm bảo dữ liệu nhạy cảm không được commit vào repository. + +## Thêm Ngôn Ngữ Mới + +Để thêm hỗ trợ cho một ngôn ngữ mới: + +1. Tạo một thư mục mới trong `environments/` cho ngôn ngữ của bạn +2. Thêm một `Dockerfile` cho ngôn ngữ đó +3. Nếu cần, thêm một script `entrypoint.sh` để xử lý biên dịch/thực thi +4. Cập nhật tệp `docker-compose.yml` để bao gồm dịch vụ mới của bạn +5. Cập nhật script `run-snippet.sh` để nhận diện phần mở rộng tệp mới diff --git a/docker/environments/cpp/Dockerfile b/docker/environments/cpp/Dockerfile new file mode 100644 index 0000000..53d1451 --- /dev/null +++ b/docker/environments/cpp/Dockerfile @@ -0,0 +1,17 @@ +FROM gcc:12 + +WORKDIR /app + +# Install necessary development tools +RUN apt-get update && apt-get install -y \ + cmake \ + make \ + gdb \ + valgrind \ + && rm -rf /var/lib/apt/lists/* + +# Set up compilation and execution command +COPY docker/environments/cpp/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/environments/cpp/entrypoint.sh b/docker/environments/cpp/entrypoint.sh new file mode 100644 index 0000000..6dd5edc --- /dev/null +++ b/docker/environments/cpp/entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +FILE=$1 +FILENAME=$(basename "$FILE") +EXTENSION="${FILENAME##*.}" +OUTPUT_NAME="${FILENAME%.*}" + +echo "Compiling $FILENAME..." +if [ "$EXTENSION" == "c" ]; then + gcc -o "$OUTPUT_NAME" "$FILE" -lm +elif [ "$EXTENSION" == "cpp" ]; then + g++ -o "$OUTPUT_NAME" "$FILE" -std=c++17 +else + echo "Unsupported file extension: $EXTENSION" + exit 1 +fi + +echo "Running $OUTPUT_NAME..." +./"$OUTPUT_NAME" diff --git a/docker/environments/csharp/Dockerfile b/docker/environments/csharp/Dockerfile new file mode 100644 index 0000000..f845454 --- /dev/null +++ b/docker/environments/csharp/Dockerfile @@ -0,0 +1,17 @@ +FROM mcr.microsoft.com/dotnet/sdk:7.0 + +WORKDIR /app + +# Install necessary .NET tools +RUN dotnet tool install -g dotnet-format && \ + dotnet tool install -g dotnet-trace && \ + dotnet tool install -g dotnet-counters + +# Add .NET tools to PATH +ENV PATH="$PATH:/root/.dotnet/tools" + +# Set up compilation and execution command +COPY docker/environments/csharp/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/environments/csharp/entrypoint.sh b/docker/environments/csharp/entrypoint.sh new file mode 100644 index 0000000..c280f81 --- /dev/null +++ b/docker/environments/csharp/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +FILE=$1 +FILENAME=$(basename "$FILE") +OUTPUT_NAME="${FILENAME%.*}" + +echo "Compiling $FILENAME..." +csc /out:"$OUTPUT_NAME.exe" "$FILE" + +echo "Running $OUTPUT_NAME.exe..." +mono "$OUTPUT_NAME.exe" diff --git a/docker/environments/databases/.env.example b/docker/environments/databases/.env.example new file mode 100644 index 0000000..88d2bbb --- /dev/null +++ b/docker/environments/databases/.env.example @@ -0,0 +1,39 @@ +# MySQL Configuration +MYSQL_ROOT_PASSWORD=rootpassword +MYSQL_DATABASE=tech_notes +MYSQL_USER=user +MYSQL_PASSWORD=password +MYSQL_PORT_EXPOSE=3306 +MYSQL_PORT=3306 + +# PostgreSQL Configuration +POSTGRES_USER=user +POSTGRES_PASSWORD=password +POSTGRES_DB=tech_notes +POSTGRES_PORT_EXPOSE=5432 +POSTGRES_PORT=5432 + +# MongoDB Configuration +MONGO_INITDB_ROOT_USERNAME=root +MONGO_INITDB_ROOT_PASSWORD=rootpassword +MONGO_INITDB_DATABASE=tech_notes +MONGO_USER=user +MONGO_PASSWORD=password +MONGO_PORT_EXPOSE=27017 +MONGO_PORT=27017 + +# Redis Configuration +REDIS_PASSWORD=your_secure_password +REDIS_PORT_EXPOSE=6379 +REDIS_PORT=6379 +REDIS_PASSWORD=your_secure_password + +# SQLite Configuration +# SQLite does not require credentials + +# MongoDB +MONGODB_PORT_EXPOSE=27017 +MONGODB_PORT=27017 +MONGODB_DATABASE=tech_notes +MONGODB_USER=root +MONGODB_PASSWORD=your_secure_password \ No newline at end of file diff --git a/docker/environments/databases/docker-compose.yml b/docker/environments/databases/docker-compose.yml new file mode 100644 index 0000000..4b5a42e --- /dev/null +++ b/docker/environments/databases/docker-compose.yml @@ -0,0 +1,76 @@ +version: '1.0' + +services: + # MySQL + mysql: + image: mysql:8.0 + container_name: tech-notes-mysql + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword} + MYSQL_DATABASE: ${MYSQL_DATABASE:-tech_notes} + MYSQL_USER: ${MYSQL_USER:-user} + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-password} + ports: + - "${MYSQL_PORT_EXPOSE:-3306}:${MYSQL_PORT:-3306}" + volumes: + - mysql_data:/var/lib/mysql + - ./mysql/init:/docker-entrypoint-initdb.d + restart: unless-stopped + command: --default-authentication-plugin=mysql_native_password + + # PostgreSQL + postgres: + image: postgres:15 + container_name: tech-notes-postgres + environment: + POSTGRES_USER: ${POSTGRES_USER:-user} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} + POSTGRES_DB: ${POSTGRES_DB:-tech_notes} + ports: + - "${POSTGRES_PORT_EXPOSE:-5432}:${POSTGRES_PORT:-5432}" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./postgresql/init:/docker-entrypoint-initdb.d + restart: unless-stopped + + # MongoDB + mongodb: + image: mongo:6 + container_name: tech-notes-mongodb + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:-root} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:-rootpassword} + MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE:-tech_notes} + ports: + - "${MONGO_PORT_EXPOSE:-27017}:${MONGO_PORT:-27017}" + volumes: + - mongodb_data:/data/db + - ./mongodb/init:/docker-entrypoint-initdb.d + restart: unless-stopped + + # Redis + redis: + image: redis:7 + container_name: tech-notes-redis + ports: + - "${REDIS_PORT_EXPOSE:-6379}:${REDIS_PORT:-6379}" + volumes: + - redis_data:/data + - ./redis/redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf + restart: unless-stopped + + # SQLite + sqlite: + build: ./sqlite + container_name: tech-notes-sqlite + volumes: + - sqlite_data:/data + restart: unless-stopped + +volumes: + mysql_data: + postgres_data: + mongodb_data: + redis_data: + sqlite_data: diff --git a/docker/environments/databases/mongodb/docker-compose.yml b/docker/environments/databases/mongodb/docker-compose.yml new file mode 100644 index 0000000..7ffb944 --- /dev/null +++ b/docker/environments/databases/mongodb/docker-compose.yml @@ -0,0 +1,19 @@ +version: '1.0' + +services: + mongodb: + image: mongo:6 + container_name: tech-notes-mongodb + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGODB_USER:-root} + MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_PASSWORD:-rootpassword} + MONGO_INITDB_DATABASE: ${MONGODB_DATABASE:-tech_notes} + ports: + - "${MONGODB_PORT_EXPOSE:-27017}:${MONGODB_PORT:-27017}" + volumes: + - mongodb_data:/data/db + - ./init:/docker-entrypoint-initdb.d + restart: unless-stopped + +volumes: + mongodb_data: diff --git a/docker/environments/databases/mongodb/init/01-sample-data.js b/docker/environments/databases/mongodb/init/01-sample-data.js new file mode 100644 index 0000000..fbbf409 --- /dev/null +++ b/docker/environments/databases/mongodb/init/01-sample-data.js @@ -0,0 +1,70 @@ +// Create a user for the database +db.createUser({ + user: 'user', + pwd: 'password', + roles: [ + { + role: 'readWrite', + db: 'tech_notes' + } + ] +}); + +// Switch to the tech_notes database +db = db.getSiblingDB('tech_notes'); + +// Create collections +db.createCollection('users'); +db.createCollection('posts'); + +// Insert sample data into users collection +db.users.insertMany([ + { + username: 'user1', + email: 'user1@example.com', + created_at: new Date() + }, + { + username: 'user2', + email: 'user2@example.com', + created_at: new Date() + }, + { + username: 'user3', + email: 'user3@example.com', + created_at: new Date() + } +]); + +// Get the user IDs +const user1 = db.users.findOne({ username: 'user1' }); +const user2 = db.users.findOne({ username: 'user2' }); +const user3 = db.users.findOne({ username: 'user3' }); + +// Insert sample data into posts collection +db.posts.insertMany([ + { + title: 'First Post', + content: 'This is the content of the first post', + user_id: user1._id, + created_at: new Date() + }, + { + title: 'Second Post', + content: 'This is the content of the second post', + user_id: user1._id, + created_at: new Date() + }, + { + title: 'Hello World', + content: 'Hello world post content', + user_id: user2._id, + created_at: new Date() + }, + { + title: 'Database Demo', + content: 'This is a demonstration of MongoDB database', + user_id: user3._id, + created_at: new Date() + } +]); diff --git a/docker/environments/databases/mysql/docker-compose.yml b/docker/environments/databases/mysql/docker-compose.yml new file mode 100644 index 0000000..515b79e --- /dev/null +++ b/docker/environments/databases/mysql/docker-compose.yml @@ -0,0 +1,21 @@ +version: '1.0' + +services: + mysql: + image: mysql:8.0 + container_name: tech-notes-mysql + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword} + MYSQL_DATABASE: ${MYSQL_DATABASE:-tech_notes} + MYSQL_USER: ${MYSQL_USER:-user} + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-password} + ports: + - "${MYSQL_PORT_EXPOSE:-3306}:${MYSQL_PORT:-3306}" + volumes: + - mysql_data:/var/lib/mysql + - ./init:/docker-entrypoint-initdb.d + restart: unless-stopped + command: --default-authentication-plugin=mysql_native_password + +volumes: + mysql_data: diff --git a/docker/environments/databases/mysql/init/01-sample-data.sql b/docker/environments/databases/mysql/init/01-sample-data.sql new file mode 100644 index 0000000..24c06f5 --- /dev/null +++ b/docker/environments/databases/mysql/init/01-sample-data.sql @@ -0,0 +1,28 @@ +-- Create sample tables +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + email VARCHAR(100) NOT NULL UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS posts ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + content TEXT, + user_id INT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) +); + +-- Insert sample data +INSERT INTO users (username, email) VALUES + ('user1', 'user1@example.com'), + ('user2', 'user2@example.com'), + ('user3', 'user3@example.com'); + +INSERT INTO posts (title, content, user_id) VALUES + ('First Post', 'This is the content of the first post', 1), + ('Second Post', 'This is the content of the second post', 1), + ('Hello World', 'Hello world post content', 2), + ('Database Demo', 'This is a demonstration of MySQL database', 3); diff --git a/docker/environments/databases/postgresql/docker-compose.yml b/docker/environments/databases/postgresql/docker-compose.yml new file mode 100644 index 0000000..ee7e7f2 --- /dev/null +++ b/docker/environments/databases/postgresql/docker-compose.yml @@ -0,0 +1,19 @@ +version: '1.0' + +services: + postgres: + image: postgres:15 + container_name: tech-notes-postgres + environment: + POSTGRES_USER: ${POSTGRES_USER:-user} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} + POSTGRES_DB: ${POSTGRES_DB:-tech_notes} + ports: + - "${POSTGRES_PORT_EXPOSE:-5432}:${POSTGRES_PORT:-5432}" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./init:/docker-entrypoint-initdb.d + restart: unless-stopped + +volumes: + postgres_data: diff --git a/docker/environments/databases/postgresql/init/01-sample-data.sql b/docker/environments/databases/postgresql/init/01-sample-data.sql new file mode 100644 index 0000000..3531b8b --- /dev/null +++ b/docker/environments/databases/postgresql/init/01-sample-data.sql @@ -0,0 +1,28 @@ +-- Create sample tables +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + email VARCHAR(100) NOT NULL UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS posts ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + content TEXT, + user_id INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) +); + +-- Insert sample data +INSERT INTO users (username, email) VALUES + ('user1', 'user1@example.com'), + ('user2', 'user2@example.com'), + ('user3', 'user3@example.com'); + +INSERT INTO posts (title, content, user_id) VALUES + ('First Post', 'This is the content of the first post', 1), + ('Second Post', 'This is the content of the second post', 1), + ('Hello World', 'Hello world post content', 2), + ('Database Demo', 'This is a demonstration of PostgreSQL database', 3); diff --git a/docker/environments/databases/redis/docker-compose.yml b/docker/environments/databases/redis/docker-compose.yml new file mode 100644 index 0000000..13505af --- /dev/null +++ b/docker/environments/databases/redis/docker-compose.yml @@ -0,0 +1,16 @@ +version: '1.0' + +services: + redis: + image: redis:7 + container_name: tech-notes-redis + ports: + - "${REDIS_PORT_EXPOSE:-6379}:${REDIS_PORT:-6379}" + volumes: + - redis_data:/data + - ./redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf + restart: unless-stopped + +volumes: + redis_data: diff --git a/docker/environments/databases/redis/redis.conf b/docker/environments/databases/redis/redis.conf new file mode 100644 index 0000000..57c1035 --- /dev/null +++ b/docker/environments/databases/redis/redis.conf @@ -0,0 +1,51 @@ +# Redis configuration file + +# Basic configuration +bind 0.0.0.0 +protected-mode yes +port ${REDIS_PORT:-6379} +tcp-backlog 511 +timeout 0 +tcp-keepalive 300 + +# General configuration +daemonize no +supervised no +pidfile /var/run/redis_${REDIS_PORT:-6379}.pid +loglevel notice +logfile "" +databases 16 + +# Snapshotting +save 900 1 +save 300 10 +save 60 10000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir /data + +# Security (no password by default for easy access) +# Comment this to disable password +requirepass ${REDIS_PASSWORD:-your_secure_password} + +# Memory management +maxmemory 256mb +maxmemory-policy allkeys-lru + +# Append only mode +appendonly yes +appendfilename "appendonly.aof" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb +aof-load-truncated yes + +# Lua scripting +lua-time-limit 5000 + +# Slow log +slowlog-log-slower-than 10000 +slowlog-max-len 128 diff --git a/docker/environments/databases/sqlite/Dockerfile b/docker/environments/databases/sqlite/Dockerfile new file mode 100644 index 0000000..0ede5ed --- /dev/null +++ b/docker/environments/databases/sqlite/Dockerfile @@ -0,0 +1,20 @@ +FROM alpine:3.17 + +# Install SQLite and required tools +RUN apk add --no-cache sqlite sqlite-dev sqlite-libs bash + +# Create data directory +RUN mkdir -p /data + +# Set working directory +WORKDIR /data + +# Copy init script +COPY init.sh /init.sh +COPY init.sql /init.sql + +# Make the script executable +RUN chmod +x /init.sh + +# Run init script on startup +ENTRYPOINT ["/init.sh"] diff --git a/docker/environments/databases/sqlite/docker-compose.yml b/docker/environments/databases/sqlite/docker-compose.yml new file mode 100644 index 0000000..fa0c955 --- /dev/null +++ b/docker/environments/databases/sqlite/docker-compose.yml @@ -0,0 +1,12 @@ +version: '1.0' + +services: + sqlite: + build: . + container_name: tech-notes-sqlite + volumes: + - sqlite_data:/data + restart: unless-stopped + +volumes: + sqlite_data: diff --git a/docker/environments/databases/sqlite/init.sh b/docker/environments/databases/sqlite/init.sh new file mode 100644 index 0000000..151ab06 --- /dev/null +++ b/docker/environments/databases/sqlite/init.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Create the database if it doesn't exist +if [ ! -f /data/tech_notes.db ]; then + echo "Creating new SQLite database..." + sqlite3 /data/tech_notes.db < /init.sql + echo "Database created and initialized." +else + echo "Database already exists." +fi + +# Keep container running +echo "SQLite container is ready. Database is at /data/tech_notes.db" +echo "Use 'docker exec -it tech-notes-sqlite sqlite3 /data/tech_notes.db' to access the database." +tail -f /dev/null diff --git a/docker/environments/databases/sqlite/init.sql b/docker/environments/databases/sqlite/init.sql new file mode 100644 index 0000000..78dd428 --- /dev/null +++ b/docker/environments/databases/sqlite/init.sql @@ -0,0 +1,28 @@ +-- Create sample tables +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT, + user_id INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) +); + +-- Insert sample data +INSERT INTO users (username, email) VALUES + ('user1', 'user1@example.com'), + ('user2', 'user2@example.com'), + ('user3', 'user3@example.com'); + +INSERT INTO posts (title, content, user_id) VALUES + ('First Post', 'This is the content of the first post', 1), + ('Second Post', 'This is the content of the second post', 1), + ('Hello World', 'Hello world post content', 2), + ('Database Demo', 'This is a demonstration of SQLite database', 3); diff --git a/docker/environments/databricks/Dockerfile b/docker/environments/databricks/Dockerfile new file mode 100644 index 0000000..b4f168c --- /dev/null +++ b/docker/environments/databricks/Dockerfile @@ -0,0 +1,36 @@ +FROM python:3.9-slim + +# Install Java +RUN apt-get update && \ + apt-get install -y openjdk-11-jdk && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Set Java home +ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 + +# Install PySpark and Databricks libraries +RUN pip install --no-cache-dir \ + pyspark==3.3.0 \ + delta-spark==2.1.0 \ + databricks-connect==10.4.* \ + matplotlib \ + pandas \ + numpy \ + scikit-learn + +# Install additional libraries for databricks +RUN pip install --no-cache-dir \ + mlflow \ + koalas \ + plotly \ + databricks-cli + +# Set up working directory +WORKDIR /app + +# Create PySpark configuration directory +RUN mkdir -p /root/.databricks-connect + +# Set entrypoint to Python +ENTRYPOINT ["python"] diff --git a/docker/environments/go/Dockerfile b/docker/environments/go/Dockerfile new file mode 100644 index 0000000..826ea4d --- /dev/null +++ b/docker/environments/go/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.21-alpine + +WORKDIR /app + +# Install necessary Go packages +RUN go install github.com/stretchr/testify@latest && \ + go install github.com/golang/mock/mockgen@latest && \ + go install golang.org/x/tools/cmd/goimports@latest + +# Set up command to run Go scripts +ENTRYPOINT ["go", "run"] diff --git a/docker/environments/java/Dockerfile b/docker/environments/java/Dockerfile new file mode 100644 index 0000000..c26955f --- /dev/null +++ b/docker/environments/java/Dockerfile @@ -0,0 +1,15 @@ +FROM openjdk:17-slim + +WORKDIR /app + +# Install build tools +RUN apt-get update && apt-get install -y \ + maven \ + gradle \ + && rm -rf /var/lib/apt/lists/* + +# Set up compilation and execution command +COPY docker/environments/java/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/environments/java/entrypoint.sh b/docker/environments/java/entrypoint.sh new file mode 100644 index 0000000..8266ec3 --- /dev/null +++ b/docker/environments/java/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +FILE=$1 +FILENAME=$(basename "$FILE") +CLASSNAME="${FILENAME%.*}" + +echo "Compiling $FILENAME..." +javac "$FILE" + +echo "Running $CLASSNAME..." +java -cp "$(dirname "$FILE")" "$CLASSNAME" diff --git a/docker/environments/javascript/Dockerfile b/docker/environments/javascript/Dockerfile new file mode 100644 index 0000000..65a40d5 --- /dev/null +++ b/docker/environments/javascript/Dockerfile @@ -0,0 +1,13 @@ +FROM node:18-slim + +WORKDIR /app + +# Install necessary Node.js packages +RUN npm install -g \ + jest \ + mocha \ + chai \ + eslint + +# Set up command to run JavaScript scripts +ENTRYPOINT ["node"] diff --git a/docker/environments/php/Dockerfile b/docker/environments/php/Dockerfile new file mode 100644 index 0000000..d1c4abe --- /dev/null +++ b/docker/environments/php/Dockerfile @@ -0,0 +1,23 @@ +FROM php:8.2-cli + +WORKDIR /app + +# Install necessary PHP extensions and tools +RUN apt-get update && apt-get install -y \ + libzip-dev \ + unzip \ + git \ + && docker-php-ext-install zip \ + && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ + && php composer-setup.php --install-dir=/usr/local/bin --filename=composer \ + && php -r "unlink('composer-setup.php');" \ + && rm -rf /var/lib/apt/lists/* + +# Install PHPUnit and other common packages +RUN composer global require phpunit/phpunit + +# Add composer's vendor bin to PATH +ENV PATH="/root/.composer/vendor/bin:${PATH}" + +# Set up command to run PHP scripts +ENTRYPOINT ["php"] diff --git a/docker/environments/python/Dockerfile b/docker/environments/python/Dockerfile new file mode 100644 index 0000000..81f51f3 --- /dev/null +++ b/docker/environments/python/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Install necessary Python packages +RUN pip install --no-cache-dir \ + pytest \ + numpy \ + pandas \ + matplotlib \ + scipy \ + scikit-learn + +# Set up command to run Python scripts +ENTRYPOINT ["python"] diff --git a/docker/environments/ruby/Dockerfile b/docker/environments/ruby/Dockerfile new file mode 100644 index 0000000..79f6f31 --- /dev/null +++ b/docker/environments/ruby/Dockerfile @@ -0,0 +1,14 @@ +FROM ruby:3.2-slim + +WORKDIR /app + +# Install necessary Ruby gems +RUN gem install \ + rspec \ + rubocop \ + pry \ + sinatra \ + rails + +# Set up command to run Ruby scripts +ENTRYPOINT ["ruby"] diff --git a/docker/environments/rust/Dockerfile b/docker/environments/rust/Dockerfile new file mode 100644 index 0000000..dbe73e0 --- /dev/null +++ b/docker/environments/rust/Dockerfile @@ -0,0 +1,13 @@ +FROM rust:1.75-slim + +WORKDIR /app + +# Install necessary Rust tools +RUN rustup component add rustfmt clippy && \ + cargo install cargo-watch cargo-expand + +# Set up command to run Rust scripts +COPY docker/environments/rust/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/environments/rust/entrypoint.sh b/docker/environments/rust/entrypoint.sh new file mode 100644 index 0000000..f2f726b --- /dev/null +++ b/docker/environments/rust/entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +FILE=$1 +FILENAME=$(basename "$FILE") + +echo "Running $FILENAME with rustc..." +rustc -o temp_executable "$FILE" && ./temp_executable +rm -f temp_executable diff --git a/docker/environments/shell/Dockerfile b/docker/environments/shell/Dockerfile new file mode 100644 index 0000000..37593d8 --- /dev/null +++ b/docker/environments/shell/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:22.04 + +# Install essential tools and utilities +RUN apt-get update && apt-get install -y \ + bash \ + bc \ + curl \ + git \ + iputils-ping \ + iproute2 \ + net-tools \ + nodejs \ + npm \ + openssh-client \ + procps \ + systemd \ + vim \ + wget \ + && rm -rf /var/lib/apt/lists/* + +# Set up working directory +WORKDIR /app + +# Set bash as the default shell +SHELL ["/bin/bash", "-c"] + +# Set entrypoint to bash shell +ENTRYPOINT ["/bin/bash"] diff --git a/docker/redis/redis.conf b/docker/redis/redis.conf new file mode 100644 index 0000000..e474859 --- /dev/null +++ b/docker/redis/redis.conf @@ -0,0 +1,4 @@ +# Security (no password by default for easy access) +# uncomment and change to set password +# requirepass yourpassword +requirepass ${REDIS_PASSWORD} diff --git a/docker/run-db-snippet.sh b/docker/run-db-snippet.sh new file mode 100755 index 0000000..35f67f4 --- /dev/null +++ b/docker/run-db-snippet.sh @@ -0,0 +1,141 @@ +#!/bin/bash +set -e + +if [ $# -lt 2 ]; then + echo "Usage: $0 " + echo "Database types: mysql, postgres, mongodb, redis, sqlite" + exit 1 +fi + +DB_TYPE="$1" +SNIPPET_PATH="$2" +EXTENSION="${SNIPPET_PATH##*.}" + +# Load environment variables from .env file if it exists +ENV_FILE="docker/environments/databases/.env" +if [ -f "$ENV_FILE" ]; then + echo "Loading environment variables from $ENV_FILE" + export $(grep -v '^#' "$ENV_FILE" | xargs) +else + echo "No .env file found at $ENV_FILE, using default values" +fi + +# Start the selected database +function start_database() { + echo "Starting $DB_TYPE database..." + docker-compose -f docker/environments/databases/docker-compose.yml up -d "$DB_TYPE" + + # Give some time for the database to start + sleep 5 + + echo "$DB_TYPE database is ready." +} + +# Map file extensions to Docker services +case "$EXTENSION" in + py) + SERVICE="python" + ;; + js) + SERVICE="javascript" + ;; + java) + SERVICE="java" + ;; + c|cpp) + SERVICE="cpp" + ;; + go) + SERVICE="go" + ;; + rs) + SERVICE="rust" + ;; + php) + SERVICE="php" + ;; + cs) + SERVICE="csharp" + ;; + rb) + SERVICE="ruby" + ;; + *) + echo "Unsupported file extension: $EXTENSION" + exit 1 + ;; +esac + +# Get the database connection details +case "$DB_TYPE" in + mysql) + DB_HOST="tech-notes-mysql" + DB_PORT="3306" + DB_USER="${MYSQL_USER:-user}" + DB_PASS="${MYSQL_PASSWORD:-password}" + DB_NAME="${MYSQL_DATABASE:-tech_notes}" + DB_CONN_STR="mysql://$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME" + ;; + postgres) + DB_HOST="tech-notes-postgres" + DB_PORT="5432" + DB_USER="${POSTGRES_USER:-user}" + DB_PASS="${POSTGRES_PASSWORD:-password}" + DB_NAME="${POSTGRES_DB:-tech_notes}" + DB_CONN_STR="postgresql://$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME" + ;; + mongodb) + DB_HOST="tech-notes-mongodb" + DB_PORT="27017" + DB_USER="${MONGO_USER:-user}" + DB_PASS="${MONGO_PASSWORD:-password}" + DB_NAME="${MONGO_INITDB_DATABASE:-tech_notes}" + DB_CONN_STR="mongodb://$DB_USER:$DB_PASS@$DB_HOST:$DB_PORT/$DB_NAME" + ;; + redis) + DB_HOST="tech-notes-redis" + DB_PORT="6379" + DB_PASS="${REDIS_PASSWORD:-}" + if [ -n "$DB_PASS" ]; then + DB_CONN_STR="redis://:$DB_PASS@$DB_HOST:$DB_PORT" + else + DB_CONN_STR="redis://$DB_HOST:$DB_PORT" + fi + ;; + sqlite) + DB_CONN_STR="sqlite:///data/tech_notes.db" + ;; + *) + echo "Unsupported database type: $DB_TYPE" + exit 1 + ;; +esac + +# Start the database +start_database + +# Get absolute path from relative path +ABSOLUTE_PATH=$(realpath "$SNIPPET_PATH") +# Get the path relative to the project root +RELATIVE_PATH=$(realpath --relative-to="$(pwd)" "$ABSOLUTE_PATH") + +# Create a custom docker network if it doesn't exist +if ! docker network inspect tech-notes-network >/dev/null 2>&1; then + echo "Creating docker network: tech-notes-network" + docker network create tech-notes-network + + # Add the database container to the network + echo "Adding $DB_TYPE database to the network" + docker network connect tech-notes-network "tech-notes-$DB_TYPE" +fi + +echo "Running $RELATIVE_PATH in $SERVICE environment with $DB_TYPE database..." +docker-compose run --rm \ + --network tech-notes-network \ + -e DB_HOST="$DB_HOST" \ + -e DB_PORT="$DB_PORT" \ + -e DB_USER="$DB_USER" \ + -e DB_PASS="$DB_PASS" \ + -e DB_NAME="$DB_NAME" \ + -e DB_CONN_STR="$DB_CONN_STR" \ + "$SERVICE" "/app/$RELATIVE_PATH" diff --git a/docker/run-snippet.sh b/docker/run-snippet.sh new file mode 100755 index 0000000..913c8d5 --- /dev/null +++ b/docker/run-snippet.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +if [ $# -lt 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +SNIPPET_PATH="$1" +EXTENSION="${SNIPPET_PATH##*.}" + +# Map file extensions to Docker services +case "$EXTENSION" in + py) + SERVICE="python" + ;; + js) + SERVICE="javascript" + ;; + java) + SERVICE="java" + ;; + c|cpp) + SERVICE="cpp" + ;; + go) + SERVICE="go" + ;; + rs) + SERVICE="rust" + ;; + php) + SERVICE="php" + ;; + cs) + SERVICE="csharp" + ;; + rb) + SERVICE="ruby" + ;; + sh) + SERVICE="shell" + ;; + ipynb) + SERVICE="databricks" + ;; + *) + echo "Unsupported file extension: $EXTENSION" + exit 1 + ;; +esac + +# Get absolute path from relative path +ABSOLUTE_PATH=$(realpath "$SNIPPET_PATH") +# Get the path relative to the project root +RELATIVE_PATH=$(realpath --relative-to="$(pwd)" "$ABSOLUTE_PATH") + +# Special handling for shell scripts +if [ "$EXTENSION" == "sh" ]; then + echo "Running $RELATIVE_PATH in $SERVICE environment..." + # Make the script executable in the container and run it + docker-compose run --rm "$SERVICE" -c "chmod +x /app/$RELATIVE_PATH && /app/$RELATIVE_PATH" +elif [ "$EXTENSION" == "ipynb" ]; then + echo "Running Jupyter notebook $RELATIVE_PATH in $SERVICE environment..." + docker-compose run --rm "$SERVICE" -m jupyter nbconvert --execute --to notebook --inplace "/app/$RELATIVE_PATH" +else + echo "Running $RELATIVE_PATH in $SERVICE environment..." + docker-compose run --rm "$SERVICE" "/app/$RELATIVE_PATH" +fi diff --git a/snippets/databases/.env.example b/snippets/databases/.env.example index 46b59e9..ef9053e 100644 --- a/snippets/databases/.env.example +++ b/snippets/databases/.env.example @@ -29,4 +29,4 @@ DAPPER_CONNECTION_STRING=Data Source=:memory: # SQLite: sqlite:///path/to/database.db # PostgreSQL: postgresql://user:password@localhost/dbname # MySQL: mysql://user:password@localhost/dbname -SQLALCHEMY_DATABASE_URL=sqlite:///:memory: \ No newline at end of file +SQLALCHEMY_DATABASE_URL=sqlite:///:memory: From 3962ec407ea111147c88ace04ba9d6848ff1c0bb Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 5 Jun 2025 16:08:08 +0700 Subject: [PATCH 35/46] update: quick update md file & env docker --- PULL_REQUEST_RULES.md | 2 +- PULL_REQUEST_RULES_vi.md | 4 ++-- docker/run-db-snippet.sh | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/PULL_REQUEST_RULES.md b/PULL_REQUEST_RULES.md index b25ef90..c0ce0cb 100644 --- a/PULL_REQUEST_RULES.md +++ b/PULL_REQUEST_RULES.md @@ -71,7 +71,7 @@ Select the appropriate options: - Use clear, **conversational but concise** explanations - Use correct heading hierarchy (`#`, `##`, `###`, etc.) -- Name files using lowercase and hyphens: `binary-search.md` +- Name files using lowercase and underscore: `binary_search.md` - Place content in the appropriate folder (`algorithms/`, `design-patterns/`, etc.) - For translations, use the `i18n/[language_code]/` structure, mirroring the main docs structure - Update SUMMARY.md to include new content or translations diff --git a/PULL_REQUEST_RULES_vi.md b/PULL_REQUEST_RULES_vi.md index a8d0fec..bdcdb86 100644 --- a/PULL_REQUEST_RULES_vi.md +++ b/PULL_REQUEST_RULES_vi.md @@ -71,8 +71,8 @@ Chọn các tùy chọn thích hợp: - Trình bày **ngắn gọn, dễ hiểu, gần gũi** - Sử dụng đúng cấp độ tiêu đề: `#`, `##`, `###`,... -- Đặt tên file bằng chữ thường, dùng dấu gạch ngang: `binary-search.md` -- Đặt đúng thư mục chuyên đề (`algorithms/`, `design-patterns/`, `aws/`,...) +- Đặt tên file bằng chữ thường, dùng dấu gạch dưới: `binary_search.md` +- Đặt đúng thư mục docs (`docs/algorithms/`, `docs/design-patterns/`, `docs/aws/`,...) - Đối với bản dịch, sử dụng cấu trúc `i18n/[mã_ngôn_ngữ]/`, phản ánh cấu trúc thư mục chính - Cập nhật SUMMARY.md để bao gồm nội dung mới hoặc bản dịch diff --git a/docker/run-db-snippet.sh b/docker/run-db-snippet.sh index 35f67f4..1a0b754 100755 --- a/docker/run-db-snippet.sh +++ b/docker/run-db-snippet.sh @@ -70,7 +70,7 @@ esac case "$DB_TYPE" in mysql) DB_HOST="tech-notes-mysql" - DB_PORT="3306" + DB_PORT="${MYSQL_PORT_EXPOSE:-3306}" DB_USER="${MYSQL_USER:-user}" DB_PASS="${MYSQL_PASSWORD:-password}" DB_NAME="${MYSQL_DATABASE:-tech_notes}" @@ -78,7 +78,7 @@ case "$DB_TYPE" in ;; postgres) DB_HOST="tech-notes-postgres" - DB_PORT="5432" + DB_PORT="${POSTGRES_PORT_EXPOSE:-5432}" DB_USER="${POSTGRES_USER:-user}" DB_PASS="${POSTGRES_PASSWORD:-password}" DB_NAME="${POSTGRES_DB:-tech_notes}" @@ -86,7 +86,7 @@ case "$DB_TYPE" in ;; mongodb) DB_HOST="tech-notes-mongodb" - DB_PORT="27017" + DB_PORT="${MONGO_PORT_EXPOSE:-27017}" DB_USER="${MONGO_USER:-user}" DB_PASS="${MONGO_PASSWORD:-password}" DB_NAME="${MONGO_INITDB_DATABASE:-tech_notes}" @@ -94,7 +94,7 @@ case "$DB_TYPE" in ;; redis) DB_HOST="tech-notes-redis" - DB_PORT="6379" + DB_PORT="${REDIS_PORT_EXPOSE:-6379}" DB_PASS="${REDIS_PASSWORD:-}" if [ -n "$DB_PASS" ]; then DB_CONN_STR="redis://:$DB_PASS@$DB_HOST:$DB_PORT" From afc321b4172e8d162043ccfb22878183cc135276 Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Fri, 6 Jun 2025 12:50:41 +0700 Subject: [PATCH 36/46] Update funding.yml --- .github/funding.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/funding.yml b/.github/funding.yml index c548d3e..039e040 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -2,3 +2,4 @@ #github: tanthanhdev ko_fi: devphan +custom: ["https://www.paypal.com/paypalme/ThanhPhan481"] From b312c17237a87aca37598e8b25c9883639c579fa Mon Sep 17 00:00:00 2001 From: Dev Phan Date: Fri, 6 Jun 2025 12:51:41 +0700 Subject: [PATCH 37/46] Update funding.yml --- .github/funding.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/funding.yml b/.github/funding.yml index 039e040..e89127a 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -2,4 +2,4 @@ #github: tanthanhdev ko_fi: devphan -custom: ["https://www.paypal.com/paypalme/ThanhPhan481"] +custom: ["https://www.paypal.me/ThanhPhan481"] From 2806d1ac89149f28de983ed0313150fb9686b96c Mon Sep 17 00:00:00 2001 From: Dev Date: Fri, 6 Jun 2025 17:24:33 +0700 Subject: [PATCH 38/46] feature: add blog website --- website/.gitignore | 41 + website/README.md | 125 + website/WEBSITE_README.md | 32 + website/components.json | 21 + website/eslint.config.mjs | 16 + website/next-seo.config.js | 59 + website/next.config.js | 0 website/next.config.ts | 61 + website/package-lock.json | 9578 +++++++++++++++++ website/package.json | 58 + website/pages/sitemap.xml.js | 149 + website/postcss.config.mjs | 5 + website/public/file.svg | 1 + website/public/globe.svg | 1 + website/public/next.svg | 1 + website/public/og-image.jpg | Bin 0 -> 787122 bytes website/public/robots.txt | 25 + website/public/sitemap.txt | 10 + website/public/vercel.svg | 1 + website/public/window.svg | 1 + website/src/app/[locale]/about/page.tsx | 65 + website/src/app/[locale]/blog/[slug]/page.tsx | 259 + website/src/app/[locale]/blog/page.tsx | 272 + website/src/app/[locale]/layout.tsx | 99 + website/src/app/[locale]/page.tsx | 125 + website/src/app/[locale]/privacy/page.tsx | 71 + website/src/app/[locale]/terms/page.tsx | 78 + website/src/app/api/robots.txt/route.ts | 37 + website/src/app/api/sitemap.xml/route.ts | 143 + website/src/app/favicon.ico | Bin 0 -> 787122 bytes website/src/app/globals.css | 241 + website/src/app/layout.tsx | 34 + website/src/app/page.tsx | 103 + .../src/components/blog/category-filter.tsx | 48 + website/src/components/blog/code-snippet.tsx | 85 + .../src/components/blog/markdown-content.tsx | 179 + website/src/components/layout/footer.tsx | 65 + website/src/components/layout/header.tsx | 153 + .../src/components/layout/theme-provider.tsx | 23 + .../layout/translation-provider.tsx | 66 + website/src/components/search.tsx | 119 + website/src/components/theme-toggle.tsx | 26 + website/src/components/ui/accordion.tsx | 62 + website/src/components/ui/button.tsx | 56 + website/src/components/ui/card.tsx | 92 + website/src/components/ui/dialog.tsx | 143 + website/src/components/ui/dropdown-menu.tsx | 257 + website/src/components/ui/input.tsx | 25 + website/src/components/ui/navigation-menu.tsx | 168 + website/src/components/ui/sheet.tsx | 140 + website/src/config/site.ts | 31 + website/src/data/blog-posts.ts | 179 + website/src/data/i18n/en/about.json | 21 + website/src/data/i18n/en/common.json | 111 + website/src/data/i18n/en/home.json | 16 + website/src/data/i18n/vi/about.json | 21 + website/src/data/i18n/vi/common.json | 111 + website/src/data/i18n/vi/home.json | 16 + website/src/lib/content-mapper.ts | 610 ++ website/src/lib/i18n/settings.ts | 156 + website/src/lib/metadata.ts | 89 + website/src/lib/types.ts | 45 + website/src/lib/utils.ts | 6 + website/src/middleware.ts | 61 + website/src/types/index.ts | 22 + website/tailwind.config.js | 80 + website/tsconfig.json | 27 + 67 files changed, 15021 insertions(+) create mode 100644 website/.gitignore create mode 100644 website/README.md create mode 100644 website/WEBSITE_README.md create mode 100644 website/components.json create mode 100644 website/eslint.config.mjs create mode 100644 website/next-seo.config.js create mode 100644 website/next.config.js create mode 100644 website/next.config.ts create mode 100644 website/package-lock.json create mode 100644 website/package.json create mode 100644 website/pages/sitemap.xml.js create mode 100644 website/postcss.config.mjs create mode 100644 website/public/file.svg create mode 100644 website/public/globe.svg create mode 100644 website/public/next.svg create mode 100644 website/public/og-image.jpg create mode 100644 website/public/robots.txt create mode 100644 website/public/sitemap.txt create mode 100644 website/public/vercel.svg create mode 100644 website/public/window.svg create mode 100644 website/src/app/[locale]/about/page.tsx create mode 100644 website/src/app/[locale]/blog/[slug]/page.tsx create mode 100644 website/src/app/[locale]/blog/page.tsx create mode 100644 website/src/app/[locale]/layout.tsx create mode 100644 website/src/app/[locale]/page.tsx create mode 100644 website/src/app/[locale]/privacy/page.tsx create mode 100644 website/src/app/[locale]/terms/page.tsx create mode 100644 website/src/app/api/robots.txt/route.ts create mode 100644 website/src/app/api/sitemap.xml/route.ts create mode 100644 website/src/app/favicon.ico create mode 100644 website/src/app/globals.css create mode 100644 website/src/app/layout.tsx create mode 100644 website/src/app/page.tsx create mode 100644 website/src/components/blog/category-filter.tsx create mode 100644 website/src/components/blog/code-snippet.tsx create mode 100644 website/src/components/blog/markdown-content.tsx create mode 100644 website/src/components/layout/footer.tsx create mode 100644 website/src/components/layout/header.tsx create mode 100644 website/src/components/layout/theme-provider.tsx create mode 100644 website/src/components/layout/translation-provider.tsx create mode 100644 website/src/components/search.tsx create mode 100644 website/src/components/theme-toggle.tsx create mode 100644 website/src/components/ui/accordion.tsx create mode 100644 website/src/components/ui/button.tsx create mode 100644 website/src/components/ui/card.tsx create mode 100644 website/src/components/ui/dialog.tsx create mode 100644 website/src/components/ui/dropdown-menu.tsx create mode 100644 website/src/components/ui/input.tsx create mode 100644 website/src/components/ui/navigation-menu.tsx create mode 100644 website/src/components/ui/sheet.tsx create mode 100644 website/src/config/site.ts create mode 100644 website/src/data/blog-posts.ts create mode 100644 website/src/data/i18n/en/about.json create mode 100644 website/src/data/i18n/en/common.json create mode 100644 website/src/data/i18n/en/home.json create mode 100644 website/src/data/i18n/vi/about.json create mode 100644 website/src/data/i18n/vi/common.json create mode 100644 website/src/data/i18n/vi/home.json create mode 100644 website/src/lib/content-mapper.ts create mode 100644 website/src/lib/i18n/settings.ts create mode 100644 website/src/lib/metadata.ts create mode 100644 website/src/lib/types.ts create mode 100644 website/src/lib/utils.ts create mode 100644 website/src/middleware.ts create mode 100644 website/src/types/index.ts create mode 100644 website/tailwind.config.js create mode 100644 website/tsconfig.json diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..fac4a7b --- /dev/null +++ b/website/README.md @@ -0,0 +1,125 @@ +# Tech Notes Hub Website + +A modern blog website built with Next.js, Tailwind CSS, shadcn UI, and i18n support for multilingual content (Vietnamese and English). + +## Features + +- **Modern UI**: Clean, minimal design with Tailwind CSS and shadcn UI components +- **Bilingual Support**: Full i18n integration with Vietnamese and English translations +- **Blog System**: Complete blog functionality with Markdown content rendering +- **Dark/Light Mode**: Theme switching with system preference detection +- **SEO Optimized**: Complete SEO setup with meta tags, OpenGraph data, dynamic sitemap.xml, robots.txt, structured metadata, canonical URLs and language alternates +- **Responsive Design**: Mobile-first approach, works on all devices + +## Tech Stack + +- [Next.js 14](https://nextjs.org/) - React framework +- [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS framework +- [shadcn UI](https://ui.shadcn.com/) - UI component library +- [React Markdown](https://github.com/remarkjs/react-markdown) - Markdown renderer +- [i18n](https://nextjs.org/docs/app/building-your-application/routing/internationalization) - Internationalization +- [next-themes](https://github.com/pacocoursey/next-themes) - Theme management + +## Getting Started + +### Prerequisites + +- Node.js 18.17 or later +- npm or yarn + +### Installation + +1. Clone the repository + ```bash + git clone + cd website + ``` + +2. Install dependencies + ```bash + npm install + # or + yarn install + ``` + +3. Run the development server +```bash +npm run dev +# or +yarn dev +``` + +4. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result + +## Project Structure + +``` +website/ +├── public/ # Static assets +├── src/ +│ ├── app/ # App router pages +│ │ ├── [locale]/ # Locale-specific routes +│ │ │ ├── blog/ # Blog pages +│ │ │ ├── about/ # About page +│ │ ├── components/ # React components +│ │ │ ├── ui/ # UI components (shadcn) +│ │ │ ├── layout/ # Layout components +│ │ │ ├── blog/ # Blog-specific components +│ │ ├── data/ # Data sources +│ │ │ ├── i18n/ # Translation files +│ │ │ ├── blog-posts.ts # Blog post data +│ │ ├── lib/ # Utility functions +│ │ │ ├── i18n/ # i18n utilities +│ │ │ ├── utils.ts # Helper functions +│ │ │ ├── types.ts # TypeScript types +├── next.config.ts # Next.js configuration +├── tailwind.config.js # Tailwind CSS configuration +``` + +## Adding Content + +### Blog Posts + +To add new blog posts, edit the `src/data/blog-posts.ts` file. Each post should follow the BlogPost interface defined in `src/lib/types.ts`. + +### Translations + +Add or edit translations in the following files: +- `src/data/i18n/en/common.json` - English translations +- `src/data/i18n/vi/common.json` - Vietnamese translations + +## Deployment + +### Build for Production + +```bash +npm run build +# or +yarn build +``` + +### Deploy to Vercel + +The easiest way to deploy the application is to use the [Vercel Platform](https://vercel.com/). + +1. Push your code to a Git repository (GitHub, GitLab, or Bitbucket) +2. Import the project to Vercel +3. Vercel will detect Next.js and configure the build settings automatically +4. Click "Deploy" + +### Other Deployment Options + +You can also deploy to other platforms like Netlify, AWS Amplify, or traditional hosting with a Node.js server. + +For static export: + +```bash +npm run build +npm run export +``` + +This will generate a static version of the site in the `out` directory that can be served by any static hosting service. + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/website/WEBSITE_README.md b/website/WEBSITE_README.md new file mode 100644 index 0000000..7b63ee8 --- /dev/null +++ b/website/WEBSITE_README.md @@ -0,0 +1,32 @@ +# Tech Notes Hub Blog Website + +This is the blog website component of the Tech Notes Hub project. The website is built using Next.js, Tailwind CSS, shadcn UI, and i18n support for multilingual content (Vietnamese and English). + +## Overview + +The blog website serves as a frontend for displaying technical content and notes from the Tech Notes Hub repository. It provides a modern, responsive interface for users to browse and read technical articles. + +## Getting Started + +The blog website is located in the `/website` directory. To start working with it: + +```bash +cd website +npm install +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000) to view the site in your browser. + +## Features + +- Modern UI built with Next.js and Tailwind CSS +- Bilingual support (Vietnamese and English) +- Dark/Light mode +- Responsive design +- SEO optimized +- Markdown content rendering + +## Documentation + +For detailed information about the blog website, including setup instructions, project structure, and deployment guidelines, please refer to the [Website README](/website/README.md) in the website directory. diff --git a/website/components.json b/website/components.json new file mode 100644 index 0000000..ffe928f --- /dev/null +++ b/website/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/website/eslint.config.mjs b/website/eslint.config.mjs new file mode 100644 index 0000000..c85fb67 --- /dev/null +++ b/website/eslint.config.mjs @@ -0,0 +1,16 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; diff --git a/website/next-seo.config.js b/website/next-seo.config.js new file mode 100644 index 0000000..f5f4328 --- /dev/null +++ b/website/next-seo.config.js @@ -0,0 +1,59 @@ +// Default SEO configuration +const defaultSEOConfig = { + titleTemplate: '%s - Tech Notes Hub', + defaultTitle: 'Tech Notes Hub', + description: 'A collection of tech notes, code snippets, and technical guides for developers', + canonical: 'https://technotes.example.com', + openGraph: { + type: 'website', + locale: 'vi_VN', + url: 'https://technotes.example.com', + siteName: 'Tech Notes Hub', + title: 'Tech Notes Hub', + description: 'A collection of tech notes, code snippets, and technical guides for developers', + images: [ + { + url: 'https://technotes.example.com/og-image.jpg', + width: 1200, + height: 630, + alt: 'Tech Notes Hub', + }, + ], + }, + twitter: { + handle: '@technotes', + site: '@technotes', + cardType: 'summary_large_image', + }, + additionalMetaTags: [ + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + { + name: 'author', + content: 'Tech Notes Team', + }, + { + name: 'keywords', + content: 'tech, programming, algorithms, design patterns, databases, devops, linux, system design, testing', + }, + ], + additionalLinkTags: [ + { + rel: 'icon', + href: '/favicon.ico', + }, + { + rel: 'apple-touch-icon', + href: '/apple-touch-icon.png', + sizes: '180x180', + }, + { + rel: 'manifest', + href: '/site.webmanifest', + }, + ], +}; + +export default defaultSEOConfig; diff --git a/website/next.config.js b/website/next.config.js new file mode 100644 index 0000000..e69de29 diff --git a/website/next.config.ts b/website/next.config.ts new file mode 100644 index 0000000..e59dc5b --- /dev/null +++ b/website/next.config.ts @@ -0,0 +1,61 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + reactStrictMode: true, + swcMinify: true, + images: { + domains: ['technotes.example.com'], + }, + // Configure experimental features if needed + experimental: { + // Add experimental features here when required + }, + // Add custom headers for security and caching + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'X-DNS-Prefetch-Control', + value: 'on', + }, + { + key: 'X-XSS-Protection', + value: '1; mode=block', + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + { + key: 'Referrer-Policy', + value: 'strict-origin-when-cross-origin', + }, + ], + }, + { + // Apply caching headers for static assets + source: '/(.*).(jpg|jpeg|png|gif|webp|svg|ico)', + headers: [ + { + key: 'Cache-Control', + value: 'public, max-age=31536000, immutable', + } + ], + }, + ]; + }, + // Redirect from root to default locale + async redirects() { + return [ + { + source: '/', + destination: '/en', + permanent: true, + }, + ]; + }, +}; + +export default nextConfig; diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..171494e --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,9578 @@ +{ + "name": "website", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "website", + "version": "0.1.0", + "dependencies": { + "@radix-ui/react-accordion": "^1.2.11", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-toast": "^1.2.14", + "@reduxjs/toolkit": "^2.8.2", + "@tailwindcss/typography": "^0.5.16", + "@types/react-syntax-highlighter": "^15.5.13", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "github-slugger": "^2.0.0", + "i18next": "^25.2.1", + "i18next-browser-languagedetector": "^8.1.0", + "lucide-react": "^0.513.0", + "next": "15.3.3", + "next-i18next": "^15.4.2", + "next-themes": "^0.4.6", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-i18next": "^15.5.2", + "react-markdown": "^10.1.0", + "react-redux": "^9.2.0", + "react-syntax-highlighter": "^15.6.1", + "rehype-highlight": "^7.0.2", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1", + "shadcn-ui": "^0.9.5", + "sonner": "^2.0.5", + "tailwind-merge": "^3.3.0", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.3.3", + "tailwindcss": "^4", + "tw-animate-css": "^1.3.4", + "typescript": "^5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", + "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", + "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", + "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", + "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz", + "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.3.tgz", + "integrity": "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz", + "integrity": "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.2.tgz", + "integrity": "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz", + "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz", + "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz", + "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz", + "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz", + "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz", + "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz", + "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz", + "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz", + "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.2.tgz", + "integrity": "sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.2.tgz", + "integrity": "sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.2.tgz", + "integrity": "sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.1.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.2.tgz", + "integrity": "sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.2.tgz", + "integrity": "sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.2.tgz", + "integrity": "sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.1.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.2.tgz", + "integrity": "sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.2.tgz", + "integrity": "sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.2.tgz", + "integrity": "sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.2.tgz", + "integrity": "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz", + "integrity": "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@next/env": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.3.tgz", + "integrity": "sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.3.tgz", + "integrity": "sha512-VKZJEiEdpKkfBmcokGjHu0vGDG+8CehGs90tBEy/IDoDDKGngeyIStt2MmE5FYNyU9BhgR7tybNWTAJY/30u+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.3.tgz", + "integrity": "sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.3.tgz", + "integrity": "sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.3.tgz", + "integrity": "sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.3.tgz", + "integrity": "sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.3.tgz", + "integrity": "sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.3.tgz", + "integrity": "sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.3.tgz", + "integrity": "sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.3.tgz", + "integrity": "sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.11.tgz", + "integrity": "sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collapsible": "1.1.11", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.11.tgz", + "integrity": "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", + "integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz", + "integrity": "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.15.tgz", + "integrity": "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.13.tgz", + "integrity": "sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", + "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.14.tgz", + "integrity": "sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz", + "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz", + "integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.8.tgz", + "integrity": "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.8" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.8.tgz", + "integrity": "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.8", + "@tailwindcss/oxide-darwin-arm64": "4.1.8", + "@tailwindcss/oxide-darwin-x64": "4.1.8", + "@tailwindcss/oxide-freebsd-x64": "4.1.8", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", + "@tailwindcss/oxide-linux-x64-musl": "4.1.8", + "@tailwindcss/oxide-wasm32-wasi": "4.1.8", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.8.tgz", + "integrity": "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.8.tgz", + "integrity": "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.8.tgz", + "integrity": "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.8.tgz", + "integrity": "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.8.tgz", + "integrity": "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.8.tgz", + "integrity": "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.8.tgz", + "integrity": "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.8.tgz", + "integrity": "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.8.tgz", + "integrity": "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.8.tgz", + "integrity": "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.10", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.8.tgz", + "integrity": "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.8.tgz", + "integrity": "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.8.tgz", + "integrity": "sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.8", + "@tailwindcss/oxide": "4.1.8", + "postcss": "^8.4.41", + "tailwindcss": "4.1.8" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", + "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.58", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.58.tgz", + "integrity": "sha512-UvxetCgGwZ9HmsgGZ2tpStt6CiFU1bu28ftHWpDyfthsCt7OHXas0C7j0VgO3gBq8UHKI785wXmtcQVhLekcRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/react": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz", + "integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", + "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/type-utils": "8.33.1", + "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.33.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", + "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", + "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.33.1", + "@typescript-eslint/types": "^8.33.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", + "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", + "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", + "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/utils": "8.33.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", + "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", + "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.33.1", + "@typescript-eslint/tsconfig-utils": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", + "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", + "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.33.1", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.11.tgz", + "integrity": "sha512-i3/wlWjQJXMh1uiGtiv7k1EYvrrS3L1hdwmWJJiz1D8jWy726YFYPIxQWbEIVPVAgrfRR0XNlLrTQwq17cuCGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.11.tgz", + "integrity": "sha512-8XXyFvc6w6kmMmi6VYchZhjd5CDcp+Lv6Cn1YmUme0ypsZ/0Kzd+9ESrWtDrWibKPTgSteDTxp75cvBOY64FQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.11.tgz", + "integrity": "sha512-0qJBYzP8Qk24CZ05RSWDQUjdiQUeIJGfqMMzbtXgCKl/a5xa6thfC0MQkGIr55LCLd6YmMyO640ifYUa53lybQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.11.tgz", + "integrity": "sha512-1sGwpgvx+WZf0GFT6vkkOm6UJ+mlsVnjw+Yv9esK71idWeRAG3bbpkf3AoY8KIqKqmnzJExi0uKxXpakQ5Pcbg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.11.tgz", + "integrity": "sha512-D/1F/2lTe+XAl3ohkYj51NjniVly8sIqkA/n1aOND3ZMO418nl2JNU95iVa1/RtpzaKcWEsNTtHRogykrUflJg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.11.tgz", + "integrity": "sha512-7vFWHLCCNFLEQlmwKQfVy066ohLLArZl+AV/AdmrD1/pD1FlmqM+FKbtnONnIwbHtgetFUCV/SRi1q4D49aTlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.11.tgz", + "integrity": "sha512-tYkGIx8hjWPh4zcn17jLEHU8YMmdP2obRTGkdaB3BguGHh31VCS3ywqC4QjTODjmhhNyZYkj/1Dz/+0kKvg9YA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.11.tgz", + "integrity": "sha512-6F328QIUev29vcZeRX6v6oqKxfUoGwIIAhWGD8wSysnBYFY0nivp25jdWmAb1GildbCCaQvOKEhCok7YfWkj4Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.11.tgz", + "integrity": "sha512-NqhWmiGJGdzbZbeucPZIG9Iav4lyYLCarEnxAceguMx9qlpeEF7ENqYKOwB8Zqk7/CeuYMEcLYMaW2li6HyDzQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.11.tgz", + "integrity": "sha512-J2RPIFKMdTrLtBdfR1cUMKl8Gcy05nlQ+bEs/6al7EdWLk9cs3tnDREHZ7mV9uGbeghpjo4i8neNZNx3PYUY9w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.11.tgz", + "integrity": "sha512-bDpGRerHvvHdhun7MmFUNDpMiYcJSqWckwAVVRTJf8F+RyqYJOp/mx04PDc7DhpNPeWdnTMu91oZRMV+gGaVcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.11.tgz", + "integrity": "sha512-G9U7bVmylzRLma3cK39RBm3guoD1HOvY4o0NS4JNm37AD0lS7/xyMt7kn0JejYyc0Im8J+rH69/dXGM9DAJcSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.11.tgz", + "integrity": "sha512-7qL20SBKomekSunm7M9Fe5L93bFbn+FbHiGJbfTlp0RKhPVoJDP73vOxf1QrmJHyDPECsGWPFnKa/f8fO2FsHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.11.tgz", + "integrity": "sha512-jisvIva8MidjI+B1lFRZZMfCPaCISePgTyR60wNT1MeQvIh5Ksa0G3gvI+Iqyj3jqYbvOHByenpa5eDGcSdoSg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.10" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.11.tgz", + "integrity": "sha512-G+H5nQZ8sRZ8ebMY6mRGBBvTEzMYEcgVauLsNHpvTUavZoCCRVP1zWkCZgOju2dW3O22+8seTHniTdl1/uLz3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.11.tgz", + "integrity": "sha512-Hfy46DBfFzyv0wgR0MMOwFFib2W2+Btc8oE5h4XlPhpelnSyA6nFxkVIyTgIXYGTdFaLoZFNn62fmqx3rjEg3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.11.tgz", + "integrity": "sha512-7L8NdsQlCJ8T106Gbz/AjzM4QKWVsoQbKpB9bMBGcIZswUuAnJMHpvbqGW3RBqLHCIwX4XZ5fxSBHEFcK2h9wA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001721", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", + "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "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/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "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==", + "devOptional": true, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", + "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", + "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", + "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.28.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.3.3.tgz", + "integrity": "sha512-QJLv/Ouk2vZnxL4b67njJwTLjTf7uZRltI0LL4GERYR4qMF5z08+gxkfODAeaK7TiC6o+cER91bDaEnwrTWV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "15.3.3", + "@rushstack/eslint-patch": "^1.10.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-sanitize": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", + "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "unist-util-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/i18next": { + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz", + "integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.1.0.tgz", + "integrity": "sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-fs-backend": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.6.0.tgz", + "integrity": "sha512-3ZlhNoF9yxnM8pa8bWp5120/Ob6t4lVl1l/tbLmkml/ei3ud8IWySCHt2lrY5xWRlSU5D9IV2sm5bEbGuTqwTw==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT", + "optional": true + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lucide-react": { + "version": "0.513.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.513.0.tgz", + "integrity": "sha512-CJZKq2g8Y8yN4Aq002GahSXbG2JpFv9kXwyiOAMvUBv7pxeOFHUWKB0mO7MiY4ZVFCV4aNjv2BJFq/z3DgKPQg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", + "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "15.3.3", + "resolved": "https://registry.npmjs.org/next/-/next-15.3.3.tgz", + "integrity": "sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw==", + "license": "MIT", + "dependencies": { + "@next/env": "15.3.3", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.15", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.3.3", + "@next/swc-darwin-x64": "15.3.3", + "@next/swc-linux-arm64-gnu": "15.3.3", + "@next/swc-linux-arm64-musl": "15.3.3", + "@next/swc-linux-x64-gnu": "15.3.3", + "@next/swc-linux-x64-musl": "15.3.3", + "@next/swc-win32-arm64-msvc": "15.3.3", + "@next/swc-win32-x64-msvc": "15.3.3", + "sharp": "^0.34.1" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-i18next": { + "version": "15.4.2", + "resolved": "https://registry.npmjs.org/next-i18next/-/next-i18next-15.4.2.tgz", + "integrity": "sha512-zgRxWf7kdXtM686ecGIBQL+Bq0+DqAhRlasRZ3vVF0TmrNTWkVhs52n//oU3Fj5O7r/xOKkECDUwfOuXVwTK/g==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://locize.com" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@types/hoist-non-react-statics": "^3.3.6", + "core-js": "^3", + "hoist-non-react-statics": "^3.3.2", + "i18next-fs-backend": "^2.6.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "i18next": ">= 23.7.13", + "next": ">= 12.0.0", + "react": ">= 17.0.2", + "react-i18next": ">= 13.5.0" + } + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-i18next": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz", + "integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-syntax-highlighter": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", + "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/react-syntax-highlighter/node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/react-syntax-highlighter/node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/refractor/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/refractor/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/refractor/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/refractor/node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rehype-highlight": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz", + "integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-text": "^4.0.0", + "lowlight": "^3.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-sanitize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz", + "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-sanitize": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shadcn-ui": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/shadcn-ui/-/shadcn-ui-0.9.5.tgz", + "integrity": "sha512-dsBQWpdLLYCdSdmvOmu53nJhhWnQD1OiblhuhkI4rPYxPKTyfbmZ2NTJHWMu1fXN9PTfN6IVK5vvh+BrjHJx2g==", + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1" + }, + "bin": { + "shadcn-ui": "dist/index.js" + } + }, + "node_modules/shadcn-ui/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/sharp": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.2.tgz", + "integrity": "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.4", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.2", + "@img/sharp-darwin-x64": "0.34.2", + "@img/sharp-libvips-darwin-arm64": "1.1.0", + "@img/sharp-libvips-darwin-x64": "1.1.0", + "@img/sharp-libvips-linux-arm": "1.1.0", + "@img/sharp-libvips-linux-arm64": "1.1.0", + "@img/sharp-libvips-linux-ppc64": "1.1.0", + "@img/sharp-libvips-linux-s390x": "1.1.0", + "@img/sharp-libvips-linux-x64": "1.1.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", + "@img/sharp-libvips-linuxmusl-x64": "1.1.0", + "@img/sharp-linux-arm": "0.34.2", + "@img/sharp-linux-arm64": "0.34.2", + "@img/sharp-linux-s390x": "0.34.2", + "@img/sharp-linux-x64": "0.34.2", + "@img/sharp-linuxmusl-arm64": "0.34.2", + "@img/sharp-linuxmusl-x64": "0.34.2", + "@img/sharp-wasm32": "0.34.2", + "@img/sharp-win32-arm64": "0.34.2", + "@img/sharp-win32-ia32": "0.34.2", + "@img/sharp-win32-x64": "0.34.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sonner": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.5.tgz", + "integrity": "sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz", + "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.8.tgz", + "integrity": "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==", + "license": "MIT" + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.4.tgz", + "integrity": "sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unrs-resolver": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.11.tgz", + "integrity": "sha512-OhuAzBImFPjKNgZ2JwHMfGFUA6NSbRegd1+BPjC1Y0E6X9Y/vJ4zKeGmIMqmlYboj6cMNEwKI+xQisrg4J0HaQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.2.2" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-darwin-arm64": "1.7.11", + "@unrs/resolver-binding-darwin-x64": "1.7.11", + "@unrs/resolver-binding-freebsd-x64": "1.7.11", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.11", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.11", + "@unrs/resolver-binding-linux-arm64-gnu": "1.7.11", + "@unrs/resolver-binding-linux-arm64-musl": "1.7.11", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.11", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.11", + "@unrs/resolver-binding-linux-riscv64-musl": "1.7.11", + "@unrs/resolver-binding-linux-s390x-gnu": "1.7.11", + "@unrs/resolver-binding-linux-x64-gnu": "1.7.11", + "@unrs/resolver-binding-linux-x64-musl": "1.7.11", + "@unrs/resolver-binding-wasm32-wasi": "1.7.11", + "@unrs/resolver-binding-win32-arm64-msvc": "1.7.11", + "@unrs/resolver-binding-win32-ia32-msvc": "1.7.11", + "@unrs/resolver-binding-win32-x64-msvc": "1.7.11" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..e675786 --- /dev/null +++ b/website/package.json @@ -0,0 +1,58 @@ +{ + "name": "website", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@radix-ui/react-accordion": "^1.2.11", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-toast": "^1.2.14", + "@reduxjs/toolkit": "^2.8.2", + "@tailwindcss/typography": "^0.5.16", + "@types/react-syntax-highlighter": "^15.5.13", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "github-slugger": "^2.0.0", + "i18next": "^25.2.1", + "i18next-browser-languagedetector": "^8.1.0", + "lucide-react": "^0.513.0", + "next": "15.3.3", + "next-i18next": "^15.4.2", + "next-themes": "^0.4.6", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-i18next": "^15.5.2", + "react-markdown": "^10.1.0", + "react-redux": "^9.2.0", + "react-syntax-highlighter": "^15.6.1", + "rehype-highlight": "^7.0.2", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1", + "shadcn-ui": "^0.9.5", + "sonner": "^2.0.5", + "tailwind-merge": "^3.3.0", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.3.3", + "tailwindcss": "^4", + "tw-animate-css": "^1.3.4", + "typescript": "^5" + } +} diff --git a/website/pages/sitemap.xml.js b/website/pages/sitemap.xml.js new file mode 100644 index 0000000..dbe75b6 --- /dev/null +++ b/website/pages/sitemap.xml.js @@ -0,0 +1,149 @@ +import { getContentAsBlogPosts } from '@/lib/content-mapper'; + +const WEBSITE_URL = 'https://technotes.example.com'; +const LOCALES = ['en', 'vi']; + +function generateSiteMap(posts) { + return ` + + + ${LOCALES.map(locale => ` + + ${WEBSITE_URL}/${locale} + ${new Date().toISOString().split('T')[0]} + daily + 1.0 + ${LOCALES.map(altLocale => ` + + `).join('')} + + `).join('')} + + + ${LOCALES.map(locale => ` + + ${WEBSITE_URL}/${locale}/about + ${new Date().toISOString().split('T')[0]} + monthly + 0.8 + ${LOCALES.map(altLocale => ` + + `).join('')} + + `).join('')} + + + ${LOCALES.map(locale => ` + + ${WEBSITE_URL}/${locale}/privacy + ${new Date().toISOString().split('T')[0]} + monthly + 0.5 + ${LOCALES.map(altLocale => ` + + `).join('')} + + `).join('')} + + + ${LOCALES.map(locale => ` + + ${WEBSITE_URL}/${locale}/terms + ${new Date().toISOString().split('T')[0]} + monthly + 0.5 + ${LOCALES.map(altLocale => ` + + `).join('')} + + `).join('')} + + + ${LOCALES.map(locale => ` + + ${WEBSITE_URL}/${locale}/blog + ${new Date().toISOString().split('T')[0]} + daily + 0.9 + ${LOCALES.map(altLocale => ` + + `).join('')} + + `).join('')} + + + ${posts.map(post => { + // Find translations for this post + const translations = posts.filter(p => + p.slug !== post.slug && + p.relativePath === post.relativePath + ); + + return ` + + ${WEBSITE_URL}/${post.language}/blog/${post.slug} + ${post.date || new Date().toISOString().split('T')[0]} + weekly + 0.7 + ${ + // Add links to translations if they exist + translations.map(translation => ` + + `).join('') + } + + `; + }).join('')} + + `; +} + +export default function SiteMap() { + // getServerSideProps will do the heavy lifting +} + +export async function getServerSideProps({ res }) { + // Fetch all blog posts from all locales + const allPosts = []; + + for (const locale of LOCALES) { + const posts = await getContentAsBlogPosts(locale); + allPosts.push(...posts); + } + + // Generate the XML sitemap with the posts data + const sitemap = generateSiteMap(allPosts); + + res.setHeader('Content-Type', 'text/xml'); + res.write(sitemap); + res.end(); + + return { + props: {}, + }; +} diff --git a/website/postcss.config.mjs b/website/postcss.config.mjs new file mode 100644 index 0000000..c7bcb4b --- /dev/null +++ b/website/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/website/public/file.svg b/website/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/website/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/globe.svg b/website/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/website/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/next.svg b/website/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/website/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/og-image.jpg b/website/public/og-image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1d053cd0b5e891697e84ebf09cca22d81e85e49f GIT binary patch literal 787122 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�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

c&ZXhthRWfW)QFNNHN7*o*KyU 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

T4R&8${kZ=fD6l7=WnUpc>LN3^!e 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ξvPjcvgYPb9F_i^`X9+icC{q?1U<9aEfU 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�QFR4*moyi?;%SYwuk-u@Y z-lW1sqsQ@JHF_O<`EvWicW&`|ksN@L9f6#HyJHxDVoVrNyaRkqp{VY2fwoWnH%2E;8g0EAJTUjw6<=0)oo z5QJQ-A_yw#UMguC%scZkAC|TaT~Ifc7JsPdmcP*~F_9Wc zZ>@W+4jOxAD!$8tApXbwrD42TbMLav)>0MDQ`HkWOSlWgv+78`uxsn~Jc+GN60|!= z&m69q2+jAX-(9tZxomGf3{)gL@+z*5FZuP`au!!pPMF;t*^Z8~cq&U)B(AoR-HJIO zJRR1A4=BCr_sj?hcF^OlbiwOH)>JlHXU0UA6gwKCX+L;;A2p+}q@q^eC?SxU$w1BtJI$n`$g7vu|IiL0K^M3oTX6Mkj7_Qa@})U0D^8a2Gm0GGxu{Ho2-$ zHk_F#loKc9`b#mEQoGlw+h)RPUi-wGuUp1pv)Nf|%K6euV%6%Eq)0`>H?@l-)*hbz zx_u@XC~s4~{W%InSRfzgsPSx`4(OMHC#0-=Nw za7pem%5WiMvR`k1`sGAfEf!l_%HlJ@zjEaF)zzkJeY=X~Xt_*d{eHkpRy_NZYUQjn zzCs=<%@m#QT1Klo-DIi5rGB}hs%3tudKJlvFJ4(e4>jV%`1EPmMb1cC#HJG@qeQl+ zvEI6yRpm{&*NMvNTfxwUL1U#kV%uM+Y8!2=#@j{Hd%5`X<@Ou)P2s%*OfeJ{VGJ`D zXt5D2UhiUqhg~Re>;xD9bN@qLW#I0&hj=dyVN6^ALK)p}7_VK};bNwP9V=Aa9Or;B zVPBQgyhwH8!mI($4Dj>Ezn&hnRaY)?`h376S=#8yq#}#cqjf8aec=qv+UHqnm+R%X znYB*Q_om&=w2G#6Qd?Ke65@MdpF8sfU2?cu*YQy6xGyzFu~g-|6#>FZmvw!k zmRk2_mYj3LMZ39HI@0CZl-5T1zRY=sm3F)QAgZhSK+^-->v)ES);7c z=j`dFkPYn<)GTMcUjNcT1H*0#VK^%r10fN*mg4d8Nfb&A!9LT{bgR@$-wIoUL$;`$ zq`}$Jjx+w|;u2DjBz6QS#RObNaLzE!u}df%;QeU&bueXwK>-Y4JD^2GkP=J) z2raBE6!Jp3D98es0V#~mIXxic`SzQqpen{MRlgo_ve4Re5i+EW{C!1AW$p3v|M~d0 zZ>Z~jtty2AeTxaDgch}BE}@Xn=Fma!6H=k`gt*-rrJx!BJq;@r4@nfSB@~4_p z6}DBml}eiYcIe1*tXj4BaBy0wv|3V0vxo9b;897_DwnPJL0v|c$Je(dxh^4H5Ky4r z43QyOq2|bPsv`Ml{%tr#f*GVxajeEIU@ ze?RAhU~s8h9P$iEA-v}F8$$MV2SDhq=!9|ijm!tdw9ob?-r|x-3E8m%98F0M_~0uJ zfPJ0b00Y5$35MaX0ssI8206yGFjG?k3@~9X{oC`$-=8*v#paD~sV@eyz5a*LGrUR~ z$i|H0-Y0iGbH>jRuf7yFp2DNp3bm-*=@_f&oP8cnuWn@(R7ONtc;hjBQQ&ciOcEJs~#X^i}f zdPhM~ee_y$9I21&X2ic6b_cy))acjV8e?55^P#LDGpPxh6qE&vbJ`b^A{C3%4g*Z~8ev}mRx;9^!RpfJGK1K?yOB(m78c=q8rzvk4SHEq20n!K&v4~K_#tOHM4PwWK-8e9t}d%8mG0@m8{I1O!T}@-d5FOQ$m`IWVAe!tNN85t;I>NULDD5 zx2oS%5LPhQe4?pjHUM0E`f}ul|6q zAYck^j@|$CIl7cF3_u`f1^kEZ1Jrr3pxse&4TJzCj1s~qaf$oe$KRhGxbEytd087a zRckgzhG<@nP-TetZiLXNIkc}TONp~KD=msFJ6e{u%7Mwll3FyfGHr+OGR)XhjkW8aRtbw_Ag%6++e*l5d2EDB0A?F+MH%U2BJSP*(| ztLA2FXp$GE^^4hjc;j#UI>!lmLkuTTWGA}Ji*|H*&h^@Ud}5C>wTY%%r6Vi{vM#A% z6e&vVZxl~*tdQq6^;QsTk7n<@iBcO)lyGUNzCK4DCrI+l5tR;Ks`iE0&&Im&&lz`? zs@Xt~T)aHyMoZ52mNzp4#nxjx_7}DxTd5!*Gejd~ z47@jHACiyj?gkDpim{4#j zqlCDHOif${;4+|;l7cZ&v>D;AD=Si5`N3uEtEC7ib1`M)f8T!h)U__UO<`RL?ZI(r zaF3*@cgH(UW_IT3=f7^h+3nxuZc$<99u!p?)FljHM`^(sVFZJl1N^VwZvWrsj6WTC z%htAEt2%3a#3?9?JXG4QxN1d4M<__f%fR%ja~-v6)AO~zt)gk`rpGtVgDFkFSkGDgzYKMKuOLpMU7r8PM@Sezjv*(ELg6!V8FdQ+zz*0o{g2HX7{-`h0|2IO5g@##G-oVl zzkU4uX~cPgmMFD~6`NX6?Q)L|T`XVl6VsTMRX?q&@>238H8ag=T$VJ$QDg1wdP`;?)r8pq7X$FmxA6-+tan9H{PvF^NEN9*Hh?Kn<5?Z>8vxHm_NgtX`sZMY7SUU0MerL1zjuk;We#}CY_4O)jl{QKTx+XHM#81q{pl5 za`W{2$LIgbbJwMmV!{gl2LPs+0_0!yc}87=UFzcBZvT9GC`E^!a5wPBo{=cXHey4Q zwB@C`QtO*hPeO@o--N1azfPU~DgGv7K8G=dLNE6mmjbU<3op z39#aFn!E1v?GH~Qj*lEslg%U&x;~fmWXIOj)VqjEtzZ7{?GG3OCAmu|WyOI4nCyUF z=eY|E+~OWg3-#1}c7MD5zn}Z9W#w)e+r4!uF;tO@E|M!b3G7x&^lJaGRPJ)Cgj-v@ z(k}!%GQud%j>FB=nhQ%czSvX+JMHL=YHZ4FZdy%;t-4xjrE}l8FJ}$zQ*Y#`*{Czi z%3(`z`bS4y%?M&YmW9CUOnYeCSl)ZRm#9@~$ZJiDY$-i88>@#eUv7V3Y|nYekW&H_ z025Xeec=7oh!pmw+$HYUEC9nriw`TBS%47Ou|43on5lpeV3ZYuTMn4+H~cwq3mO0e zOrU6Z1R&Qy;XggXumgs#U7R00-~Rp-3~H~!Sd^UUIMbc-VP`JS1nsiyE9ZKm3DRM- ze0wD~j?WKyHJj>JNB1^2Ep3ym+w1yB-duLow92W?Su_ZxN#Ex5%9O7mIqXyh!^~Ni zr>pnVHUExNyz}zI#T%(ymqSrKJ3W?zp8Nudo08_YgQwa)x&62 zmN@Y$J2g_V!QY4L{@eZv1!{)w&c(BGnyE^qtVmASY_twzWb)t2EpN=nD|5Wcq+nH^ zC0X3zg%f=(c&1v3<8Xpz(rO-aLe$gdK^zRjpmlSntXtfqhh!AaQ7{SP`pag>pAOW% zS5qWkRFp*1jFjs&)Eh~@%s5#rZ5nqs-Bs&&JC+oGwQi<~tfI-`DyYUn>vH+7TQQ;} z%LJ`A+ExuQjf}D`yqlJFE;O$mw1=nPKR*Aj10aNw9WcNJ&w&7g{JOyP@2#{Ly8s0e zY{k`*QfVpi#~N2^mrhDURf-Zh-KxzBCv1W=6}z)`+-z!_Y$&U78fr1xs={=e@u!Eu zl@iU(b#3$ckK1SW0OtS{Q$h*(508KloKuR6<&=AXfzaHgvLJxpp zN@)%Z10#jvgu$giiq9>A*^XjD@O4fK2@#%ee|Xw-d$ZEmTcg;ow&J#p*vbYa#<1kr{tPYN_IIAOey5x0^T>|{q47pzklY(M_hC&pw>LAcI>#( z(7Y?Dt%fb+Xq6i-4h1zlwFbSYB_-&wJ*=(EM|RK+;-FMMM3UI>Gd*r}owOZ=6=Qld zm~X5>SM^)mR5&*VN=Xbpy_#*eR~Njfio&OsFQ6n1s%n4&V=b$%khn>Gb#9;WivI3M zlsF}7zg&I!<>L>}?haf`iR-exsh8%MVwdc2?qZBFEkI|4QcQpn0y~O}boLI4H~=O) zdcCiY#0=wt+yJ|R=H>z>Max*xELwbtDWyBI2Y^z%!vq7k7-$Zd5}?E--w=$SZ-0M! zSeu)NxuU98981UN84AOwBnn~>mDDRQG*J?*hn?<(^WU}vcWfmW63r;ny3ha^W&bgeH@lDLB3p?&EWL=Gi|f6&+aB%eWZ7~XtvlI zwP@K}MYHJWlUHuW@te{pFgQV51>B?2m&9s(#hE?yauJ}8J-Dper=N_dV{R^$d1TbL zT$Z>4O7L{0X>6lPed`|&Jd}Ow_0q;rG0p`)>s`kB<(aXz zvXjkfX1u?<^Qx%spVfk-Cv4{p?~T0FMrs;G>z}NJK8n@Zq$ilVem;m(OP3?>)QFq2 z!{t(td#5Nfzu!EF(P3Pbgryv)!KR$7Hk(Vy>*(2|syI>uwZuxt=rjc@T=rx2(%Ymz zKmGOb`G8_zKuA%6xhuvs7m6?-hHDovA$Ugs7dL>dsE^KKRPlzg|Iqj3&TLUlZEikl zhAr=Y&V8$=i`WsGX0+KL3CS1pdJu0OrWMCRy7Y44SB$9hZu9w%+uw2wK;RojfxwPl z7wo-Vo?`;A+fhRD140P}eBe@wb6UL1_EPd!Cmz8-Ttapfav+4#9Z<*_%jw<@d{FEa z0dk-ilLPqQw?8~Rpq{N-GrQ+~((H1`e`+U3njxGxFFM>WKmYh6ae-kPHGnef4SU`MD zuv?g<_L2W$Si%4Y3|Ju^(&E>!zf<@5_V=fU%jsgYY~wV|;fnSJ9+XTJeGvZ69aHs;QQS>H94TT>Fe5I(0GPN86I>W!T?QBz z)iB_)q8C73H+?N^%85U-{GL&swY#P0Ty4aZIZ2V`kXMKnSTVrJHh%7+q zm`ie82#BzQ;&xDQMckdcBbec$pPrE-Bn-RU-QfdFfZcxgbmFZObg1ewFUM!R_s(me zwdx3bpFd1~`PauEUCgM94_pd_xNads6dkwwsrVWbR+tX}7=FI}^K;GYqh|Y^78&RE zWO#q9X6RioUYhSZpYC;=E6=36o5~>DSpDiqfBr#yd}q=9_b@>*Bf-*vsn8l)&iIz}XpQB1PTxJ*d5rUkc zG))O2+w-h|kN=9!Q5wf7PSQuSFhWGv(-@%`A(WjYa6;0|JDO>l^lXobNkRxl7+>Q1 z;NniUKfX(NIs%OwI3A)OQ9`Y1v>(^AgWb2DCM^+O7-idXI*(+jt}Rv+1j= zkrJ);Iv(^!c1yI&b+2$%3cF5D=?t4*$%G!DG%%M2t_Pqyod{uTZ&J)Vk`^*h>^OD7 z%!{TwkhnQiI0aJPlvbg5LQPhC0>5tT!Fq!-z_gv?0ue}#XL72sM+aQ1rHIrS4S@_? z@kfSjRf>6z?KreaJCK1@_xp+D&!UsLa3TdQP2p;}{DKwBP&%b01A4~5D>ds9-?z>0 z{T?fXi;0knWXGrpd8jIHp3Zomck9qKc)$B*Wx~!ekWWhWXeF%cwL{tZa{bHQ1J(&b zIwCRBV?>Zn2#)b3O3zb-ag4B@pV6vHk0wD zD8|{qABy$!lp-(wlBsh*r`yu$=ap!Hj z7fOdRuLWAQ#zd>5X+bL4aC%s@hHRv^oDYqfqn?G0tL`FTELfuAcfVXm zG@SApuiCu&zV6TE27GJHY7HnDuY3sWQFB$Fz^Z&A$N_JRie-4Fy>g)p-zu*^3=X|B zy2Vw3o&bHO!EQK>b3v04MszEzH`*8U&^WM%HuLoAK$SY3vg0blsjHN%=e#M1&K|8= za1IU(w#!HcLoEi9Rj)nOd>$y&5nBpnKp2tIZHm3?lLsscc$I$d`IIqigQ48IVz^K(|$K#9{G^`#QmKeX1uXj2; zJ(opaD6~8&T$w%j=#wFG{cfu!5Au8`fA&__&)1SC&02a=M_})Ie8O!dH>{WcG?xg;I033!58)x zeK_y9B}Od>br3QT4!XH~-jAY?6MWU-Br0!oT!UuSA~>R-`h`k8_wDQQCP4|pgrs_k z;_TZ`GQwJR&wm`G)(K8AxqR`%cNn1`c7J$@pFMl_EdFkH_vWXY-S3_~`{^b6;ky^R z>G^#^Qmp?qMi}q@pntcEbP|8}9l^Ul{II+G1I9#0F+!IZ5t5{Y#Fqre*&Z(|Afin6 zhzUmV%d8D36!+B}b0SpOP;B+)M*=@EHxtTK4Uy+StqeGo^SoYf6mkL2`|iwlp~sn& zw%-z`4b7g48_s4yV*sePPfvVNtV9rUp4#$Hrx(Kja7D`A*pSatumKFKGToyEgXRa$ zFmUM}2Vk+}g@ObmHP66;%$*E3Mc`7Ej{o!(=xLO#9_4GcS_ygB-E7JY`ni{P-MxcA zRvNEdMb2L}1lbA<2(PrR5mH_E%COfF6u>}`-NyNU?))iKiT&b<*)UcHwQ)^%09cSFS3O3NKGD_ZL!%&r# zxdrLDx-V2X)66aS-0PoNIBb_&uvA(_#lCE{Ie8RT!jS=BSvf5n@lA2R)0s{RpW5Xr z8wmAc5OOTHPoHw0m}5trY+Ks^mkpX-uoS!({%o(nR-@e}6Y#;B~B-N1~Cy1mv z!g~7W3P(ta&L4**(o~Nhw#jz8-IDF!)9v=}f8TDm+ceoG2|v% z+wC?@)4!+L%k9f8`8(N?^gEL3Bt1{Ep%Q|~rA~C6AWZZmE2$8I^h-qal#qwzFW2kM z+@-_W*(6{^&{Y=YmUscfAtk>rbi~HRD_68s=pEUDG4Oj(G`s4|8b_mgC@&=59354> z(n_{@00q#Y#o*|u!G_M>sXyUPD&XKM7`JJMW)5B7e|lQ4dGg|DHN478GU@j#jS{Gs|9jsneh)kX;zJOxJPDK86B=aVzLwOzf$Q6c&<$p`#^?EsAfL@D5$su1#RrbZ2L6V)3Y5BRj$i!q(p? z&9XV(L`Q#(V;rO8yW8dM?d|PydAq#5y}Mi9-Y##mpWE#H?33kx-ud63{hu#xZf`%g z6}9RGTHo`_j>JqfF2^^#YLi=?xJ+>U>ypGeMw#_4#RMaq5Q5V`k4We;B{;?@A&=F8 zG`;)!RnUfuq6q-SMompHhuTV-9+nKJuO2bnT!xK(sy;esG@Cq}aZsf`Bb^fJttIPc~Klm zRKFSkS>wC`Ec%*4LD95%1_Zv;5!A}4(h$Zujxr;PujSn*i-laT@Zd<0E;bxp)_|du zjMsEf7KfvpJT#<`-V1Xqtn+NWpi;Gn;-O22yr+8eV#FG%KjVt^sCQ!3%kEp2lK7Jf z%kL}llsXgl2gb=8cmZ-#r$e`4wbGavgJS8ZtSI$nUUd3JIn1@I4je4{F4YUgfj4X! zPLNyk@RW}1MF84BCBKc!3q_A(BE@9_w{s|mZ*#_;vjF0bCCHp*)1Z44Y8CFVO9NMx z8q65N*P+jA98{~`g0(>bx^GvO<_kG%!JW~geu-zCD}!yQZ52`q$b;$Y_N>Hk7klNZ zulc;xq=uF$LaI|$y=V6e#}3ji(U zBF&6AA=K0oV^e{~a@LyRiju;9v-s$)5CUqf4{xo-v9Z0e}8}TB7Jdx|KjEC&1Wqdiv=ZbiLA;k z8kApyB8+B`i?~49T>tkSiP1Spu%2e6$QY;Ok|ZgKGc!3(V}h_waGa7Dp=arK`G0?H z!a=oci5)ni_!4NBW@m6f`_@XN>-@CAD(WZImb8v~mXkn(?bRu3u0fWC{k@B#0V@1H z?T20E?G)sLx|0uVhn_4#k8MW0=vRtbb7n_9%{;R~0Wc?=Qsp@)7>-&4)2YWnj-Q2d zmp7=U$@5R1u0pxeKk+N8Z(oEyjecn-rn5Z z+}+*X-QC{Z-QM2b-rn8Z+`PQMzrV@eyuG=*xw*T!x%toUo12^ao144)o7>x)+nbc6 zX%Zup5^{Noke<~V2+=PIzP!vbXBZKJQA%EBqribyRpjHTCCj-|n1fSlMl}MOp4h=@ zpvZoKFUe)YH&?>NUQklJ)|d*|d61hP`yzG1%;ZT63!dWI3o+QgU;Js~7Pm=J^#gm$r> zKB~d`C&A2`MfGxH#&n~Z3v2I{5E_sbiYCQROU=Gfw#>m|?R~mBgq9R&5X!X3HM;JE zgIzAtRL-EH&%ZCXI!1&fI?)NzG1e1;QcMz(Ad+Goq4=*j!70KR$H`W|{c`>7^Vk2c z$G88NH@-bSef#?D+qZAuzWy!z+U#o$Mlv91HfKCtVt_>Xs9uNu6R*_TUCs3ZmlG11?B3z^6lI5W{cAl zCFqgzOky3QuKg+O;W5Qq$dx{U$6gvK7P+I#P~5Kh)6;*+1q6{IDvGMnE_Bt zlJw=n@|Wwir_O;IQhd(p`*ObSA9*O#eClx}#_Ixpqi6=j2LV4DzN;M`Hss*q&5XLz{LRA2g9CAVnk$4M_=sN1Z~S1y8~cgVW85b0w;9B``WEbuRyetWoJ6Gyjo8Y#+x^^kstNx+*y>{XWqfw_3z8=cPQ&X zpjgNHrH(Ikq$Bia@)X6oP99tPWc%XwU)Su%*B?H9{PywV$3LGxe*E|x;MgQ}^ zmMPl3M2O%7Wm5;3Bq>Ug%o2>Tj!2p{97m0G@bxt_a+U6->pP z=hFjP6!D|Y z<;^{MMK- zL(`s!)jn@9q9JQJoAy*srK`aaRTF_ohgE*{{_8)M+br1~U*aUmup>BI4seVKLh=8Y z<3KMSmcLwo0TlOkFg4mDXSk5wqx{7uD1&?`mrU-H0kvL5ST{?|8?P{11z>(bnKs=r zOVufapziZcA=juWfc8Va773Ha$r>@P!$S>0>1+qBi>r^?J9tpxuK|% z(P|VZo_2*^$At&dJ9a^fHfXS`5^J=o(B@8a>t2_k&T(_PN=+q0eSV z06ojpU>y;hk~GFK&Y-P0K_trw;y6iDJzD@4ekAN2a%^b5qM|^Ro5y81`s8UWRd!1C zs8s7Q`uU7NbdOeTU>uA1Sovde% zAD*mN#dWmK9$(h$^?G&E%oXSBXuV!Xkv|Vaqeyw|si2h|&KKWnG|kpHQ(4WQ@65mc zU(19bf^m#6(Xp;$Oj1PBSWk6?aONsOBpWm&gkb%ro4e)p$GY*{%wIvrFd4T$^WOT! zsL1eK?&y2C&&~wvEUL;tZ0?srwc$mpa07wRY{095?1saG!(qdtzjtAIG%iBVTjb!` zDi`b(D&eL$s`;{CdC!}*z|TbCnxih)yYZa z>87}IGOv#-n^iQQ!=2pdWWyJq1l(KzU10|Vy;7w)d#dyna0WmBw!D922W7?y{hVYO zO+w;xj5FM9CRQRi`R-x)`}Ml4E}Q~YSH+rV6l)Z03f_W+lD~S}Jw0g&W2xxwNjs85 zD?y*JBFAlp&NLr}&C)_Lea-620xNhEY5;goJ1&4Dg<1g04ri3nK5!LIQF9HaU!lCR z75UPqJ)jxYFc|Q&Gvoc33RWeJ0*>rObSG5IBB0)=@JWw46Wg0Y;PV>gMJ{}&RGvcM zYMav{S5 zbt6<2C=H=e-t&$iJl4iO%ZH z>G|O*H-xn}2W>j9jKB9-v90)OM|)Q+d33HM3whVJ!tsQySL$*g3$I@L*4PVtUXsoP z#&xa9V9^h{U#|bS+hT-w(Ida@(ccnZUMA@y4K&8&T*rv~2ME$TISU2g%}h?ECj!s= z5OA=_}#}EhQ&;tajx}1NzuA+p^p)4IFJ&fud~~6*%N6mF8Dxi|2>0 zc)Mi!0bFdpT`zA_lBU@}dwdzkF(UXfjT4fh%NS!qVuUUUxg?j#InfD8U*0VDzBcy& zq}A{}jB3GCqsrHmp3!HG9MfmNx0_>2P%6E^HPvW#GFG9=)g0K{DdhO8sRL=XVVl0% zX5a`=;R1@S!}1)8`(kZF*VQ@`Tm*d?Kvija&Z0~M!5T?*-xexQeQqHRJJrhg*cFw~ zW>=${+_*Rrlu6!Jg!Q+t%i9d`j+1QMC)F?YjQO3SU4#kF0swJLFd=BWe7LseA!w)| zq{aTgWqIeVIcp!~HpLJuz!Wr#aKTn6O|~_T^WSPVEKpY;j%I$bW(P*I^Z6fl+pNZy zr83E7LgMuCxRj=827$+qT^s$ycKOTo+AYCO!LRV4!Za2_uAx!mp5b`XQ;XFM3WiM> z`a+F9s)_j?Z$rb77MxKMdZQqiL{Xzs5=$Jzg~~|j3F=@14Ov_D-tr}m>g8V9V_Kk& z#mSqZAE-i4J@jULpPr7-LJ^3@QP*W6uU&Ny_8WPh;d6Y+^u+JOLpVEta;{=A1%?{d zL@E+Is2H?e=ng!?04fg*TI8a3-XC#-Cb&T0JnpIUiEGxOLAAZ&s+kKRCm327v;oy) zTz=K4gR1bHqg#RvtR2NW!Bv!B|0Iot`l8P9`1g-UMo*? zv&e)@kyEwq{*=qL94ISuqh1n1POWU1H_hH^JUOJF+BM1D>st`2a5xeyVdMd-tN6zy z>a9Kh{Lkg~CB|v2<7{)CqKvnX5Fxui#X83LS&T@0j!;U-_Sy1ZpV$3h_+I8kCE)4j zAjnG^K@j{Tth6DI-fRLn!|EzAkJ$uhXe81c?yFb49^5x5yFJIQ{=ww!h-k|F2?;m#Ud}+QphH9Z+*}&G= zAD_%iRZ*R9cNy$0dMcG#m6Fm$1;d6pX zf@7SLIL<(!KWuLwu5(ZL!hTyg+LvuU-{1dG6$+m|?9*cRdu2Wq$Mg1wJyBqapvv#> z6(rSg)mN{0P6n<xlA)IUg1PHS{TiG8Lsdp1-=XVSXanLt7lS4*e*99Kw;jRs6O9mccJ9l>}cqX!g@vMO4eDP zqF_jOI&jc_0(o&t$x1l>G<~{`g5z$dP$(-@yFDCIHDw5Z${9zUy2icG-BJUs*ktZ-S~3@CU;;%v5`q<3Lao zTj8c*)}(ro0W!#Urt@lnnn4dhdD`hV<(@b-Tb_CH`Jc;eN)Sp3N>EHlLdbcnKVoT6 zigB8pUt*LZotzUA6MXxx&ug~;@&YU5G(~h4;7HVRwT<8yp=FQeO5Qp2c*SQ`UeF3G zEe^bqr;4!B@Lg@r3ManP3B!E~nhNEP%|Z}xN{epTR8E~5wDg>>0(Wp+>yH=F_04UX zViXe{J#rFoc4W>3N`IC}vI+T^5Q4sYvHbPA)hn>3#M}EuvDiBu(Ml+A{1Ea0R+=oF zjB?yXKQvf@-xKYSEga?Bdw&!2oeqpDOySkxMT3U6NHr|R`+gl>UteE;{`~dpxAJ=Z z<o5#N$E|h_#PYt+ z1Flr93~lIi7N*eN^SE4o3UW1LdJ?fctpY>-%vyZ@$K7`K@xRg|cATK|EO(sAryt4W zgv2RH@Wbuz*O8-^Y4wxC74k({%`4NgM0>7V1C4f@S6!|zz#&wjQ0lk#`O*G`!f}?r zVX4vcKHxxkpQkjgW*L%1QNvQMeK=KC{3mv-<_F@TE36n=5RS()7tT!I`A`;Hbg5#R z#ahWIi{`kpC```IIO`;AR2`OkbI@rR-#Z~&P+DSP>NZ-1BkIUeEyWvna;ey=i}|1z znhFKpmEO7(4_I3o1VAiUTmghK?a)UaSJ5ok?Y?^_o7Ds8`(7_pa>w0p+@*q+(z7@r zH#h=8?QlRF)i;p;#Gln^j#Z%urUpI!Sx|(~hpx2Gg+k6N_AlDLcFKBuFoJMcfNmqt zoSZ;O_QSAfS+W>Z1bfI|I06vqMXo=g+bl=9ZQl+UL`8}EG)wB^DQI0Ke zojy2T2(=L_nL;b{`+S?ChDF!#RXtX9hxlO0>)eXyPnOQT@eYJoryN4(Y5PAAgfpy4?D8(X7X!wd)7tPN)cI`0Mo zTLo}@Vr_Od*S{^Z!c~kjpWY)Mfy6}LB{7LnrY|6ugd`-5AoPoD z>$x9BCoBZtzTVz!U#970LI^@SzD)H$lm7^#7^eg!ncX)gDT>qW^8T7Xt_ab^Re#uP z*KN2eiVJ86tPrhgEeYBRBb%Th@WXu0Tt^ffL9suJEW13at}np4^ZB>iEg=Yz^GiJ@ zDMsWG3XWoebR1_g2t6f)T#_Wk54XQxuMhmn^yrh8^V`9uX#}r6Q3IDPij2)$Rom?O zGr{N_wk^YGbF8y>#dTZIcBI&}U`d@Aj1qU#4wNrFn^mT*+R6Pjs=q znEOLp^+FzckmKbtC{Qh6&|p#kfaU`+--Z{)2?wt(1})2TRT#>RvnPrya1aDs-7=0& zX|5)W=767>)O#o`Mrx?oazU;rrvTK4Zpnc&Hx%+?l@7-()#K*ls>d1>*T{{a(zmsr z1H-KZb#7ekje4M1DzmLG*MHn?QJSWNBpjc8Vs=g(lOl@Y9$;|GL|zn7p{mZuALB(0fUD z{kaGVTF`Pl#XNR6O@r+URp<##(?1@U4+hq_)j!Soe!oh=?*3^s6FOJTpcIvIEwQ_x z!;rHVi+p#{w}SbH>*ek8_V&fYumAOr|Mkyb{`l9w|NZ6L|5yq>tiOEuvW}u?y*3qZ ze?s$x!?Mi=GmFX(1uhJVrKq_BrJeQ1uXi`wm)UXUF_ia6@5$1Q1QAT)IE~{}PxT+t z?d|=wKaXZb7m5cSrxb-I?X6_#6hhbDIlNNnMZOiPZ^uK^Gk`T0N^*7BH}Z8#tA#6Z z{N?@U-3cm+2#`1!sJB@;Lm1ah!odQ?gATmcL)GFW%M`PCW-$2{J~$2d1Sy zKP)+Zg=0mA3y&L$$cvwj24XE78gK$VjxKnT_+-MlQ}!&R{nm6*5JvB!THdFOS@)Bx zD(b8U z4~Id>=h^awE$564GpSia364gV5Vpn869FGiU|!=lyevYdu{qQFpNyt8kxQcs@9O>d z`Gm6s)rRzk17ls1>m9%t1`B$YG6tJ{P&)>lPlfUxA4&!9P?m;^ReuzTT`K^Trvdkk zZ}^tLD$s@9PZZOT+MvTSAv0Dcv`85ha4I{GU4UmeCFDGVYal{!f**^xI?3pK1fQoV zieEk~f4h$0K+JEnZayr*DjZJt*xplBrArYDWM2z};TbbMsxp>9wOIg%u-@MpF?5+L zHUQ-c#aU`v{gDZwRVvCsurdAT-JCFJ9(CUEJXZokA*yxX+IC*3^cDHo zkLH{>KhYex8q*6-x&R@?A2aloLAjlsTu!PMzka*hZgHIcfMbH=6zg~wVU(sg`O}}K zXI>nX*-X+n{mYBH`|Iydd9GXlfQqWV&q+{d4liDNrJQL(7+iHI)oz|q9Bd1~J?xt1 z$TobQ3uUwF*|}VyATwQ~Z}Q_Z+T6N{g*%f z{pI36OTp)_AH&u9%a`?+FJIOzZ%B>%Ql6=a;AEVus=T{efBU+;-M-8`X_x7veJzc# zPA*A`ajcU}$R5X+=n~^}yS%?{&RNG6hO%(%fa0+5@xtWYH+{JZiwk#TJdq7(Qc9m% zwDT0KZf4^}9!mA0eO_=Tq9Wjl3{3S2?E>L{Imn;*6@-hGn4;3XM>%c#KuigfbMI z#j3W?^YgRb>sQKtY29Nv%P?R?7*e(Vh!QzvM|cY&6Vl480A?cqg^+pE8$)r?9{QjL z!Q`k7wCK#MPo<}G-fN32$16u*$FJnv$y+lp%#}HM!;I&qps9R57zyx5=9K#B6Gl3M zE#<^gMPRDboLaPV<^4Ro@O{I8J?_HbA{n$CnrRy&t}98rGH*yVxkdw_*R2S9t=WF0 zOeM43s&l?Yc^=3IIS4;-J~*s{j@xmZst0?uiKPLbHyACi&DhcES_(+|Y03rbw6qO@H* z7oMEJQq9U)R(1GR6ih3}R+WiR@hGWFADxvy|F+y>g3x)2V;o}~<2c47{gXVFRhcsh z-HY^+WJdtlVz{16305?(j3=8F-}=<%hb6Zm?*yLkmMU?BBd;cFzQLIapbFw4uXcx? zGo+wN^MXQGz>EX_QGqIjuyGI+Tf%shA5Cj@AiSGJy&c-Ctbz_x)xw?+8jYGT>a!c6 zU77J!b1c2v9J#$3gzpWg$eg@4aTk@A{ON+K@Vw!j<|(i@_Ek0<%TVz-Sj??wUT8Y| zUF)dd`NRpdZ0u=~cF``*1hZP`mY9QS;D65uP#Z^K=V!HHk3rxul#+7|%RgSwAhcxH zh5fpdD=UJ{U)JEmn83PAh$0+gj=QM=$!fLaC9@VIt z3DnbII6paBt->B7iGy)p?9@%Fu*XSmIP#q7Q}2?RS`*6CIHhk#m9(P63HRw|O)yK}zaBAkTRb_!$5)D_%!G)j&+1NMBu!B^wMt@4w#o93>nIcgF?@cu!}t9aXLS!upJt3Jf5Ufx1=M1t zE-(Q*RybDH>>MvpS`l_;(cHCmoFVisYAgS(b5#jh!I{!Si!G>SQ4K#CS}pI-yWe*? zc@wU!)%?@vU+>5+PU1vIX;#Q4gb?&h&-fTPP6)zjj7UuMOGFUXw>P(U|GfV4H>bGj zcuHY06$9`igle_jSh%%Apm=-~vAhbVbP$<>MHvF<508vcwixdOcpA^sdG*Zm<;4+v zKKuM-d4F^BSN+*D922bnfbUZt1`61cf zzPO%Md_SmAtz3)JN}O4$)Q=5zr$4CnPB=K6sGFf!1>%e=`3u?Nfw1!ZUbM+87R(h^ zSD$}dZt-P`QJVZUMwnb?)RY(}NGEBk$LJ-Aae9eKlB8+!!^7?G*I$}zRt!h57g>Iz zoA>xhVR2$UZ$OHphk$)*^o1ccHszo(pbc)$_bXEaP_{aiOARq!fIv`!UO!iDbn2EM zD4n9BoUwVwW4JP$3#W>fpD0$z9n6#;>l=#Wp12|3?3r_@*!vBZ2ctbn5v-YCXdQ); zbQ-!2a2y6uHrMvT630@u%E(mrd7~IQM>)@9r3E~jceTFZRO*6u#6!oBD+^&ohrUF0 zRW694|}B#9!J4W z*VmwY))d-myKev<W%*l-yZqQ8yLEueJ}ksK>(<@GL=CS&H^1+X3i^VEtv5)J) z-?JN*j!8^(LgK80b(vhIi0FjqaS~&qrz9q+zPr7>yB?brEg0q;G3;q8b2wsEM=V7> zTcUh;K+jiiiV|IScy7-C0X;GOBfu^qx$O@D1e?jCPA#f^S~+U0KmWdbc!B?l5yqL0 zT*rD$Vw51AY`5F(-?v+mrY~=@ZNc5$^6u{L{_8i<%S~HBeNOcw3Y;kU#i;o4+uiN< zB_TS|b*w*5E+I?^N-q&Y7!zH`u}&_@IflT=T;H)^5yLk}I z{qadnat!LZ~V_qYJyl4ZYP)Q-7_QvmPQv=}Ag8?xTe)$8p+9tr_17P#{jmVYJI|-<&%ZCX z=QxftR|g_7{@>#16zL>II>8AdM;HH&DfWU;5pzEf+|yJ5uToLBQVU1?DQkXlimT#A~Vj{n@Kuk0hb zUIvWji|)HQbSvD~H%pRUK2|^pK_tE;ah#-t{69Rs-*4L3-nVV+RomELFaHq40TV6a zAIK4u5r!sRP-6KYV}jU$y+~yh-JLBx8d(}>-3Dz#6HVq;4|>))n)Gy^Y(2NMtF6(@ z$-edcbMJxE_a8_sk(jl<-_Pf|9(#&OO8%CS3_Xf*2q7f9UH$p#GkbjTf>G+se8OFY zLz$<&r7Q#-2w~+ltegri6l(KPb1+!m=<9XUaR7VT_D|rNql+;1U{s?nMw8e3)$K#J zAp~DzLK1XMP?jVFVFTe5p=Si+4Z-($G55dkv+O>*zy0v(r?5JiD=Y_H&@9yjPcBvG zU%uSs(R@NMN(o6aoDq^FC`BYm5YD@f2}u%y5`^&m>VE%RW1Z0R*-#rk@78xQH7Nk$ zWI6&<$!rfL!SQ^yY(=JImm9%)#43=21B>SEM$`;WKK*`opJGA~%4OmyBDr7v8+AU( zNQ!Z0z0%KWy{hqj zZz@or+Oo+u}4%b^g@s`K@5taKd}t|hoty7Cit z8`rr)=WM7ln(4KD-F{p5t(FByEV%VBtM?g6LdpuK~7q*J`x8} zh9wJ@6dQUKRTqPL<@M|*S%ARf!;v=jT|lja8-5Ol>-qek;OWi?PyxOD^vCKxK?o)I zI>Cq-#-WiMW=TejG^coH2F}QapakJ`^Vj?D?*6l1k5#2DyS^TN=KbU|MSa z)cL#2o2_EkR~W&Z#9uzGK5RA#!kdH;17~8lLV7%j5dhi3=tzC#K_W=807Gt z+~5AT_qAaOhA%WOws|$QP9v@rE(Jjq+R*aOk43*sJ+-wKZ@u)oLY1sc`nvx-Fw5WBC^3I^wu37QIZ1Ns(>9JD~{4UGOtx>(V1~Vxtu{}r^`+O11HLhHx zhJq#u0@r+Ag0n%8kaaS++vlNRT7InI}u^OsNPVFxshCiAA{N|aQk7-8po5hsYx zm)=y;Wl%V>D(4L_wdyWSwFXdZmIcZa6>6z~-Van?W9^+h@GQ$K@%8ardg)1E;Ev@3MEDd=_Bz$ue??HY7m? zNfAaVF%q0)IQy0*MF=5;jQhXb{bwJmk>s8M+T(dBjf6ncCA~sZVk4-|X0>w|T(;`5 z9Yjw@r|!JCq^lRFs_DGBS@PrK^Rn`MRJ1O2Yxsn(6xCi?wv@Uk|DZ{r0pZO#$559& zy=CbDxLu8ON$w6bDf*%_d7QK}M3GB*i3qH2GcUHpI+GjGXn2(6d+M_P6~;Snw5(H~BVIdU- zy2b0gP!JmWdb`z5rauipTLrWWp(|=gdO?qsV$0{XE~JIm-g+$eDz!!6o-``wl1{t6-)giJ zciH*@NL^m!WBa`6Ff#8qB+6@dUeXi? zU*Uva+^|##c-H||)9KQd=V_u+S9wza^$HX%mzS>Qg7=Xxl|20|AIk0wvWzooOkJ&2 z*LYK+qPh#Ys`QR)zAp8yE?laonnGb((n{^QH(E65$y1l23MQoBK$t7d_-WAt&SU{E zDF5^RKX)0%DJDjWk_06gGEjadPjev`%6%OeW1N!w1<(jphIQT*n)*CqZNA$!BT%;H9VT9J)WLb3vWAtD;`8nHvq3+)0LlphIKchqFA7}`#)9>@U+1vfGH1NcM`X{=F9~gJ{iD z^lOc!oyknGwOP3ee4v5`U5>PulF|)8Z!47JP>znGABTm(+V$6V$PCz{VJG1IxM%;e zzq>s|7;gwMvIM6IN)gI%g7OdJ8l~9C5<=1pCmF#cF~0i?xqH}0qGd1JEoysFVIiyX zPq&lpmoIl8?*D=wgUh+!0AaGpP(~7hahhitahi~fWX7xeyAS*Lk})F&@>01jw}iM? zqP(anQ(V+8NTs$S(r-Id2x-j$J;Ci4BdWk%$$*7p`n_2G`0MX?56S;82O}gy7~w1< zB+D>)Y$TH;O|mrGWJ!h|?!NA~Z1YA`oyv{G3xUqqaN2D8f_VV?JnzlcNA}F(>DLrI zt(cG&yF~$5s@*EpAb5EypH4Z;dn;(QZ5^Jelfg^Xr2=&wJA&}UpEhi>F^^5Z9M2t< z<^!=)(tthY#w<$(p%a;J*@C@(BX~bqZrEglc?3+Li4cet9W>Un8e1$>DY?`Pc|C&a zF)VQWIajiRVW();mKxk`ZQ$=(&Dx+T!_!uIP06wd#WqtY3RLCiacs4!k)pK$C2~hv zANJLteMt%3atvE?!FN6NlH+F|=}t-N^MQDyvyDc*H@DrF9lvhfa966YE|~T?%?W%{ zJzZW^^>c4_T=2%_K-Kj! zuuR47^?j%fFRE9~&-?$}{e}o4$iNvP=ny9aBa9J2*Bg`(oITs57$0JcGLquIJgok? z|12u;_Uawi=!i~Gh!)L{XWao5r)<9`2!)~KfRma}Q5Hi1p--KK>P{G6c8>gCYPPU* zqjG`%X&Dtt+fRS29`JWYf(cGALWz-)B*RJasQbhlVqlyhlqH#Qjs8mhdiS5b!c_d= zMs%9G=F(Csd@u8#!B@*v_A8J77HwLa78f#mli{6WMU7u z0r&T}ANJ9~K@;9kz@F3R6?qtk9(Yspi*&7^wQdG<%Y6sH^J2(oT<^R@@liXZ?VYAA zs3-6F-p5~mzk48At`H!Gk$=8<{5P)zra0G*kPSIZ(q|8=ulwytqa;panWv4RdwRku za_eI;wk%d-V4H7H?(q}xxKdXYK~;PaXac<4xpnIlgz&U^<54^;M#aWtuFUjxvkh53 zkai5+0DJ+!5j}GF1`yz|d=ySEf);pBF@jA6h4z5*`0#CAwTA<)GK)sdGfz}{0zWK5 z$vyy0*w$Y1Js4K03#)2Usxn*_1vYeIw<-ad>WwueycC3S=SK7;Xrma+{k(t{+W>yljpr$0V1Qo{(8F0Xj zgKguq`6OUO2>eUyxmNV$kMZLSATcn>RrPt)hGaM~a_bz*wUQaS&InEqSO3~?YwcjW z=#7uvkLQ9T+1kKcdcL<@$EA9)pi&Dzb}s7>3@;#WF6fI9vy88-rfn}4T>a9qwQ$E( z;mosSPBKT6GGCNSQ7|h|4IQ2_gwj5{wV?Uxt%}BpD*aAc>JC2&GA0 zRYWMg`(wYoFz3AsP%DLvV7>6|Nx_M7FTkthF>9Td7ZQRpY!HkIF)+$6G6_ixg0uV8 zhyB&ghfoem=a4?`Mor5X9avO=qOjIUm8v+-4?D}{>?aggrK*B3I%~3ZAyCJ~#nHP- z{sKS=LP>%NF*0N%#y4SWii{1;LxBk8Y_h*StiJBI2i0<*o{JNmr63nD>?q{bP#%LIc7|T)%wuT;sNNMg0d%pi$7OX`ZFy&w)H;6wYdj}SR6w^X9JLfL zs*qKi6-x?QKOdJNgki5C29c^-1!-7mH6uWao*HqmPBUGmFCI&A3`E%M0x*8^ZaM>8 zzjVByxc4BWpee(?-{k4y``(*j@oA~eF;mq(nX&$C+K$SjBE6gUy58C4PI=ElR_zYN z7+MA2;|jsqEQnsVytdVPAzHH{9JI@GQ{j&Sz%8gIWVPnW<+?HTj!%|=@9_=*%v206 z0I=x458f6fHh@rkQkU$$#vYUbP@yL9+>@iL@_fDv-ZTq^*D|Y(0c^2P1=!KX^95Ct z%j1POahgwkBKM+}58 z`tWe~kNxNM+&n5y^`Ybsg6&m#7TNJc^Z z94)&`siZ1*i!d+!do|3PntD(0L_m7bm{T+ zozFKJS_>Rdp07jeb!gW+CmhG$cs4EaZ8I!aUs(Hp-90=*=sMR8CggEpMi3$?!iXdo zA)ICj*$`|PXhTv1XNR||KlWRvAoRq3t>C;1)&b22ss{s#<{f4PZ_JulVB>XAQ0uJt zBt8o0sWUGfQF6eG%PHuw>Qn9L)92NHeNT*xAhJOTLO3Dh5aHzU8S;d*fe1n=xz5dL zhu>v)e|}omWbdQ}ROm2V1g&8h1d}gcZa@6?SCSei+ZY59eEqnC!;i8wWIV1n2q9U9 z?{7cs`$Kge6zARzZB`&-!QGL4*qjg-Q`@_&a^5~^p zQ|CYDF94F@{HJen?!cow)W{751m~BINN*Q48E~!=ysy@m0%SRt)+mAJJQ{kue89Z~i%aOs?TRMU!=NnjzO$Us@=so? zar#nl1eW^UFzJoJb4`GunoO|3A_p04-*2eHt%IuL^J^Zq}#M)KFgLu}xDM~kj=YEw!`mir?Q^OX@I z#CY|v`se=hJNYIM8qVUXDA>BVq~@GcaH(N0Uau!UrPaKVAn~E?Pz~1%bYXWOG+KPU zqy%bIv`V6=_0;Lf(|G^K>K^}SUQEpKAdi7Df)RT4?A6`B_A$l6 zz+su=kHK70!IZ0qyjv8wvle}F^LE5Rx26Xo>@J`3N~3h5vJvku!}1gF^|ZjVygO>q zur(B5{lEdRQ9nN)I8E%)Hbs?{=2sD0W20KIjzFb##Pmk*9m%5K0*P*NXKv5{ z61CXs!J@^MnXB4KtuA)kZ(kf#_P?#}b3|u?NJb2jY%+rL5)w{umLxbak|ZD1BD^7n z@%?wVt6zUT68x|{axc3LLF!n-RUDNTU%uSkX0P%G$A*D1G7OR=1d%MoY5wRNoaTUD zf}h>r-tG&R`U_qQ6j&8xk76B-8jB;VIHT=e!<}hSF`)G}u;SXJEbJKhb;K$BPogJq zgMMND@z>v1_gR`=XGsnn&p8hXF&;%hNs^E}l7vY{uJOa|*ZtN5!-WY{2synblt%|c zW;WwEh3>{W^)!NhOR$)=08cE2p&0Ok=M_|QKwDGTbEsDHOyMX~P)8ML$8q^=R@b2% zR4qtB-yB!XkiKw7d|h=l9X5SgDa?Z=Q)7$Gs>BbKuqay`T?rQLP^np(=3Wlz!rUry zKYQ?|xm$;YtAJ{c_3q#ZS~rcw>EAE=)b@(I5?UV%D!&{uJxLsy)o!OSdTH`EC-U^T z)i6O+1}y+WhkILm%LNu>gfawcq3R7=Gy{%B0Pa8$zg~Q)O*F_rSekM$30szXgNyeq@iJ>dL+)RpoPL4nzpFDjyag#et@|md*ys z<;hJmlmi`xQKi)!c3Ny9a(G}lOfjP1`*{Dy>OTDrCnUir&9wt0rvvBEos6K&z=sJ& zm|SCmUfun%|2z&%7_Zrqtk@D04281&^yFswu5MGuou%02&FbL!vPieY_Q>=!o~4@< zE!jW)d~R`_b3K@YpvqCyW$#h|fb|CcB`rBlwc9>`O2pG8mt`n(++W14qc2|`KBP&O z;Ebds%?M5j!Q`4G1Z5jUNRkplk__jecY;ZV5lZp>>fie>W!Evi$~ZI+%FC({&K-Hs zIt?5NbWVJ#I;MD!S7qAdmvhdF%be7hQkqE>-_9MY-R^Jqf8Kp~MhHrfVI+A{pX9(M zl461}A%7v{5MykhoT`;!{QcqW>eDZ^;l~-TOmubBt#S;*Nu%ld%a;$|6N4lfCRs*s zLas?3{w0Lq!z4kE8u%RHjnVz>hdm9Miw5MVqIWTcAS@2fr%&dBKCc6D#0^S(g>~oW z$G*v!^j9uGMuHGlwgt=nL$#L68w#&NOEqN z@o@Wfzl9VX%(rpbd#^NvU<|;?YygE=DAc0`bKqKV(ULDmb!AG)vOW^1Q9)3P+O*RI zq5b5vqaVmtc@a>J;GoD^a1@Tq^Xhb}X(cgo#-OQDYu&2HM~j768#X6z9j48X+m;BQ z-(2wB*^}pH1eJ)HDcxvHDK3c07aTV#{BYupo1WExlp4%cf2W@x`O&gdhrTJ))?;`A zV`b_W`Q}hiV>k%FPo)|(ht;CzYVSShNR{DK2n3y{1j?ez@C4A(+M_>eOr!IW?$ciD zqRE&6lr?_sRT$xB5bBi)H>}5nK%4S!#pgSLZH*Wia0)+yZ(SSsS2jCs@JdmB=ber% zN>+j^r>Dt|>TI{`kQ)ckvAwqY+-ijmWfl76sIFNT$~%@htt@EDkxHB?2F+&ug##XE z02n>$TM>0^ zl3|1pN(oL0$%uhTf>C-%2uWYv{;}Uq0?*_cX6TQ5rc4(CO54$AwSo%tmdD4A%Dic4 z+$@?yX2&<;IG|gKRQ6xP?Z`d-c+n_CkDwd$}%I5fn|u?-+tKpm*SDBgia`XynZvX&YebUK*LBo z<}}&~?W1My=q>BF0MLB)s_(>7FrPa@*o@{PU(2rmC?kk$lE*S7F>scon2>8ia+qR< zGDI?xApCIqb^rOLI9xdIK>>!gDhaS1@o*G~oUVC+qQRm*nJZPU(gl2@V~IoFwiU`6 z+WtW6L&%kjt?AOdY_r_CDAHrj)x328H0lR_u=J_6E;!ymhax2?s#XrQl0GU@z6e-{ zD>hGCvUk?i&JU<*U6PA03f8%;eiZSm_+cK`07Gv|M2h z>atvx<{@38dc~eE*8B1~J3d#1kCojh;AdK$t&LzfRK+&Uz4RfkPPUisnzP`%Ts{X8 z&G-CZbnf>gz?r@%dU7BwrFSQMbL0wDNgPrIDD(ub8Z)#s(tSP@qEn>++2Rc~jw6q2 zecu0hmy!P>C^0sK7-^o|ezb1iqck^!=3;J=1}2wgv#W|Mr3oS}5}$NuwJXetXq9nGbyfP(D9eO7*x}bs-xho}Q;L$cC~U`7z%dmEwqw09DpRH5 zo9(9`S0B>vaF%~dZ4gQcNgl`Y2F~yX_BqM3w2+p-j#6UUQJlFK)>J&l`goTZPMA9S6g!1H-8NeIad zlwyJ(?!N9n&!TdFB%V+glQ}nTbcCMG-CS^y()Qjocu(fIap6J|wc=Dh2!xVl$48Y8 zeA<`b1%yL(u&56OXW4$^YUeY4-7`)2z9aTr*PfgYXwcVvP}haXD>Y~SQN-9W!wi{9 z%UVwTQ&3Pvz$rkvq$nQNpEgFI4m`ct5TU6>^0L^E7m#}M)6bSz6v{ufv}xq8B zu2FHkC`fG}if+%l;h_e&%Bj`zILlIkq7;;E*(!LHYbI zuiWLkB7-Ct;RKOaw_o?4hd%~&u3lpc3$<5w*4u`r`=dtpsiZ);$lLX5OBUN2I|H^> z2sE*9K>?~4^CJ~198EcszRTEGVUI5PAqzV+EKYjB6lxrto?3-6<@2s;`n}1bR)@cQ z`k%W8j0qxHf>L~l44mPdo$z=e#|V=o$tM+=VW8`TU?ctQ_QQ6+^>?7`oDT;>PNiUI z9mPjB4;L+w0>F=k(Qv)q9RquzMS=u-^Kg0GrD4>6Enj@z|L5-e+Y~3+GeS~~5!n!w zqZv__p$#GU5TWGokRbF78)Rb;f-w5I2}x3dNk&qV=21_w zIefVNy8nEpmg)0LT4)44FrQO?5EMMmbuKu*7=%=-r}*bTd;YL)Nt1aUSYqS^CzTd_ zQjb{9XKQ0-Fm;OV(?XNx!-J-|)&Us4RrxJ{CR>U!;7w4gDb{6ynwf#w7iobmcW3^n z7=bBM9#@;no1kua(03S-hq^`6J}t%I06(5 zcY_;EZL^iI^*RnJ!PNv%V0PN^d_Ry_E7FduuH^-`G~qfvO$+nZPi=YNHzVzdDTAoy zuq;mr-R1-wYkr{3%vLEFS2c<~78@lXc8VgTBN#2ABWTd1&tXUbHLA*|FJaX4ebAK) ztltzahBL9Z5C(c-yp_*-p5Api(B!&<8@ft$g%?_pZrOU5@@TgHG#=GPUhDa?Ykl7T zd6y=K7$K5n1g9BFQA#i-BqJy@9{*tSv`Ctw`~^@IcjX|KB(`Utzb@_iWA(H`anrv4 zrhcsWKa?1oF_)EA5S`NnZg70ls6k08wwF?grTZqww$Gx2hLBT%|6&kCaE6mKCy^M0 zBq>UAP)L$}(@JM3&0hc)6l>{g&kq?7(p|-d=OeF4fwl-~kp;TFZk^CWI zt$xc$f-u2}L6AW<*9Z}k!F}_)lp#>>hXH}<;&{(%*aTFNJ3DS zWQjot!byryzJw(iK^tVGBuhz_rT4e5_9Bowv}<#9OVY!(Os}b$2crw$rJr*{*c}Hh zjH^{&5xb&Ai$$pqovOf`n5MWsl4`&Hes_;il9In6f(?|CTuWjQl#{^nV?aU(!5Jp_ z)x+wK{b!ki4PIA==T<{@;<;58U>oqfsre!A16H&IF!j}mFGnndMGIC#NY7eAhvQp; z6+RW3EidYx^7Z4F&ie8#TvkWkg(wA@OcmN}&@kIK`l9YX9rmOCpu*Tb@zo_^X9h8;%2CxQu$yMUi`M zL2m{c!HZ_l>(kbJKAxHZWO+dfMzf+Ydl9MB?()Z$2^p_lolhE5b=`w?FVrrj*3-R2dj4jk3ZmyJA4K1XJ!=uU}Hu zm<>i9`rHD%Ofdk2Rk<#3BJh6zRPhMbt&U?ejLsCiWuM}OG9O&oyGtI5pjl$0rWk+u z^ndR%a+qL(@Mc3Evvfw5e)HXD#7HnEB*RIH5J4m(XhSd|tKWXyf9`ksG&K%`qAajH z8<}q}6^ol51&Y}$9x?rr=Qz!UsV&v|!O)D%ISYXUKY#l2VRc9nY~Uop21W@&DM1Fo z7~^XrL7N2O}7zAUCNUm;2WcBsaJZzTB0T?;JEH^r%s@N0Cm+LQIR^NY-EouX0S&SqmqQWvuw}UzlC>YJ%Gip(# zXjnDb-udxIu2DZyM1fPH!jtN#`|rV0mU(JJ_mjvl$vW$8UbjDBZhL`^VaJnQ-@7k@L7rPE(O)V4YlH+|`NS8AO$07nbl?&e9nEKaG2(?@|HS)gC2MhDQU zE6^9mFa$JEYg9oj>Wi>l)BdgvE47_y7GUWGcsgm1B}l!M z{Gr8NSshxLn>qtE$i6=Utj;w&04>_EsGR;RRV!( z2g7wJT2rREj@Nu4(uePN3+XK=RwJ=N!7(of2P5_dHkYHQs7Mj;6^jzICL2UGD41~} z-v4=bpJmx5d$g73v+I=LEHx6cdF0w7!^pq?F*1mO?g=p-R=*YY+wE~BtVZXmstZw* zQbeupL=8=3DcY?%S60`escBI!EerO7=LXK`C?Yg_a8`-CFuqe1Ea)9k`$qw z$&lmpkdfu;CL=R4l5H@?nB1;@{4@_bBPn(`hm^(5;SE3JRZc%w zz1Q{0TF{2j8ikCb`hy`8ef;&GcMmAdaE6SGkSxnmL{OUd5He&COz+eu$%-$b3Ir;E&37;-c$w(7d+lm!WN_^PU!PQ>yvX~McnlZ%wBV5$q z0NV0Q&hCyD0x!swXnS=;ndbJS~?&QJ^Syz_*Jmt1hht=exmC zD7hti5uV+M)e!(1fRb{d2z^DOA*66xg=jJD>~N%xyrbr2SBh z6dVDaLO6jg(5xlsS~$125yWF*@Vg0qZd zd1aJjB*BPmG9zCfV~ldI)avVgJAjdNbQ@|s#oLp)DZPW#V)C@wTUu(MUQV8JPg-w+SPJJ{!TQYfP zIUxFPU`hY->%VXB@4w5kEJ+QFP?ivaGL#v)>7HbW82Qj7A^EOgLohKiL|)z9{_p;m zIONMwFx7|rXnbH>r}G$8!neb~p=bcbKA`8l7n;lJRl$!SKR&RdPoMsMm)##CWMtVR zAo(yMMjoe65h8h?J~1%KaF%7c?bpaQ_p7`8$Ior+!aEN%7%3_}>#bjgc6m|$@@4hm z{#BYGL=Z|b$%v6Nz{uwD@MV8?GG!2_|Xh>Q5V8^xVpe_!2`jAYp(d?cUhW+ch;RNqb;h>#>h zhsoi?>g)cqS(hY9VJ)zeEUsP#LNF@Gye3&Q2s_?TeWA)w>7FkmM<3h_Ec#u$RP>eu zIc&9fdou0#FgSwp>>_B4u4XfV=6z;Y4FFhD+Ua2K(2!ARQ*h;}CjNceq(u?RqjzWJ zsAGvQ#<1j_A1_&j3fOk*d1MQ;#1`y{D+oQR4bBEn`V`=;7T{Eh@lQnROy#1J3P+o; z8ulDtVx)Sn9BPH)3tuQVES_OpUVpJW($1IXZ7wdzbv0_fq*<%cE!NAPtQ3w5p&+;| zJ%Uf^mzHoKaK1F0!hXQFX1>is-&;~v1JX>mWnHqG`|TPkkYp-I!AJdH?6? z-grfD@~Dm?DIp}yt`SK{f=GrElqQ61ka4{s*>?|j|JZLwux~4RwQ)4!dS=bkW&No} zjmG7|?lg)8svd#X%kvNvD7g?sDhoBPe>FeRgov`#v1joV9K}3{KmBocpJ7ZgoR9=( zge1hkkKg|WL0Lu;jIUEn5Fsh~F3IlxXTN%si!h5qfmkI^HQWKnN^#_sV%gV$p&f}=c zOYW>CbF#b(21VD4kM{q%dqprx5}X(*Aq1rvCRy?g37#27Mo^X^L`XvLw;6j%5^{gL z`nvx$rjPxrmbNP_s9gZhw5V^rq3I=TdG(zX!al?EoGs~;U*y-j$~fMC`S$^uFEDQ@mr?y_o>|j_n51cPw z?*4k85%ldxTqoHE5kwM{VVor?$)T(H4JRe!{`Ph+T(Z3$jJRGQ5-tk|`r^W(xIh8r zF{KAK3pguYsAawiZTeVrZ%UVpFtK}2CxL$fg7?4ves_-uNy!Egk{yzSkR<266XOsi zgdma`grqpxWQQ0ZKHPoXe{NEYI1#u|4cR6G2Nh>@B1>}^z~@XT1e^!Co=FMEMM_%G z=fwfkmlf#Ej_KLRf?Xj01Q~S#I0t{IO-p0CTGT6flvs$9-# zvwjp=te~}K#VY8|0yS<>^hr4B>1FEOHc~`)Hhx>~@utp?)R6Z(r!FNnMNVipqH~@; zZ(AbG^BRPw6s5*+=EFd6>}u`YtGA*8H9b3lqXEz(SS-0C?&kE=k|LfLUzF*lLwmg0 z=iHME+hqW)9hj0eZqBLUkP6B?>_AQ{-gJ10wE`D}7T8%obs92|K+kLx?4V5XkScS& z5XC(QRw4<|Ro80wmqERt$x0Ehh1jK7){!)tdEW>$?p?Fbh4Y>@g8}SI6fb(6u5e~u z@U4orqnt|;uW4$C4{p!2EFo$w9cYqRHh>$Nh@uxrTNP1}C zBuU7IBq=cnK^aL>jJ{O?uk*DRA()`+?Dl^?ZG~RH?+?6fFbpN%-|>{Cu4@usYaXb8 z8ftA-sPds5X;F3bmWpgWthx4rc6ZX7uK6_d8-?EcP*SBp08zk|d!r_`wk3fnHc!vh zNzdUMN)#15HH;MSp8ovnmw(@;2FA~>NtR#z5!pOu0WirWw@I@3w!F;iEg8WH!oS_# z{rl7Y^|o!DUBFP|pH2z@Hk1G~3k{n08UkMvA_xopVI+mDw(~37U-tj|;r2tmq9X>% z&*m7XgpiC}Ba-AN4}u9v5}XSmkrPuwBOh=5` zZMOWD4{jhQLl20}K}YjSwWaGSiwarhi*F^Uh1|>8Q1>h?n8Oz3x3&HhjtXlNa^;An7LM1P$KBVb zXOYhF9OdowPUzE35imyq&DWm_&=18nbn82TQJ+u!%0TsfRy5hG;tZ?z0FPB;{@yM1#lb=4?eM*sr0cVqP?jn(%R?x7zPRUo?&C>hwm=AEVrO{XcX5EhXBH z2;&g}9s{Hxrf)LB7*Ng`Q1XKO_3n@LDyS;b`=&mw_VD{sK?^52GnK77&4w|k*FSc> zkE$OAgZCfRq18$EbgVX$7nM@35SmT5RLqN$tTpLAEn11&^rWVxC*$lm{i=>a+(RyI zUEr}M2XYtxTs8U?r&C@3&)xSNfW(%0!Wh}<0)TysQeglQL=4C-{4(~yLG;U~`7gh% z?@uPLp1Ofktd~`#n2`IG#6d#NCG$;DR8^!X&7h2Ov#vFp-LL;=zS(RUgNPHLQA}y1 z1R~&^*^E+~$Fo_=;spr+0Y->TqwV~U^-Us7AX6*N!kf2eLFFier>0OZ7>|$h=X}rJ zfeRQAfGFDi5ta}y=ozB`5o5ax;K$m)e3lNd;T`q7lRO-rLvM9^f_kYZ{JR+O}S-!JN7*r~{xe2k6T=10TRjgEj@7k>7n z;i&p|N*5jV!(4H6{uI|L%|k~}%NS)!g0E<=f`m{ovx*yz{apJjX+E{^z#Jb0QaybU zcup@73O^mTt{bDjw{ao=a|0Rah2d(&d{S>06YtUrGTnUMOXxyIN*t6AEhX_(@D?-I z)C*M$4GXeVDT{^hb|P!qQR?>Auk^6_bIVlyR&rH!9Xu7ZalJVaGL0QMfWPrjN3GS zZg>^N^8Q|#lzXqH6>T_uckxmR+sMR&VSqhzl>e^XQ~cmn|5-oL$$m%_7m9-Lv61DnPYz1=xn0=WXA z&E0(aul0R(l{n5$8vE~`$hu_l|u&Pj-X)1}S%fBtf}x?iOO>&%_u^0+6KW|@2-$bF2yTReW5 zzu3On<-B~G_fsGdp%iQaoGew0-kovk_a^3Tsls1z{6rII^o_{7DRu{$E@IXn-#>ZM!?CFNA3m%rF z3mjOBbQhNb$1RKHvx1eamrvc1nZQTK*NwBY3PPxoKP#zN$AVBot|M1-ULGBE#(#HT zH71Kmwk=@nnc24EJ4l`kQg3gXT6Z6Lxm&f;lC>N*g-OLVx=OOw$BBbb(~?hAtl(S@ zyDm1ef?IRyy_NK?q`Qa3jxwmUpFF{x{qwHaM2BUwludd@<=Ar%4`xcQ*uhOrzBsMq zPETIf4~k{fw`8*{S6Z^Kw~A#oA^7QjCvoUxt6^enxQ(1IS7%D8W{JsiRd^MiR9@?) zp93jZ>+8eG!hBf&dAEJTfsiHGltz>-Z6JHZ-tq5jLV*xs1EV&H;Q80P|5`r`RORCb ztX<|z=3i` zNko8B8u94Ur}^DKAJ+G;k7nb1SjW#!{X#`i(qg&biMWSKs+6q-;dr)sT;Kn3H-Ane zxMGx2SW*B6l-Lv~XTaV7+rjlHql7a~V?6@cKhP+mZ|?qi|A-HqeO<+`ZxT4D4HZ$< zW&?j<%|1WQ_qNdv1R7_&04xb*Hl=asd3+pMOh4rRx!J67P*j}bRNKnc7Uup;&HJ}< zaD9>&f_=>ljWovju&R4H7B4J8fA6RJj;0iGvKw>@m*ST{?!HC@IDk#Ky=N1k-xA+} zFvjEbIl^oXgfjZ#>-_iigQto4<1T)wCe-pQ;T+XwiDb6@TF;#1{JO6syg|)3U6fuJ z`FhWNDUL=;`#f;ufi8^?#Od+t?DR0D6n%3fWNS*@IjbZ(PvnB+I%?1q^ae7=V-zUC zWh3Duzf*qadlmQp@;QF=r6`EONmkC zUD6Rh;;^k8v<{yYnw3$t-^ZC$A)#tD@mhBCnJMm{Cr|Py*$*pPM-Cm?#8ug{p5)YQ zvFW%<_}aziHFSf&!`ftEKCJ(|1c`@Q=Bb?rTp>7ux|LTPjG%JC_%5Mc+KhGG3vk4jzXbVl9e+-m9garmr@@; z&Au+Hv-{uX+bw}5<&02HsZ9s~c|&cm`4vR57oO1_pd^a6pMGEe|M9y2X`FAWsT)V{ zNbLaYUc*eblZJGj+8;lBUjK8mrLafrD@v~-W`7%D+pt_lob#Qkg-`+%2t+_BWrQ)f zVq3o5ZZ`8@{0Vqu>e_5q#0DrByelXBsITspwS~ ze?=$)g+k*@lW^+D-FM`KX7mCH<4*qm*ZJ37xLjld1z^OEZ%&|bNGJe-B_qTJ z3VY=1{P*?4)P3Fa!|`=qczRMvqg<}~sum(c3vOSjfuQ%jSy)of-pRAEo5J{@ci|*6 zURKr8hUUHf86P&r80pB96}hWxzESFXCs> zoQvap*H5HpuTuSti0aNN!ct5}tVFz?N*l7bS#$LX~* z@LI0h5_EHl-szc?E0z;=ewMMO}<5Ya?;fC1MouZu8 z0y8ILG}hhKc#yFU^uWWOA)z3lcwH2(@;8QpkEhjF;xJtlnn-Fl&z&EWmLx4^=4>Wl z&k9Uz2*qw%2-M{2QfS~xW@d?6X?hwS-!v}J`I#4ZT41P^ps1goRQDL)>p$n5a!NS?N@!%;z{#G?V<9JJHre@vC?kZ}Z@$j|YrX2~VIoW(iZiKP4OJv7 zGv7s8p)*tR#n371NUkot582*6wgy?_Gg_5iW>GO$HjDj?RP)O<*>T^y)%)M(TmDTF zPJk^50AZX_N^Qm%5N?ybhyq;#XY|u%vzfoSyNkmy?(XLEyZPOGb2s12H}m=3X0zGc zZSFSn&1Q2qpKo@*@8>@VicXxN2&D+mKMG%A3$t$(d4ty{f9xe2o&q7$vb2lYYP5Y}N}c%r>P-)2X0p##OR@Psp6;WwBxiNpIR{ z;%ZG6QR=kcsC)YdqM8xJuq%`oL)EQcw(kG9`wCzK*uV%xJoW;xC5uA|<3@xNAOM6e z=?`D$zpo!o!vZd0RzTk-+ql4 za(%@`qU)Wso>hZnr*KjBF6xKZO&?ocUBB)3(b3r?hnlI5P%sW}!lqlvIA}4;v<`11 zuadBmdF+d$s;0H%aZR21PUBGUM`q!Pi*OI`*D6(GQO%h8VJm|wRl^CfS`*crKSrXG zp5^LlBGo8M6{+v`H4Wwcq?egi-rne1x~Nsk`*r8FQEJpoG0}W2piY>?`mrD!g?_iG zHl08Xi{ap5{pTI03@C#rf>`eI4aP^}AuSMM6Bc{+o`0SH*ZQIKz9ReObNos&{cJ8< zleN)xNw_in(^6jz`qhr7l@F`cqUy;RIVV0b&yf`*R8gx6o+nI^fzHRusd4{5^KJAz zUY#+rWRwuXz~+Q-AUu}Z(bzG{0lt6ngZ=5v51%&wuh)F@e|!CZzVhk+_FbPgn;$=q zZ-1OVYt(DzsHJN`i09Z_-xHs9R+>&wA7@m@B39LT9u#+*K@&c-eD z0au<5KR?bl+y=fgF2st(SbJ$x20#g?j59)QN&%MSm-%MB2>OEOH6>G+Wv8Z5sfYMX zsEtcPN%K0F{yWsbmQkC!epBceQ&D?*tmSSLK{*&t8;$GxKkmM6f8h|D{urY)hN;IX zfovxNumKob5}>KOos>Wv?qcf;naz8uowwW zq;Y!OP75lUIbb#%$!t; zrMxbSO;g6L+%0aoqrT?_V?WUM8=?_Nnss)r-DVY(6;Z<WLl)ew)kZi`mogdO@{Qnc=-5e*J4o#;)V8B8d_~au)o#*} z$NAQxgGEtP1`q2$=h5!&j{s}}5#u|48;vNlNeq#tutxys_h0A#wSMrEZryzInd) zJXS+-n*sn|#>2qfix5f(WxI-J&)dS4l(HyD9|{q2Ph%7y_=HS9F_zaCzQmT1`1c4eNXvz^WW>wD=U~5 z$G5K(Z`#w28yY_A2u@9Ed}lmAs2~}y_;5450g|65e`!(4^FP&;3tB#zhtJL$d)DMqdH`>0cWcxk0fr4@&`04(S zyDht-gwu$`4GSTZ0|7>&h!MK8oGl~Ht_Y)Kd-wZ#_2alK;^O#e5LmiD^BZ+X?J4#1 zV=S4nhOj20{c^)sv7eY0s_DP$311bG10xL30y%o>Ml8Gaq2$-Y ztSGfdz0yTnxOIi}$i+{D3*FSO-=bHU#%)^Gv7+fg(f#Zeedowg!@}ZH$@%FM*K(v> zN%clI8S^mZ;J#LGCbffHZiEkN6C>$hqv%=HPQpJ`hE6^h)$0m!PWuzBrXKsX8gc^7 zL#bY_bu>utE8}8LI7Et2t%-_xZ3?0&O$AdGUFX=;#{DpCC>P}r%j1GHs};uOtf7dg zFFJ~>zE_L#e%5rcQS4Z9di>E_v|it2l~V8AcUS8_?|xvr9|y1_0))gHW=aTf#sDZ` zltu8I(1^rCz-&F8l?|zm6lb`)%p?Mo!dby@xEFH!=0dFB92Jn_B+Zhq$+9cU{g7c^s}5A7KDaDZ~^% z&i^yozS(T%ai-UN^J#t;XMTSAWxL(-=XON64KYFsVkmM1Hi-jtZ5nR`ctqK9M^52D zY;Lm%DBa%8|M&gF*y`d8s`V?)yi>4nT`2jBKtf&b>Eq*kvxRSxO9K3gz;a1B1+quj z4%`JmAvS%`?dH?^G9jj`N-%A>qvp8l3%Z#v>j$UM4pP>z zC^)FnA1pcwswX8O6BxlH=QsiGYT4@0$#eo-9ktH9zATkTuSdAwcJj4aQg0xmhg6}T z%~ys#_64z1kOwH6QL5sh*_5T0cr zE3bo2C>ZAPb=J7{s)O2CLx(Bf?_3(ifzvA$d%jnZdM$iAYn`}*WZpu0ThMM#y@Q*> zqSledvuUHIwJ$u`3Pj&mu{9aA&-7}#uVSS+lFHMv@vM;%t>*3RP)mmH!}`y=U#Ja? zFJk})u^9zUNSx6_2~Y|Vwc~*_y!d+eU+dL}PG@3d{UFqY!EmNEorLPB^-kz@t=o=M zT1ishRC@?F-eXlorL*K%z@pMtLkka&{dQJ3&c7WsX7|6%w|p;(1&EwbI|4$9O&DR6 zl88b?zV)hv+Ez!oMubF1|CI(GO%qzYyu2O1e9Fa+>RjPbZG++oA7vd77^Q~ zgxK*=6Kr6%O&E~vr@PmW6ZG_=H~vr$t%WC|j9JrEQy!0z<6oYxuGgRLVv_c`-@AI?KNW6X4QgI`6 zJuO>A+CY|T?e2j)d3>Dzu#I@UlY_lo+ybX0q7kFij=-i@ewTNO~4glHV~T=0@03R77@x|$$ca6ouhx-`r%G0FM8nPKE45QRsMN10XsE(2GOsVLC zpxq|@Q#8&gXE&&HX`Q!j6396nr%&~Fk~drY?d4Jb0?mx%7%SFmJg8tZsH=L`^9-e) zYV7CgK^ZIAi;{nFlF0}NPo=Te^|L6sfL2Z+OJDYFRy7LmWQCEIQ`Cy&FP({LJ@ZbI@K zXJcix%3A)UP|M`ia!p*_|L^&n?@>l*jOl>g^zYxGOCaB18o&S|3O3MfQ{?aZg1#Od~Pb(}qzqH3sGsaJy0Su75wW&K9-gYY8d zO+Hv|Sq)viR0~5t(idD6mlt7F)tE9N4hu}`L{!%Q7xUi9oi$^M<^MDETG?x@@MDv@fgfsI++Ms|A#+h@RUxZhjY}0|zS#HVdt{jgHU` zYGp^eeCN~)dbXPD2&P|CI=9*^tA*qH-{#wh5N7XjVsmO!62;2n2!Qj5+E=hs-^SG= z1)z*xu?Q%Q2mrBx*bp%S#HNe@fh8vlC~yj~k_jj!Jczv{cd9un^Q`5OLK19U^4)=JNcY1f$j2? zII}5P+8h`V8yGM~Dc{?pltaW|N&f?)h`@{I&sltd5e_u&BzOcgVuS!8)MlI$YEvLI zqKt5x@ibrb{Kb~; zQr>{<1^{n%a85!(h)Ucz#jLGVfR*>u$I@kn&#p=BB6Q|E84 zT=PJ)2d1C(}KtYX#@1j5W2?uw7AZbB8~* za9H&GNogMswL!&A_K&ezC?}JGyf1`(w`<~|pRS|nD0P}VJ;s^0tx`IyhjP~JOXx;2 zoOh-uO>xR?u)}pVqwM)4?pEir8{6 zR=tnd`T4asF#TF7L`Z1lYU%UjRH!$cN;nXk`AqMirUqF{*KX0xQ9e1l|82gFUhGa4 z1)EY9PXjoiQ4|9SDcGNEcD~CPA?& zC2Wr|c10PD#c!1UvYG$*IB1vT>3PP?mimS~O7==pG2tA&M2<2jeV1QcufNW}r{8ls z&IR4|CHz|fKqv0CB&vS0btP!W^;&QTqpq&{qUcEo&Wdx?J9VQErcgU z(ap&I6Kvp6c4cEE`nUS<@yq;EtOSgO3b5m@zM+Ir#(|TFb4qD^G#TZa%^IT#MoFuR znnrct`8nwT&pae}Gr}o5lTt zT9y6q^>M}zk?cBk4cEuR^sR6(L#Uusw27)DF6D-+>t(m7Oy8=)$9f?Ubk}*ge^!}v zG$B`)k~*$uU-xdSDA}(cx<^SV9E?P)2TK2mm+$pkCRVbUMy6iV-sOS-Tl!5Z;|L$r z%oD{3$Jo(k%BgIK`%fbHywZFhWegwpWbOxH_b72-r5UqnOGWL97{Jkxd9C z)PC{x?jP$1EcdFjk?Wke$GFlLn}MbYND(VqUnz#TVhV|L9ha@Dp!WPrPwDCV-hqbk zsG;@|>IdT*9_7o@>i)O8ZA5@CscpZYKx`64vFOJp#Qrv7gWWi78IdK62#W}(He<0L zp2YdKj56B>3WO3HsLk!TV;}&OFdJY=7zG}2LMX9;u!vA<^EZ?NBiqg0@G<-jW7&`t zZ>l7{a-)HY*wOvldP-aUugT)OS6{w-zuAj`&^NItnh*x@kdabi#~P;i2~Ie%Iq-;6 zhzPd{XEXxJDD1_4f~yGrgRni$7^RHvu_cKZqxOZT%qZBC6D;A6Vfe;oELhM*U zO9`VKfY^jZ@W!UZ=G*x%pYIh=AvcVAho>*4_93JBfX>7{AhJH`LGHlx67*i`@r zwW8oE-YefbZkA9w(|D?)@ABDf<|en$3h$9;jwRV|Ofd>{tLQ1{?Q3_uz@8dTQB5hf zbiA}>hsAOx@I7VJtyWMWeQ}{TMW?hV2EYOF&CJmNC($%f%PL{L+^IT^aMJ1I z7p7h;8sTlm)DNY!S@8S4{eD7m64hspoWnz-oqO3_ygSs@1~SAcz8#49#+j%~cseQM zjB>i`qr_r(o~q;no5)8U3b_M*Qo+041 zdsk^8%_{Z{ZBg{oPZz#p<)jb(#SOl#>WA{IFTKMJtvY-A8n5nun{R1k6UyzK*$5&^ zZ6J)0y(Q%Y=w574QU*~RQpWZO<-fey%>T812rzDhw}z;dr@f3L`GTtUbM=w?w5|o; z4L`r0Z?-@of;dbx{s`HUQLup$LO3OH)3t;xusy!HTl>9&oD!;n)vtx>$T?PWv#ywW zmv!)La+}Sfk(QpRuJyrCE#KE>e&PV5o)q*?4^4R%eEIe6D-ymkCA`I~)W+k9S4vyDBC6S2L-WR&8zEah6tDA7>cg4gm(*ms zTJ4y1_1twroW=E`I$jmR!&k+Nz{7ga2+d^w)NN0Ml$TKDeo>Yy70c{5U00d(%uC-0 z>&ITN9L~lyBN!|BtmZ%QXCEvf>jhz@rnODg_}EFiXLWD-u~$URtn$hbuxItGz#8Lh z-@{>ARdY({U|*1P*<3C&xb!~+9V~P&d_$||YW3r@LZ{@v%L*N3ALEhpW1}hLw1n5L zS=ZS4*tfiX!nwIfo_d-nD)ml5s~ih{JMgaEQe&hPF|rigObN-3(F<$!20m=G_4+&4 zNeYgb?ww4PW=&SSY}V8?q|b7hUa4DdVx^4&4HdPKKlYr{K`1>#DLtwB=XGo9>XnR> zFYn7~oWzNaF)Q`Om{47 zZ?UQTy#D>}7dryFjjrflcUx14AWrCv8T*U?y|Ou_oPiBCjertH7%%`9CkldXM`RBu zZ~`nM%;q+;W0?gdj7G6!1_-6Q&>pZEi=i5X0#HJ)2q(m5jMBZmJqj;A&3|8C4iAU& zZSL{_PZalEI1*HA+|dFVhiO4g;@RWp`J8jYcDwr->8$1#Irt z-84?%a8@3n-bJP1AnckK($n<2%wcLI^p%8C>Q%o(IOUZyb?g?UGo>c#e$DltRmzjw z`(Ni@ff5ctS&SQt2n7zdO@Kv90LG)M9Uhbc`^(q)uj>c-sdrqd;q}?TOWd~>VV+Er?f7acXFLc4H3)A*tt16|&O4dC=Vn!Qh3vvye3cYhz6*6UMpuU%o z?8(X`#O*MfX`UX-az@!lnl?QxV|3iSP3uX7_2Ya>7|&ki3yTCQW2e+m^ju-oY$3@J z)wVgPYUpjNhb-avI45WQBs$C}C9Izv_0+m_<}c3i(}RO#3(JyPuQm4z7+qwr>T6aZ zw2oAqKr!`>m@MHyF&i5G2^XIxg}$pgj%wk)hfZ@C)jQ=IcaUxz zcY2PPJloISH2g&&DT?on!9}t*=xWIH+hrwXq2s)yh-yw5rJSQwy6${FReaS?tJ%Uu za$uE_5gOS;Q`{GwqfF}JNDZD{9J*q++{(M^hw2LH`>_&`+l149&xhmoe~(fiz*xj> zO5m@Q+JwJ}TY%)5)9)M|Pq5-6FEt9}N}(USt!9 z2m?kr2OGYL^C7P9IWU`Z8))p60oWA*bcZ)!EMmZUyhr222L6}%!(+)TfHx@$o#J9zMC9j5O!-`X6)toDm9FHjfb<95~^?VF`r90oxG(Ea}xAi`mA@ z_z<(0y~P=gmxna|c1FQw9F}&hjb}6tW+RLc+qRc9;?&-O`$(*0ppA=y2R(=)^7F8|87ndt7tZ9Rbu6ukwdwksdg@X;Y4M2c#h=2iMum?a1 zFhU87h)pSp*MJ)*JntDPmK|pwem6NT|nR{^t*~S(7|>Dg3N zr=~9pKO{Q> zsTvlt{YuO5yZ)ko_*x7t{W6IXvkA^27l-mBYXogo!r3$u&GJI?g^{Nsp)pc&ML+E2 zw9r_Ho~!%fO`>#+NA2v#CPKLM(VvX1PAQRa@MSTlReR}Vs`=LS6XlUN7@TW?FJP^K z1}V=#Rxx+tqS8Q7`{#$=V&Gso(7jAbO8N=2S~=>CON!y=ykwxS;ue6vAi!x9r*iBJ z!M07Q&Em%n0}9k85PhBhV?DdPG#1r-Bb54K#y61fRs3GAUcJa5=h!XOE-o~+iRyi& zYKmBCPCCtQw%Bj}?M#=1Lbcbg;(cEWC#(D4=38QuD-x4H$sQ*Wp*H0}I1pgKBNA7N z3^<3l-j9zo#Qu`qx{^Z#gxHMO90+j2z-AO|9#No-N3bLI*c@UpYOEu&;~g~r%iY7H zv5y5gnF$4?-#CL>&C;}sO0v}|4|4-cRnparaesgJX-j{2!vQ$NI=$T#aA$Mj5F6mY zrW7JhIfEzydPO-zK$%VKIOUKt8vodnh!I8^p^WetmdhwW{0T7oO)Ssh@ud7Jew&k+ zj%EYdV>W%W`SkjJ_3o8ZPb8`x6J2WIQ1J$nZcpMwf=*^x~AB7C5=TiMmqFTdV>CALlCJT-1p7F_{A3*eZQ*aw3+0VhL`!vN+I1avFu`;Hs9(C&yfn^6lc`1R;yYHzIa!xBb3r+)$E1Y zPKNug8#Y8uLTTR_9aj_B)W_A^L9LB-RLf~t2vt=WhpSa#_C(L@%Vj^+kjgj7`pw%$ zUq0|~E?31?FEdLPYVVB3ky!4}3aVZiKhynW(&|f34v*SO5LSnserq~g)lt>Rc?J$9 z7eb(Fr4jC^wcFO=5&m0t5Ek-|qsm_6LQt&Ec+#8tGqv3BPc)Qtz3~{0v}#|=UTX3x z8G_1qRQ*&i}EV8ROid*pzfBRIio9hshaAOR7=( zd0aD3PwR3KA;+z0Rm}?)v(885{Y7J>4*Vw#VeIHgo6V4z6jijk|82gt-w;Xwt{Ciw zb~aF8gxGuB2B3C~t)l<{R}ldQlt;k%j$>nUPJjW22;%0JQ@+y#KrEYy^G=t9Qs5CI z1fn?S1PHl`_>#chFPpoE$B*T9$?7|5O+0l`MykqQv82`(+D$MCbEie6I7>X-|F8Kc z{^q$2-;#pf0N8P07>{>@(Gn<760tZ}AqMY4L>NRiS;j{^-bO@}P+&_6jP05iAQWsy z2<+0>Vgku>8D}F!i~#^J4)OStfK4df+uZ$YJi$eMM z=6pPyd{Y7fiN*Og=bT0q2rR*-j9if=A(W7a5keXHVY`{HZ*ONE*~5A{Od~ac#cM?< zO+r86b*u8>=hus>qzaZ`e5~j4sAf(F;p&-rySgp4+YNkq_~qBTuX{X-fg^D)0Eqx7 zwSh&z5jyZHJiEA%Tuh?-(T zPYiCdnwBk^Sj}X5BlC88{4!PVm&%1{BT!2FhsLy`o(IRRis*_BQ^BQP_myg#wM69U zNxkKk^Kv2i z!r!pv50ysa@1iQdv#R^qiYVtE)_>mdh}(ojB%Wz=9vjY<0Bjj8Ii-xnl`94IA7AJH zSXZn5!PxLR@8p`2OO%zAn8C+41>sPUghuK3)KS%DW}<18_K}%8evaT&?tVw$5}-jctj(c z*#NdpD3Az%Fir`N?-9ZQIF0BV4g@GA1Q?+V>}3Q?01_`0>6^{`;jt4cy_~;auZ7CK zo5a#W(@KI;ouMOZg7e0(dfE7Hwf@(9!#~Ah;@wjp*v{0(DJIy%H=Ev-B)wyxdVMeXr%{)q!{LTKDE*_;3{o6-pQav7IL z#3r0lu<0J%ZszMT&SYyY2;rp(diz zVC7g!LtEY#L!s!dOd%fns47XJpgV&q(u+UNo0cAhvfM*SuJ~|P4a<64Rn^>0wOD^Y zjtno5o2u$L0@5C$Raofge$@^NkM-8fMXOiHTD0OMstL2uN6*ZBUg?Xz5#?lm@XBhJ zqWHY239%LQ1$CR$Rvkerbd+{u)kC>DTB@g~s)5>-zV+k$NvBh4$IIeWAIef9dy}>k zT1MV1A8W4DLyD!TS|mHV_jq2jrG6!eL``WSvC_2j{ll^oJvn+3c>9lzU*Fz@x@qL}YzUa9gaE}r2&Fvz&l&OW|G4ZD2*s2h zu&+M9{paoJ(7w4*)ZBKx)>R{zSiV2|hrP+oLNiJ& z`(sCsLl5}{w|ZWPjd+}00XPMW38g!}W0#kNUS86ymWB^t2b6No2&8ZcJDz3WfMnz? z{=cOFV4M>G%+d@zvD+~Q06%ab0GKes_Jl3Q08B~F1-l2VE<-@g0x z_WkMXaIQ!7WlIauRh*z|RDCg$Co8XXv233A@-1}w<;&Z*JhMLFz32%D=V|tf;gn#u z2eT32)~uvJJr%ZS_pc_x)o# zFssqTLW!@1;rj{GZdkOcmYHQT6)nuIaQ63qzX<+ z*JL!wIX-fbT9YTXt4+NpD-;($YN3aM;%U33_olfx3j3(}>GqFzzhF)Y1wtsM1mg^I zo4G((iU|-PI|hVhJb-`RZlf@aYm#W_*OTcY>U*BqUM`TMCze_+4z_wNIM&x=UkC$9 z{pd#aAQ$LHZ5;(wOYTgX?Y8ppYg1m_{cH1qKH4!%X?j2~Pylj3Dd&_DifIZ!vj`jk zMi~HMz$xJvW4r?lz&Xdjcjubf|B~l^6PYy~vfD`Xr~XpWXgz^M+E6c}g$<-(@5v zy5oC(!oER`F+N~~5=trIgmWO2ury^E)rT^gagH!xvLlR83XBjSls<(63c#~4Kz7a9 z8%Y_?)}fGcP6^>a-n@DD&)YA9?Sre(y(h%Fr;HB|Yp0WCOYPVLv1cj`Pp-BXN6U-{ zKsX_ANhkrrF_3-ckY+wcPAKLa2%-4kTl{u&n=`eNJv<4_9FkiLWf)5ONKP)Mp1WRO zDD6c)A5Od*t$Xxe);ft@8&#a4ezTq)PHVz?e)sFU&x8}25+E!k7*osv7{DcE`?)Np zyGu%UKnSMv^XB*4PeG`PPGY~Zi|(@{^;9<|l9q3yb)~6`NgGYNbK@rg9gdqld!Y(m zr`B=$RXx^9>vk;FYUe*{a>tac;JtVe)L#A+jI<;{M^}aNRWQ*??MdI4+V#HQbaO+` zb8We%RZ%eX!y`{y+%sEl=!&x0?SunMj;l3c;g*v%Qnk}a%*$#=6%zSq+(g~Lmo@EB z2rI3_d^1;_xW~vrL1}XCJ?XudYm>Tn6d*ycBw2otmm13R{7M`l{Va9_{x=qovvz3$wBj32Lv_aym|=C{DdRm(;+CCy^A%PYMF^ zR>IVpZf|PSqK{m`75#8(jUNlGFm5MSX%V*v`AJpN^btzX^|t%qzeF^g30|=@9m&dl zp@j9&sJln`KaM;NDo$?k{Gn}6P3-=Ewp2B;d2 zl!1I9BtwK;t^dq#w`Q_1HCAVTlZIkS8RHqOp1;|A`qDCNd3rw-m2Ii5$e}5;7TdKtsPh#_Rr1cmlQsvALyQ23Mt=r1Mog=41}h89)5=IqdP``0tc1>g|o{q&`Syo zfbopI%7HRYI1u2RGD;auVP7xEih?v{0E|#Zcfh}cw{L&B{drwFFOQL`2`eNii4r5E zzFf40(%S9hD!J;~dalc-U%qVKa-3zoc6@(^f)rAkamO%$Ox%SrC!9XzZ{OV-hp`h~ zbjz#Q8k-XndEd&8OPNFH2UTZJnvN8Pm*Ujfg zgrz_TMRGsktT#{JTvO| z7VAiP^(qX?-ZXLhT2;xwKdD>ns2BEYl^lDa1 zh%XkQkSJYWaO!$V5~@v2Y$lDMplDHK3ms%D)0=gx@Ub_xec2O5??LE>8al5{&YgIc zh&^Mlo+g%4T67b;=oB>lV%%?)`^{!tbi|IbbzFC?%#cv2nWa-L=ugzV?Dtz|j_#FE z&>SOO?9W#@-HOcd^t4+kiLyRjOsmKgJ)xnU<(X)*>RArpt49>mG~H2x0dR^bCxnv}IALT5aQPi!1W1Y@P05Z?MlT7)j6s^A z${+;>9B@i0XOu9Y90*{zBrFTg^OR?YTgoWKI88ZA(>?z(&Cb0~H=n*tk*)}l-W>b0 zy6k#rxh~}{G{GO}iaK3A69(1og*D9U?{EM0Zu8;WZy6krtdQ{cKh6mUNb#O@%K>&k z7zN-oL#`2uDWho$z<|>1!!kyJ9;A>0&ZNIIn<@}?iGgMc4FdEBf-$CyTxKOc_?`p% zYO}ffd0T4OhtsI2SBxuE)w@bjn44%N4A)CrHFez)p2@BF%a_f!{QC@A$O+?_Yk&b~ zz%%wUCU~FPp^zT@vUzt~j*r`Zm@5}@ul1oOohDKLp^toPB6ac!^0c|rjzr1QO9j2$ zL9e~4zV?wCkB0Iy*jOHDbFaxlvBb9PE*Kav%nZ8oDv3v(v-0D`_G%-Z$G^^ zO4n<26t%spD2W4KUOkDNBUuh|t@O6@eCBwa*vkvvF*>tL zv88s^Fplb9`(saXPlA`;ESfZz^0T>&3^Z)F&K~DWYO|vq8pEV5jdi;zwe7H@$5*3z zONy#TuHafH^`s?eC_*QZBV1oU>}aZ6up>dz7ss`voEOWn)=)h;x3-&pw|jppl+l&A zjoOJQnL|B#X(UtMlS9ARtxhbZ?Y(G8*4hvYXb?6PYwhHs+3VuKuoM|N!);p}<ar>bbxATYQAq8*%Mu6;6!Y~C)`S+jS{qwe665^7Yb3H!@yqq9BtRm!}*+{J| zOPX^2QmsdWnI9D0f{fO6GrXAiH)1R=+UDceuU8F0Luip`_V(^yn-6@VY>^K2P2_Yl{nE_^$aR!-7hwZ<9DF@hPGcQc>ZU?}5 zih*bRGs3g26#nI_&0jyeT1C-Xn!KKhPIVH5Im2{5J~mzTL0^qt-JnA8`gF0V9OwS~ z_J1~S=~oBrE6ON^1ISc88N)A29YPDX%xBU zqV3DB7uCLe*?ju}{udzzDbG?xjF8K$G)aK4OGX(b^zt9++s*Cj@TjfdcRGUJ>$bF> z>vgNh)kN7C9D2#BTr-TUB7rnb*H=mz!yvLVOppbG# zfutNWN->awtUo~+#+2~v0bnhfR;m78FB+(&|7eUvL7pbwSn(^;O!508QS_daV=eD# z!a&d0?16O@4lSgbPUOh-C>$m2t!U=!qk0hB>suvx(v~ioy^qC(Cpw|(b%d&;dVb!Q zwt~?te=G-Wy==B~W^Wt?J*nhLuCzWz+gk3_HpFvTb;c8~J9kA9DgIIy`hhbu7IsY- z9mYfSUk~zj<>l&0&z?q;{QK2fH@m^AmJE;=6yggdirb~RQ+r(36+`a4R(-`SmrYx= zx1Lp#HCvmNl&%!}ItoTsoNLKrTW|#tb-eY$zOl=i=vUA1CEZ@$w;C5Wi$NzSS_8{KeR0`;wKV#kKGACro!t1!UbZDM z)Z@|Ur0sd9MXh1iqS2?@KQ^2Lr{T#f`e z7nQ@)EC+xIV8EA@5<(6rWZhH(oMJ}OEPlWlTrvv4Fi9!dVZbTBBxwfVXW6luP{Q~5 z|LjP>fDw-I-(6k0%M>Rh%|IPl8xT_VA$|AP&vDd_56eL~9Lj+vB(^!J%G%=P>!2_$ zp;>Er6`mHYWz)NvyubaQ&0Bax`O_3I@RagwHpgKv+Tv+;E6Kd!KnP%9e{ksW0F zv8Q0EBb8pAVA1Cw_RJFyMe0-t8|mKo8!$eRKEodRbrg&;61jKQ5mRI_FAs zQa9_?wGyt!O+_28)j*T&u=3^0<_$l{9DNi6&bAZTM37~FZbC36n52M#{R7@^ZqZ74 zD74UU5EZ&wbboQQIt@RfZ56fJf)*7QB{g3?HJ19b`7rOuN1om`G{xx@2d%tZn%({S z?lXlH(=>bKV*o}d1xsASB5hcWs&1vF z%K}ohX4qV4LNrz?HQg{&-BOCJg`l?1N8uppb?=A%#EDT7`OTL`;Vh@+`_8~}o~X-3 zJs+G8Dq&4R^4DEk9kw;49CW&JQ1|aQy@Bk#Mi1u8b5pG*uAqfg-S#@dC-BNn6J^0J-nSA>YKB(39u~E(Uzfew&}=312fE!A?V&T) zO3i2xxRG1YwW(>>WydzkGc&P{PxUbAXtHV69p|JV*$-b$Cz>t}&rh7gp<369vz6IJ z(cyD6XpilCS6-=MCvD_;9Vh6WSh|eDmyICv0szODF$^@_r3^6O6jMqGOL^Ag#uzB0 zmnr-1^X9kP3;)nKkv&KMu~rf_byW{TQy89AmQh3Yl=hFWa<7pw+(rx88(sv*IcXXX zr?!P2f7Du~_IbXMM9K?oaC-Nz%`aK|m1kji4jlgD38jPqr<7n!7~OxHSPB4Xw!gw; zmuAEGJ+w5X08il{^Yu_pAO(VV94KV*SI#ei^Z$j@robTOJN(t=ub;15?TY2}tL0Jd zET>1FW2+tO`a(9Q?QODJ=Vr&N<>}UHxT8k-_FwPbz%SoXfRyh+LBJTmE(J&-<(xnY zJK&siz`)tQjDQb-F_LDN_)E$eForp%lxEYxOr5y{LNUvpgFxBkjsRdrQ^TGX{)lgTeXwr)YxuGXd6J=|Eohv3M?Jr+8Z$G3Qr#lS5 z7z2t^ypJ&9y{V5-O4F=+_!Yd}+&(N(klI z4B)?4s(5v{XsY^RG3zgH7Nef)njL={>5gH_RZV=QEn3^9I$YNdODI;g&`sit+3{Rm z$WP{#NI#U!c!7+fKR3p+!}H;yeNss-26r}=1s#Hx{j@Ye!Y$CnwYhJBX^W|fq zF$@!J(kPd#es0mWnqsHcbsRA=Mzuv~AQ!!^3?sQ0y-a+gKX*|Oo{YP$UK8R0QVqe7 zJwwv$(}AiU<=;mSl{|W^6%QlyKv0ocP?71McYZ?C@p04DmTkiv%8nSda+;{g$};$L z`^UQv`~${WzYYJ8QouAb$pQhAW{99iILoiT`Mmk<_F^dv#d^M=bzUb*N9-a^FZoeC zEQt@kRxd)og!;?eS`cJ;(omjQ?d!EN6B^&6MPRdQDF?VD6gW_VF~OGvfDnR7%5YX&;sAsmFb0$#TxKT#N+<)GC4eaG zfMN!m%M6@A`ToK}_mDNN~}i}Dm6z1e)ey}ORW`1Iby&JV??*ir5H@pGk) za#OjCa`*jVF??SyP4%rSBwxO4zWpVIlye3ga5iHV4&hgP`Q_OE}u z`}N)D?=ac1EP9j?oU&2Oez`;s2*Chp3Yg;E=gsf8pPq}VBZ>{NtE~sNZtC%4(KV;{ z^G-z>G?1xjvUseynlgW{wyTrQz^?V>woosNosac}TWe33LPu0;{i#b3eRJh0lfG0kb8>6# z_e)4_mP9Q6X_-aU75D#@>rH~ zc2<5C$Thd#6NT}1t&IY+Ge_sI^TM<-4aJ#!Qac-cy8Ywbhwm8RloOI&xPKswP!5=- zneB@2gD!*sdGmSm+wJ;vn5a=nx8g85Ju5k`W-ID6OoZNJEhmU$VJuhmg|65rS|rs< z(hS>;>RDHgbxED9++w7*C85x3-~GAy@RX43`kU#ZH~;~{Q(%|^!<1u|!U3n8W@c-? z*UST@7<0VumD6mjM}Sh;0cV_$OQ3{977_xUa-f)Vo>iM@wohk_62`vYJ=*;B^Yv6l zfm*FMqH;K1og=+686bI<-`?Kn^h zK)?Iy`!}0^-rj9LzFt*MB_k(Jkx-On>!vfxO;A$3F5M5ENcYi^Toj9FBu8JqY`*1} ze7EC#2Q&jB(fx0XQ4SPRiYcX>GDdggEB)a2VN+A2%e5~|W_1MnMpNC2GT3W`AIJjAKfsv2s>yjcoFbUgofbe-Rr|TwB-kwyn^()Jqnd71~QIw0;Ad<)WptG{iJDor| zL~6nAcB4tHA5Y|hB3!wpl_#k6m(FP&wX0fA5XPuc93#0IJvdnukXseTRbO2%PMgR! z^mT4_9JMRkb>Ydv9?4}-u6v#l)b&ua9#%TCkY5FPvFIQ#P}E#kZQOT*>-=0%HEphr zI+K3a8|$K2K8z-#$DtvJu2HTc6jpR;tJdwDs?Ct2m&IJzSJ7eFtT?(Hl!Kp4br7Mb zGfzr|QU1O&j?Y8G^y=FiK{ywk;7k%e&XMPo+;$Be&P-|S1WvUf)@yOUE)Vq6&Q-H+ z92V7h(yv_jXnMc<>GrR8AHHK0QXm=qhou0IAjOaZ-O+;#Mu+(&1w!zf&u@Rby1B#J;}{4}cZA~|hmOgUp12oMT@DZz9{7$$7TfMb&F7BIO4jwxnj2ZRD8I{xE@q1!<~-1 z_xK2j`o*-hZA)H%wK|KesoK?6LcyQ+O#R%6%oFdtw6H7vg0z_A`=}M=?S|G11l5je z-Qr=@s)#)#tIn}Dm>7nXM7dL^k2LManpW=|Bex*8x?c~gwjRn8ttc4kDf)E#*X9FG z0a&^>0s$sKI2^D{*9QcCV3-1M!U=!$`R#AF>ri|phb`@8Ln#N|0?Ado<#b*5CX}r( z7&-Uz<|LRKayJr=js`7dG4z*_v@LaANlMm<9=rL1=jqpXe{T3!2XsdXX8@eCtStVY zmQ7Yv;hDr1vI9Q{LLX&a!F>vlbIdug?0TO9~YS+nV&{%jPWsf(ap+Yn@}jm}esw08BaN8F&l;*ug*eW^?=XYB4@?LUC+&I?N*Z&Eq4k&;-N8nvzV&`k!*TKVqZo6k=PAs7R}lu}Ak z0)Qz8x;N0LgagIdpYeP4dGqV-Cle`=kT)K_cwPwFOHY%2JRi5BuAkR*wH2b9X?Ye3 z54T9)j*rL7sNfBaNYi@n%DQ$5)t2u_S$h}%l zvNEB0F#FFv}(xMHjxtSqfiTa9mge~RL&eJGpg zx)`|8#4wyZ3IYwy>vpoxn?^|s9-yLbJ@XW0T?(q6D9;0NCU5J#P-%NeFuJI)wjYPx zv7XnXh7@=+R8%T$tvQjC%CRiV(fuSA-81h@9~)w)d>pp5S*=y>n2}!65+oK@@ph@I zrlXAVO1Zo+v=YjxcBvG6y8G)Lhm_GYr72*by90tT0C|*VwfYn|rkrC+3CH}==gn`o zkuFR3-0C)3Te31eH@Dki)r*m)d>uwfd}So4-5Q|NmZ0SOwU*v)XiB@iUgSLO^mHAy z7r8w01yf(#{<(ptUlB?O2e?du0i-(sLV_;yrh60^2jE#Ah!TGAf8P9qzQLO} zf874}`g{?JVZ}(QvLT?RW_jm9Q{8qH>)h+ijd~Y#MusoQV_k3A+b>_6bscczI^3kE-_u5ea9NosCYi;@S&dgHL$k+d!HI$mGD z(xQC1o~w4_QZ2|IejUuE(%jRAr={?jGZ|G&zAebL<3K#Ig<;=uw05hiSx!3^tA-(Rkq&qcLTROQ1sKQ?Nur0Q3zy4ruzOCn_$ zxi1f!)8?nUzupnT`9bzF&3p}z{s#mGKr%rk21a=LkI$RmZtX+VoW1fJdfV{&r%tr6 ze>$}E^896_UN}DqO`$K@(X_dtUN~cqX@zv<$HY&Kni>KGyox-0#7L>oMuD0{e~WPK!EMC z$3q6$$oe}#GrP{_kDs^qYO<{5g~98F+x3y^C=EgJ6elR7h9tR82kF}ObUDe3qOfk) zPL8f0-2V0X-CO?4cUgaj{ar@{Aep!eIB*Oc;1YndG~4TEpJ<2odq8r@Ffht8-gwF| zVU*G=te1gK8Ks#`gMPTg^ut&0emlKAydHI1g?MS*TkE0OM%{M%rDSyEo1+zqbH_a& z$@#OE>DXhfxDsUj%g^uL5};W^VTUQ%(RBZ8hm`Lq0YW$>G))-*J$M9f-rb%IJI1>2 z7~;grnU$6&*3@DWTp`an9ts|^OW}nd-ZKhzex+$uXD&qb$K#e8>y^sGExP;nyU$NC zr77Xr5Q!4XDeO|%+rofyz!(@~6u#d*`n>u5_R}hVg7W9j=F5fP4U3_cQ`A`e22E61 zde|yD$gO@|o~kuNtE}xnutZssYkH?zy71(6T(IX)x+^beqGv%3nfAKcMT@UX(koev zUe-sdE>~0$38PZf8p?sTSoUNi=;+7i+RU10$H&e7cpPb=*%_6@viYM{LxsaSG8{Fo zJ6g~~)uo_V_TZ%F=uvnyQ#4e%*hWK?*h8&q2Z0s+Xg}`gfvh=VN%M?~A_Q)=I~)r_ z{h8Nj7@?K(I#d2?)j|a^{qDR^+8nmMBxRgJOgvF^8%#28By?A_t z9ArDC#IMVOuA5@m3&y8jeyb@TyU}#ji8UlQD}p3Ax{`B+SQEu{+jHVd<>*LUy(~^l z$3gX()mloH7EX*4tyHzOdZe}O!=Ur^@u$1L-tYqsl*3cbfinzQHk5J-I18R-tqn?f zO4HAq-)>JeW70JyE{f0GAi8)J*3EdWXg@mY$Q#YkLXkbqT6O%&#GP&vLD!@4`H?+* zeAvhr7N_0Qz@Bt{<@);W&kcu1jNu&s&pNXd2*sQ;0K&5=9;6udEExdI2-yL~EMo_A zNIA|-T!dk=W0(+<63!vTloQVPG!eqGuUl5PCm3@8`iP|jE(!iFefP&*QmX}?BMIw9 zU#$(~oVG~Bm7p2A&_SW8o=T@W_dr1tC6)Uo*5Tt}s3Y3wI8H8y@%b;`*6(ATm5Vp@O5K6!Q>iajF zf8X7%ug?-~;`f&Wd0YsTqIR<`c}Zn`C@7Wjx@C=O>+Q&0Pvo;=6?Fn3n*ltf95CM3 zqd2Dwa8}aAoM6l`y`-5t`04lj?YmoZsLv0z{849an$>~Q8$EOMS4T&hHdItMl8@9` zghnHzw_i?Gc{m*oOZ9q?2-~&WnaIlBukSt(oRX9hN+>5BF0)(%5a5(Rno>$B28s#& z;pzSaAO>Uq{z8*Ox2aW&s3{j)>fDnmku}sD;bWMucI`^iKJ(B5d7*bYX^DNs=u9W2 zd_l^YgBOJ$Cx?{i>QV}{&OuM=$sd6tAJCeI7^yT12ob}P+G{}oDm69gq zJic+CoPkqA`-q zs|UhGOLM%SK2GE(O36X0WNW$~4U*QiB1~#|BwJE^qBXpcrnMb0ryw(X0B}xlnq4ml zWt>nT8PJPT!hxoIM{yRhqx^5B6~=oW59}*A001ealmQ2prg#T@_aA-c-|zjD|HtM< zGgln|JD~Wx^z9#ai{;h|UJH{Jnk~D+EBCcy%RPHA8|1pt$*8p*zO10m>+XXad7=cX zY5C;zX1o6R_K!E4ryt0NlwVR#c$%q6QntGUMxO$ZUFP&i3CrsDG$VWO?!rM~4SS7(K^$EJbR~p=AZovqBqc3u`L440K5{0Q+TJkp^4^L9jqQI1u>uZpES zZR-=~x^Fhb*lXeW`LIIa?WyoG;d-C=pl? ze974oXQiyuxs8ZRI^T3CJjZ}4y0)ydT!S|cwPoX3M^@WTt=?n#Ya5+hQ7N@T4-x7>Ix;H2cP!hAI(^7RQ!_ zI0t0*5{2W2oY==+4?V!`@q3%0YgL% zSITM2=gbWM&s1(}@s-#2g1jo(OEbaeigt-=*D&n*?>tCK+wz}Td7avA$Q^MEPm9wzY$s12a`Uy2`jC~)W)%%$J5JN$ zG^$hNhF5&ar8AW5Jv_s8%SLX<``C zGSui)o&KyhmQFE`c{SMa{c{iD%fIMdOm=Wy5?Jb@$j07&^Ei{k9^IrOhy2}tL*G3C2v3;OB2g6>mY0RfOw-JLbgs>p& z?vP!gRt5V3ok@<;@(Y!7oXVB4z>qdZeY zcFMi7;o4Waw;Wj19xYHmR}hAof=5wCa@obWemS{%X|J|Mta4to=shWI+JFUY>>0Vu~NM9~bJk z1o6TM8k@QT+ZFD@2x-m=_RnbO!sRgL1hYO3p9zKm~TkfGgG*AkkuGM zb;cU*B{FSrXM2~AhzM@NjWUAjpLuJ8rsgi&l4$pv>}-mI3)QhyAh*(iPQOnC+kyq$?g|u*sZ<<1SPshT6vO4gn)4xD)&nhFtqPU>eQ)){x=ad z-(HrLYMY|n3Z3qud?%qg%l7D$4s&OuT)6gLoHX*2F>Wp5gz}#OHy6XBWH+;RUX6t( zo_oF&6MJdw>fteE7o3<{`tk-0gPQ1K-Q$}Na&GCLl7t5=N?v`*KlF~)s-L`1@XE+U zMb6WD-9F7<@?YJkN9n5Cgp>_RZl-~Dc{BupMo}ifziH@P*OeAD`aiT=UJciJt(fi1 zA-RT1Zz$wxF?jdW@gyl*wkXx;sGdR1y8L>CU)1jgKefR%0HTxL@LOZ7xu~m*Tq&hI zxc~EV>8<}qofLCs^MN`Fu4_QjNGkcpYa?c&%k$JmJvDe?132^cq>uT;&9Tk6egIJO zbM>wcd)S71Bk!u=<1n}spX7JPA9QEt3NSI188`cJJeQh z?s`lo`H~d+NVU_Cty{g{8-2dhJiE2=G&cmZ!`E5Hb4aXBV2U`P=J7cVGq~)17 zaiJ*$6=^ClgU~V_VdZ8__oWX(0WaqFSI>~}7;m+;VG#hJN3>Gj$$bpq--p>)L3N+`k`?WCe87k*jy31?g4bv!9be<-I8+!oJbW?PlnGjhI2 zyWgLW!I6j9`Orak7U88*?73UFgnorth=qhA<$inm*007FmpqSZjMp}-@*r%)tkE!_ z>H?>}`f>SbmpSy4FalzKg}_bY;ke0rQ)kqj!S{K#5l?CpO-=nipf;hZbD?VaPy26$ zHFo^6FdrAMQV&RNN7(pmj2*|<-wUeSFw*LxsYfaZJO5&9__L1#dvf?Ty_*@-uylXd zw$1pJy^WVg-~7R`TFQMFIb0U7HhFhYN&BJfTh!%$?Tl<7Ejhkua$vV*?E7=Nvp@IpV;>4U3r_M~6WlOGrft(N z%{9`K^%@@EyJ6bMW6i{+0GL@JJ{pNr+!&Jqe0Y=IbM7Ri$444-?miVK3vb@1_}=f< z{fzKN!zjJ6q-?{Gi5DnhOu^nkcNW#?Vf$Br-;Havm9K}P#@LONo401cA zz{UaP4dKCeRW4JlI3Pi$sxXT=mnk*GK&q6f3F!WK8sE6M%)bRBu8miI1>&Smx`H-Y z_(^I%31LS#emvoBuL)Y!uAJJi7FyAnw&(Qe%Ex)m_rYv#FBF_2A?&@JM$XO>X?oTI zhzc7T1_ccQg1vqj`PvL$rX~@bX$TO*y(Cp`g5}P)9>tS8B!-2V@iu+jDrp#uwS%ri zG?qHX=%5m1a^VZ4uqG$9eCb{dHXQHZ}?l-pb*}GoZ=3EbQ z_!;u$%eB2$SYYQ^U~is6n+EW68NAYcyB!U(1q5=v1CC0s?jETHa0qLT8QTs^CYHx7 z%s?UZ!LGxL`Ap2kVCj!|DtgbSu#!T0^5vYvqkjDe%RyI3U!b99-WI4LQ%Tu!Ck@h7 z=LG9_6Si~I3?4TT>>euG-!H>O9lck2W-j_C*fqrGmcCW~MB2xowJ;~gMKk7)N!#=* z9H5+>R8nB_h|=tkEAK9p6hzPNF$cxV+B~%eM@7!KP70BxM#0Y#Oyi`Ef+1Z1%!xy- zJ44hGN@={~w}nwBBJUY_QB8~(2r6{_85(T{6NsuHwE?@h6NRAtuk^4{b<#1h%U}9cq;MkiI##_;MAYc_`zHR?@5_Osu<*m1uKXthgb9sQ6<3BzA{c*+BKNVpX|hg@RESC zavaxLFNx!PY`Q&Wc7WHx`=BqEx9@!>bhX2k>lOn)bx8%-?1$0%TiybP!KEa@J$Yt_ zI@r+$By; zJO#>4HmEVS;v&vlE}#PEy%A~DTD}?Wad9GCzW1c&(8DnUce;X z?2Uh*VBviHo?_gqS$j2sBD_3XT)G9eW1uB=ga?1&_m?E#?YM;~G{~Gzfq`ZkzIT24 z0rh9XdPxHKx~=4n086k^l7drqwcL>yBT<4t&jB(RUis;JE{B&$rVG~^fT3kREwg@HL{Cv;%2^Sug@RqoID!S)yMrq{3CEJGj|{84PH=Gg zQ{B3f{$E!=qA2exHz+to&6th&kZ@%XEgJbSm;v)lM^FERuZ$x;?1*ir1!kV2SowAa zO0bxg0&AKyiqX4!<1fQ9?3S{>SI0-~6KXeY`^V~uR?OyUQW`m~sRsoV%eo{t+Bo0wmPo$g0ylX5MXisqn^B%7N~os!;nEx>Ym){9tu@P^{s={ zQ4qOka{gh_g7@|Da^vOBkuCQ@q%ruci13^FIHH*qUi6;BUeNms@5QNRFt zb6#&M0xLb>>E?N3X=F{oKkzFq;<8a8-qIv!>DBK*F|OPAg>JS`4&GGoRx=f8Gjhd@ zHCw2$=l*0~R`+wI+E|X=-oa`dI4k=jCbi^0_}?%PcRm$=$adeBXQ zBmfRajT)_k{AQn_iiCC@P87}NQIf0Tp?DWL^onGO002idd$CF|1i84>WH$kFGbE|% zKRX)q6t`Qa*O%#ww{gJa;Y#8jth(l=*`qNli$-Mx=UQB#NmQVgKqEp9_SmUd?#ui` z@PN<;JLTpSm%>S<>*X?c{)Yp{ZJ^d?TTjmH*W1y)ZeVn47oEik;5Mo?V-fR+VzjQ{ z2KmBy22z(14P)C{H-?5q>z5j7uVjzwsu+-T*6Q&b6&8 zV7YLR<0IM_JK}bZ+0WF&^%d7Q!4gc_*+su)UPIDfZ8jZ5b(XFl29`V12m|~cVnQp% zc8cW327fV?KTqvu%iYb%#*Wz)-u3az9D5Mg46wqO||wPX;AInf|M%X5|>r+dBr6iHPClHWF+}jjn{DgXqVr%gsr4j zPzt6^4@|O>V{v&&=1D}$#%b*Uymh0e5RkydyPZ7Km0&<14d}Y)mR(Xf9<}t8agQrO z_awp7Z%Ry(B#BmBd!1Ly@bEdpJ7Vn1VM;Q4qWQu!f2Xbg!fZHn?P3IXUGE^p*LDxD z#I6ozFj1`E!v13;q?4ztjT%k*oA6J^M$M?jl^#U4crB2Q6eFtBwf#qdafPG_WHYBU z6iPGnOR_ratU$L6WN@ZQN$=ltb=_YKHqOi)Yo++g4Z&5va_x{WE{|Kl>8?9WJ2yD) z$snlhTKd+CkakbfkKMwu%XiZc-UE?*_YsaeYh>H z1?ap2=g{WVP2sQL!BAY>`*fDuz-K*^rn`8gYj%0vdUjKD?6Iw-*O4qdBFfs{+(G`h zqBTe+5^^M8SXuRr9QGAxZ;3Gwx_aYMQPuDUUoFWR=@{^IPZnL8L6o4r4vFE39~sX` znE*qJwY4>&xzE{2n|2?@(?yNcrF5a}yg5b9!DQFQU4c~7082Wyk7$d$f`Ydkp={eP zy5G#~!2SD@SD`o0d!hzMi@+C;SNVythODPqrAnl&$1u9BrkNNK^ z@;ShVF3K@I-PeKRce=|Lw-R}c z%G4D)`FCR%fnK{&WL1l4k#NeQjl7Zzo<3QYESWO;xM|i=_8YBaU*=tTNkV>dVu3P{ zf&)QWue;xcjsv)3HmqFifhO4ZueWT%2NnB+D{-%`x5bVxbbD*NbMcy&^%`3urcaxJRbgY(C)ELYfM%;gbhx($JD_)7G zIlS|}wbE@M7@L1KcP=V`KyG1xQL8Ga2uhG5ljR9Ahsq@ zqf1_NnTc$du4Gsx*W5fPw7_avg`WqGlH$kJ+UOJdSbD7bch=&yv6gc z+eDNcdVuBD_Lxwv$d>$}fG%+oFXXM@5%r~w-La%VHi*fpbNc>rN`;*Fgs@0Z>77X< z&YCH+{>5S&j0>|+)!_}v4p~1!_JN&P68W_QByr9DCLnXX_B{Q2=^lRZbo=>p*7M8E z-3B|txGsT1p3QGB%T@^IUh{soaKJ!FpAd012&slsI*U;gf1!qfExc>TRI0Mc|4$hB zT5CzvY$yX&M;-3U*a9gxMKrOkDeVOlq=5p73@|kFecPb}c6PsFR??q?9Z}qWA6O~> zvDDqHGy56sh&|ApQ^!8L7H0~e64k{St9=Dp!_*Uu<7;>TmHhk7(gzjbl5HofqMov` zZRy(f(|?vLZpBZKhr>@kH&AQVe~&AHYChHE9%=LX$|BI4n_MPi4M8N^io-C@cUL5W zMJ`IL2p7nygq@qz{v!d*6I*cpNuugBMug|Qv9@hgWS1B2AadF!+AELS;yCzBwk6AhmP8vVdFQ@=}O1H(CCg{a*{h0 z|7@1lJ#x|glY2#6Oyd>w$HbJXEd9Vq)-9bMog(TXe|hDpxsRHDqydz^d`>^kUE zoFY@OTBOF(dVXOX+(QMgcUkpEAtAb9Vo=9|LjR6e)Q=-bacDV=rUOJU{IgRIBEZ}b zlQ|n2xpk|JtE>Z8vRAfr0fiGN0Xn>ctGj8y$|I0zm*m?W$b8uO)@b=@I6u$`Y@CFQ}Nnk<$k|hu>=n z{18!r-HaR(L;BA0?|)}_TH9N`Ke_roP5J8osQo@)>kae_>HTI~_7r&E66M2)X~s9w zXdE*61DSk~)2?7PZn{~Aj@GYf#p>fq7vt5Vnu)mN#zNbi;g{q_J2Q>zg~pWmk;{~s z`EBdL(JMFrT$dw_nE@B*xi8lWg_<7%slW`E>9_UR2)6)-Airufc0?miE^zvNEUk=q zwdd^Re|ZV?W`Dlk-N}9csiW(ceWe@O`mExgat%~;D-qLauL9Y5cv_|`2t{+e7Fle6 z1zq?Zs|T%B&@+r(iifm18a6ZzHfVU(uIwE0#MTC;=C7PxonAS(Kf8H)3iR@CI@uv@ zHuI-PL(O;Btmr_!qNYPpif_=*{3=@*b?=sdSslkFuOn=Wy-HgD&2q8-|20Qv^|$Bo zT5o0Je|DdKImRqzluE>2_^MJ^)Cq#L=gm3QULxMur@ z@hDAK*00~Gsjen;NafGWz%<94O87xNi{Rcg%ES?^T62b_*zgsqxfWKQe@nr!erpu7 zq-6XmO!~sSGzOuX42pe73A>m(7;>FXT+RN~r5Y1zmV*(#n+}Yh!QYZu_=Jdb>IloC zV>|8kknoK(2-Nuoz6F3=`F_+Wnxq^)isz04!F89KztPpP*Z>@Cl<1!a;t~L@i8bzZ z7CcEygKWYPLGVcj$bM6N4oAg|A7qiuE<_Q7efV+Hs6!Nedb)9qBCMqqrcH&`ELL5I zmNIo~g-XyPa={tgK11tf0b?f`7E+A!OR~RxOV=NT_fl?hZ@6;)6_EaZE<-}g*7Ni8 z4Lc8if!=|Efq>Q4T;SVBJI>w${k<6=-RGm7$LF?RH#v&~Jn{8{%&fj}Om%j8mExh1 z>AdSMKJrNLDLU`9J~wW>_I9x_M=bB-NQ9Vc9cC}F*Y8t)qt@xcJ$}i8+5dWaoPB(} z;5l}^$UU*`2IA3Nv%V9(4RUw@Z6KDBNyB-s+l*mmL8(S&c8ofty7(f5RG}IGP887H_wjfR{1hMe z=ckwFi0k*$uX*8H88H9*(edk=?5_o9??87q@Z6@txj41JuF=+N21hPe zRa~U#y;KKbE3rBG8x-$pzgFfFm&QyF<*rgavZL?Jrzm0gwRUmpyIN=(KqUSBXJ-(h z^L*pN$HV=8`Up_6D87R4hCkU1U?%E}P^CSofu+CZ_y#xH-YF+d=S@MM7GU|koDK=q zk&3u`uz_rx=h;z`Y^~6bI7ydL<0a6ojDi;PD|{zpSv7=OWb}p^biu*P&IRV7=x3_< z5OJcX!)*J=z+{x3YftR?L@J*H(vb&Ea^m4nRnAK7{2Mch#uJ0cg&EywdMJd-#lPFE zWnf^f-c5{C`LX(~A^#NB|E(ye6easNK@}b8DQ(%9cNP)4D5%7jR0-Eq^p4Z25zDOj zC{dp&aLrS!%R2F?f(-hCR3_X+km)_$l*eF%CB;7V&jN;e-h~D&F@uOvqZx=%cW(bn_ik@LxXMl)0!owmnd2Z%r9A0 zf^rczplr5^(KyXKF1NxbDVYUF>(obSD&sjbQ|SBkeD>n)ZM@U5(0fwj>vUv~{bR*7 z|2{JPmLSoHHJfO@9VvCM2CqH65odE}=gfz*_w^|8+xyk2W!oUd*p|$+rEziqY_(~;--CJM29C` zty}Bh_kO;#2H0zPXS%yC719?F_`JMZOlF3Z#rhKq#6+|)u3(AxjS^=K)}uyf8fUhs ze`!UqrvMRPl(9>&)eze`n1On_V6o%#G9$r?Ku<(d3r3g-^H@d^UUNSE*Ywj}qE4zb z(};!R9#+J89*Ia(D1eyUC3o>2mVaqDP+TeuKT zfmGdX3P>~O!c@&N&D^(5luf9juN=+RaThR;2g^~ex$F71Z%={HqpzgEY=ZlDXeKzk z6_W)iKc$lsuZk8aQh_j?1>*p#S{=I|CiQr+!JvZEr{21N-q)>U&CTj8NLoA3vdZ+m}F|JS=C!#u+)B)*XXIB-yAo1AEq{17qIB%&T^ zHTha2A$2eMr#&QF<@bB-)y$)x`yBIpqMi8KBA!% z^LcRUcV^^&gXVABEj@lO%(2TB3F{GSmc1ap+CY{b4wBtT>JZjM-wX1LYcZO=T8avR zuS{evCPx9J>-{|EAu1UBZu@e#6F7I=yLw~@se8!m0dcUGJ)-l~?bs>peMO{eYTqxQ zB6EXL)@n3byGY!!_S9nH)a@_AV0R@ah9W3I^$j{DnbFiUpa&R?OoO)*tZ1S9{;;3T zSGjB%P&QU)uHddA)v`fu`Gt)_!K2c@uMz1WcUlE;LC|Cf6&vf7eaxJV=fj%hC57(n zCT!uNYy$dM&cH$*CXawKOnjg_^I2}ry>wn)5&cjMqDQ^0(tG=g0#uT@A2Z!dyl?ij zIK2nd#2ASsMzrOVQ?Z)|K0SoKcgaOdQ5=8RPArHLsyzwRcpa3lZ<`k+<#)CLOco_} zi*?LDm@e_9qY#6o@LO*}SyHf+4Qa7f=%VyL6-&cf#fH~w({1QK$cme|>Ti0nl4H-o z+Odt8j%lzuw!%j{9Xp?&EvcS6J1s4N#K%DCe16_@xc;UAzFJyZ?h)iCmo%-PFSo8) zNJ(tGs2-6ftePlFc|5XmH;G22;BSNcQEYNM1iNI%2dyu|bS~FygE^c@pn;X0 z8Ys~>I0!_y_gMvYZ28UicHo_uy0!KG4m{ttwo3nNZ;eiFTy9KW9y~oAK7D;1eg!zY z1on0bXm1x>!2fV?-KST`2_N0KOmJ<%#>|-&H}-Aj?iJGA-V5aG88oDyQ*gMxc64-9 z#7?9(P2ptY0YN-185vco_X0FGcs{a+c_A6K<{i?&ZIT#~<*1(0%s5fRA6gYd$k*j^ z7D}vx5uHyzpy(SIe7&t1CRhT)hT2X~!)uL!+u!$hyOXEKNMF^QJRAWpgS+wyRJpn1 zwL}363D+8q*9nclSnA>pXySeTv)1T}PoO)ir;wOd)eC4vvF6dxiX!k6WJe z8$kByPlm3c@FaGg0prc>DD9%IUlq!`XtfW!%MUr}J~I-Wgw2Gz1`Bg)$PhhIA+ERL z2kuURdR_hfLcO0)mtS97ohO0>dr@blkWLi#ny9ZsVStFE}>s8Wt@8W zPv{&A{XV>BYTD1_l>_+cyxS)0UROWIKjveJXRoJ)pTM%k=TnpopF$&z*db zPb5#Ng_&4XJJCqS^ayX<+}1T@#X6zqbF8P;*^({AMMkgwh72=qF+!(F>x1k@;}tgF z(~#O>OxZJ&GbbvK&p;bJ5a}Xt(7LLfwWHJ2Vu2DZhT&v9zbg#9Z5kVp=F(u~bY)V< zGYHoFh&%MQbW(HOe7zDegX1sj%qhoGv*wE{^HL+ANTNM{E_LJI6`pQe(+y}lGaJkz zz6*I>Z+Om}4%jqITxuKCTdn_M6(5Zcpe*~e%MVky)C=w^esVA3M2Bie=@@^Vr)^GE z)Semf9=cCJqk^GIL62(!li=WVaWFTQD?w?Me~^LTZ9Dw(%72mokXJL75?b4rS@U)r z+Qq?cqX9{R`a^vL>~zl!$|^Q|1aKL)lsn|YZ&NN-mXw9$GpD~m86Hx_c;%*w$pi5% zKM_>)WNnemwOMA~v;(*2$4KcObdCxrirLRAvzcowZBup=g$CMMoT=kdyRx(y^%V)s z^K{rZ&~~|IrOK7V8D!<$6!hS6l{7Z6iP6<}xIE(ReDiGruiedY@1%)z1SSCP8i73P zx`L>gnVEZEx8sHd7F_=+#M$ZT)%niO(pvAznSyRT<(Q58$I{u=mBZuXf0vhTA1^FC z?jNR!zMl$#w-yp*PPa%W&hD?yXEBMJM`NU*SU+cYAauZ7OsQMhGkJCE} zeZI)8R~b1yex$?OX(p%klx|UZ{gW=&g|VIPObyVw1v{(fpq2hY3MW5cen%{KR)?m9K#$Ic!CsYrSdtUOExG z#FE|enja3fA%?sy)2WM>Nm`shf;6^L4c4c2O$p}MF+;+>Bk|XyBk>-A#w<-{&AF8Q zH;g+igdV1I6eJQ`@2oX=PqYcxSgy!{2o!eTZb2dLxA{b&>BjXFgyGBcXnW>ak0j`wnkD?~F zc--&`U4AiaDK5uz;Aa)h-15-x^%NOb)026uM}a9rJdc`7dG*SWU2b&yb{ak?Ac4E`G3>4BhDbV`a&un?^W5fzAc z$1yOLfa_5K*Ps^njHk6vPKWeZ5OsG>SxFLk^u?DU#DAf?vVF~V;~z$Jn5Z*a!^xuI z`C93uoMUE{nN-yLCWg#ex6FC8(fPHBM)cF|dXhIU9036FHXN9B`U_~W?fH}@wUb1s z#6&zpU{p8w`N4`E;uT^Uk#3R6`A_}NY(80uzQIk7Yi!mfd{k05R1)}w?sY-RU_;hZxTCn48Y+(^> zy>b;9<+AA#u;eKzF0_s+Y&7Wq{`o0zRz;hS*O+;bu@O6#Eyc8iGHxly4-Y;2s80ZEwDQssGOxe#;Mkd>;VRw)OuqQTh)d@xWGw%P zx49x?VtU=ADpob+3o9X$!F`cx6Fun&G%;y@(qR-fwaJvBob`~Z1 z*Iy*dEc8GwG}ev9_Hy}}I(#8c`Rj@Lx1|Crmm zsun(tV9Flz2ue636fGuI%?OTVAEo?X5Rnl|uAsVFVIL}2Jg{IQGG>`5Yy2@^9^VBO zwrLSmG}S`P`YhB{Wh9n;$RdvDm7M$ond5spH$2Zp)llwJs;m{fWn9?lTs>{HTf(iGfX zSTp>J4c`vEq@BgMVV{iGLT1Oow*=ejFsqge_K+X7iL|Ne@8R+i(q|;fG$i~_arnQ0 zn)7~7^z|^kR#^*7%>xCPNOO1fcXf6Bc+3>~ylo78e{Y|PO-{KB4ODz-{Zb2F+LSVb1GP-VLiba!ugA1HVNN`6iX@^ zD$_G_`Wfu4<=7kV?1s#y43&FMOA5{@Mu2t?Fu+B4jYmM1tV%84UJ&@lY1*diT$*dEdVg6p*ATs~nktq`kXi0Yv* zSWcnJgc%tSU`B}zqu04_PvopBkt6=qSaa7ClwGd2-on0qm^v<)j!b7xY$hXbH?|j* zDpjaA--nyma&Lii2ETCdG?pr<*B~(w-lFWnG%^0yqs%i%5QCmFEY`vUVl9`ZYoYRs z1dR+$;$c$5m}?%&DU+f(UEO5<=#JL@7^B1K&ryZ>uZtcCr%Khlfo*4s1YX@N3=pBC zn$iKVJHI3G8T1Hpq)I7bACQ9PWcBF2QYae4-+UvJmmS&z&g$LwpUt29oY*kyZwYAe>8$v>~6Xb(fbj|%?a+PyJYph?#5QFwP0y9h$y0X_WH z)am>+Hm0pnQIVovChr>4pT+2VSGB9-rd->6y~q<+^J*V`H^qRHBN>F_(2fTQ3CF9v zRvyOUEE_@LBXjGqQy>vMJuXjOD`gRylGMa8FnH0sRH*#+@*)-J+tmjYzZZ|yz>mi@ z!|$)>!S7F^?-yV@(BE(S?e3`ia@zi_OD|xA_qzaQ-mr##5&~j=Jd3hU5UX>ehoQJNNzrt8* zs5YWRMdGo@Bdg7@MpFCRoo8b>apOiuF-QLdbsdEn{Tz~DzY(U*b+g=LgkZ&^B<)m9 zecq1HBQ6P6W5D>U(W+CTml`gxNEX5A4i+YiiD>8hvrqnH`Te%Go#M}UhXKvT6Mo%S=+r)45H4&j^!aML7Xq1@1 zV^3}NejvPM2tITT`>N1m%}<^HQh>z$9mv{q=?6(q$CbTOLwYti|3QVCieL%O!fpKn z1|j#|IfTGd%amfYO-xSV=OqVBnBDbEd@MErtGNOCs(V0;RnCv){e$~+7n5l<(HC#r zAsWo4758Yk^9%NE%*gww1Y5JMd@%R<$>en9>}LasLkYbg*fsX%o(h^xG|97F>8f?( zF#EVXZV*=#Fj7_5sdDvL5MJ?p z_I<$%(>`&rDq*wyb1dd+Tf&zj`$0TeP>vUCOSsq&(98~)L+8R&Hgb0r?= zMzvbHTSNNv+ZA|iPBY9c(&N9lqYa}Du^|9SOpe!`?}>?tr4-)Zm(%yPu_wA;uLIwA zmx6kNjF~joHx|`CUd4Ay$vaAjQ}50o6f^V;J**ZC>>fegv+dU5m@)3FVs=8Nwh)9J z64K1&DAym7N{WJ9&iQT$O(w@Fe#PNi%$6yzI;A7V(~u$9Op?9Ee7n#u?h`GqsKNBh zzO6zYhMB%ZhM42=xQfeXUpp8jpKi%!#{ml~BaIXo~dBLO=2ye1XbjI$#~ z#7(r)7CJ}nVRXB!$s8MWms6jmdCP~`{K=iEHE(nU?*35_1n!cDMGgX z+yh}Sl4>e3wZ(>HTRHVJ+-l;&`q#D^x7Hud)C(5wMGvd|H$dTwsOc4Hbc(gAGbWUf z>E{e;A(~0BR9T`C-|5BLr^1yQH~ZF&Qn(6VSoE8Id@W3{oE-c0i^F2~KHXj;*h z>f4p-5+$_9h~lsK}`kH#Vw@}Bd3KO7%k@1&1z*r4*@s=Z!j8vIA)n)X?TM}^f zf_-p!jeFu^_*Wo3P@|=*T8xF$CQURtB${co2uHfCq}^Ct^yESdb`gSL4|Y4W>miCJ zWY5LuMfV|jK`6GBb;*xz8ol|i)-tD$?rv|LyZ!Vmrp-I_1c8!}X^nmbps~u9hBeJm zr&g>V9iEQYs9xQeXvJwYOfVcj;=uf&AWKOL$1o8!3qh4w>5H8N1n(p^88J%}w`j2G zv6!wf)~;#=vcHA)ZNyFl;Rkm$DJ+>A79p9m=}F*~#R3phUNtVdw?H6KP}*j(OjBwt zv*VFibS>KmzCGu zURh-mqTeLs_%uICkkpysn}WgycN_OFYU9$OH?Qs&k(P9vLv0CLrkFHc@i5krV6Ig7 zY9HI{(!i|4bjlzal_Sm$`-fv|+j}&Qpg_pcH$*@`dFk)lr%!s){XX_uhJoEsqp8fS zD|-9`cm*lrM%Hy$CYm+6AiUD(3L zr2MQ$nJ?2_D6Xy=n{`|aCl~RXhDyjQ85jb=^zo4v542?H@d=s4=3SHQ2t{UMC94ex z+A=1E1UGkL%ClI$A5#qFCA%(0gfVveOm{FYQii~xtM(}}8@p2=QhwP+r6vVt)DRZJW z5=_aNvq72H1!R(8hA~1-a%Z?<8tS8zF?hDk&)V^WkaY1`f@P?z2G*%u8?}Uv>Y{ch z^)OtHRIlh#UgNv^IRk8HBT7n#_+iELI&9YMn@~WS5>tq=*0Qlia*TeW zjp3;Xj-3?l8d)lzKgIw8*eg2oat0*h**5vvmVB60Mmr8Dz*p{v+o@@IC{WR>)JnU| zV^LOOs3D9Zaki8XkIju}jT`BkRZ%BFi4B^BGBYuAFPeeRZ6eani;aON!d1NvfYOiT zXjR#DEv-U3v=gafyEacoSeP$&cJ{sAre9`GkQ%KGVyt^^b@NuJ1*jb#@`f{jY>Hzc zJ3+)2ffKlzPQv+T-|?DC!Ru7~&<<EdqO6O8S z%uJvO3;9qK;%N)NT|(T5&z`O=4< zmCjJ}yS1zuoux$AR|Q|QsFMwyJ7?5uZCZFqUHiVS9c5a#fgRylKC9ROSWHs{gtXrt zu#$}xps)xHqtb#kaRQN7GG3MbIn_IKK@D%}gU602?MQIhaDFYXTh7L%NW?v&!9b{M zrb|C1$CP3QUCB6!PpI}`7{kW$d^S!mGtl^kcI{c?5*h)T9FMrz^c;3S0&p;1!ngEb; zi;@|FbR#EOUu_Wf_}#;XTmNKO#eg5Wct0C z<}UMVkHs1|o$f38Qye^I&yg1FN{%{q%+jvIcs)xd+NI{M=ozj8<>QCOzz8%NW-GIP z9mqmnb@`ploJIL%7KY*t@2B)tA4L={xYF87TVKIgZFj96 zMSXsKq`~A|S%kHk-c~i2%2U5)maV}{S)9vBKHZ^3mWnFYf{m~=5N_^f5rhw`J)fHX z4lcgmO^MZ*Yz|R{+8mKbW*vYVx*h0}l7^eymY7zrm+Sa+aYkuo1-V%mZ9u~lko{uu zG+qB3U3&^!lDS>p%G30Bxn;LBX>Rw5?340Le(zt(gw7Ycc@rVmeVrys)lp<-3bh!kfrY-6bzp8JlJ;=*Knj@07Ad)p^j|BVEw-h>o-KwMjoz$%#TrI~O|43H7acNh4yPf)NT5!OAbW4wH zmI}1UWjkz2b_s%h>FZD5V7R$s6P7+UCm$s_4oZQ#4or2)9WUF@xvnY0arxJaBssU2 z!{dL{O3oY=Kd84L#a=HA)k+!frCAinD6lZ}OPZ`~n%va+?SPzxG$w!u{vie|TxI`t zp>o<%KUic03?KJXm?=*Ujk5Dccb#UM@0%k`af};(POZGbkLz7PL;Rx{!jD2&mb_#T zT=e1dQPX@aR3*?(>n)cTmD$JEvlJa{cKhpK8=2UAjyOJQ8`JSth`wl7i>?>Ita2He z=N5EPm99%?twers*I@#6X6%S@F27o7Q!X~(0GYn>s3n1yt=IC;RF&zCl*radhG4Mj zs+#~YIWSTY_Z7Jw-5iEs(`XXDopO~8LTm_0*b)RUeKOlHDo}m7EgWWJ)z#2u>EPMz z@~138!#xYun;Iz=3xO&Y%k)hHroxKdj#mb7S^cA$R#J%A76$TxBeQ!ufPDej=!MLF z-zY+ly6T1HYZup_P${&0(rgBq1gdmvwR8cq%q{wy5^rXzMBcJYi0Zrlkee#k0$0Vn zrK$;IoOkS|Oye`*w zrd<<*4y@)pTmtMdG}7-2?BmBn{BhLoH5wwc0OhLGEdjwXyZR)b!x^@gWonj4s`8xUoAAMF*Fi# zgP<$|{|S>!^9y!C%$}CCVpg8bl*Ezng5TdKjQrU9jB9@;iqNQ%OzkjTU!&XXakAs|e3!R_azrXHUFsH{oL^~0-f&}o zIevP{xnm@~Ju^GcZtZSKWD(!!BP(yzXXf}*oYoUWDV)ZhccBQYO^*d#;80dFPMKZl z4M@|!QkpV7DYnDIbav6Fd9F$@&{1Ds+ezKMtx;R_@b1Mp{uVf3Y9iOyuSysZcOtya0X*tU(-6HHlm;1BnpZf-B;8ubG?XYT9 z(@n{AacSn^^{mD`v`EaEQl^LMX*HQ25VDOBJPS9pG}ihlTkq<@=pVQG`D8}G=r3{w zW32^F@Ocky@0NF#ZaG1jr)(x&OgPenjO@d6+|;F>cfQvcw+T)N>#8Ykw(mf@t}(W1 zUIk`vR~%zr?Hi0S=4HVgZsZjN=4gS;1}2poYr<4=M4| z9zRD-JlKowt?}KfnB6EF5LHm0Pa+Bdm6yaJvFg!CH%Gzm4(sTIP~z$v`S}^DRkEPT z%sW*~EziZ`T+mxyN;M19YS~G%eR86nuNc8zWbd9%~UI+Y+kRQVos$7_o=$evQGpcfQ*c1#v&d=gM2or%9eOtYCke9Zo$9 zXZ;knjq<4Tz1J+%gBPW08E9XpneV6@AcQ}X_E}!FA6s_!yo<^@rhPMYj%SKvtNr>i z33eZGKPilh*qQkZ{w2Rxbza2CAl1Q1LGzlwd}Ze$hbBH z5u}apZQ!GLwFF!!Y&Xoh8pJTW8D@td_ik5`L=)0|!D?Aa(=@e(YcLVpYn)zsG$4+G zD8`FcTW>A^{rPO!a#M%%yw@nL;wRN^Rm#YB#2sTeAzuPXK zz(dkoycl>c(f4~AR`4?p&bhqbYmt%FxW1V`Kr$gQx-#J^TD%aF?S2&;zqv<}Elsy6 z)XfEpLy3dZ;O#8gmg&m&S`1&Xw5i+ertx)v7yi6}TRIH8r%NSHYBXdmoPJzy+Vqvj z55BPS(bONJmmz&I2*XgCi8p=0sp$pr?bd-iJtgbU<`2vwcZPa37=~Z{YF|5PmTGPi ztZ81yESzW(Vbk67jL}B|{M#IL;(1;;reNTGt2x|1!{>Irnagd^?Ga$3g;EfmlZ;XKiHl2#*5*Xv)_Y=-xT+l zn;C^u%Wua?Qv@6A2AcBi#6x9DTJ3dw;J)UZl>Sv+kCM%Uq@F zp!LExXsw0g1826~^sosNbDAm-g&+R}yu!P`kzcx_FSzUX!u=fRa>-x`j9g~cwceIe zl-VE}f4?#JR_cuBzD(-EkYi4lz^sf6k;G@Gq)2qcZh${rR-0iR8t;?)@*6^) zJzX&X!q%<&R4#aB8qT+!H8n>nJ~yO3m3%{(~>WfA={4G z@CcqRjb_}f{*H!V4tO+2$;F`KCoHU0{N`5N>(ApbOzr79LwHugTb5rBW_6ZZN?pr- z_BqvkeY3V7UY|Gcz7w&#VOlgE|8#@03NQ*j78p&y4$CyCV?qQO_tNu1z0$fkj(Gkujv?F0J6K8c(--+Xengfu{S2| zAQJb>P4PBp6-7*dxhk9^>1vSS3x2~}T&l-b3sDjnMIwnyP{ozL;3pyrCctJ?mgcnvWWzBSnb9K&tOC;z#fYTimm*F_FoiyX9;Rx61^Bi?L2 zv!wE%KWyuFy3VlL)vOXeSb>_c}UAl4a(<$+Rd~dSN0{-*O|fWHbm1~ zxI)f!ag|4IrMylhn?u*yC`_hkZ5z_go_;8T99q-wOx5WbT9WS9`Gc;DYG1g_IyD*w z1DkrM@KaRBtm($Z455q(6FX&cz?{_OqG45>Q#s@1gKFY0Vg3SAo(#6z3s=i*q%t?t z+n=?HZ5BcGT5mbPbeo*16}50(H!A*0I$hs_!C)k{o75Qc?UVM}X`YGG$K&d2Zt7X& zjB7iqm($#~v`x(8kgU)!Nx>a%UBk;Ub*uZ;_+hK!wwba}lWEg$K(m<&q1x(Bm^Kp4 zr?Um1VCDuMU%a(2FFQ$lFHc$Fb#*88s@Rr@kXlXbCrCl+Ta_Yz%iBYvW6TC^zkN3^ zo^Cpe%4Qqm^Ane;WS!R$e1CAN3N^S>1Pv@jiBJXld3%#f)pyV!ko|0Vh)YQ^h<1y? z80mjXN%<+jew#j0lYc4|h)?BX4gsZ)hfFZ>A2urA@%D+Z#~hNx3B$m(=&xhx7azg~ ztvu};tyGJx;fnAp>IOnlsrWEkrK{966PX~kaxw9SftkHd;@n_E?P$HNf2ECdxY zv%av=%Q*;uKtDQo`N2ZL{6qDKUbe%kY_K`1zWem-+6d)tW!TEjB-P~GoLv)hZ(PzY zcp8tu!{D2)f}?TH#?#XcosnCVPRxG%6UfYd`_79i)yYcb-7Jm9-P(1Vai!_-G6l{9 zW)EKfZ9jdfWS#(O3J_uSD^_E-JR;{Llu_AS1y+B#x>mg`Y%c~pzs1Yab^&_`VW75A z`1Sd$aky?(U2Rf?k?s2327E>|-!X6$vOiw7QJb5q&*o}sVQ$@VXTvBKd?kG;nkUd} z43}dlUs-%G$1~s-fZGg1;p zcMDV71Dhq|jn1^AeP@#(y~#!2Tt5((M*s-!{T(AkmV27h}Z9hCN=Oqk7@Z_jWT zgtaI=s!HJ?_grbiw7)pl%f`%fbz^9sm1)q@k0%>liECW4oLoBBvoCfshMwCOBkpP^ zuIiQgUngOFtfwY9)P|<+57Y9|UW*&uR(FPsj#^kXAX#Z16V27;o1x$k2Ue=YS>D=A z?uBA^$)C-lucM6z;pG$mpRR?l9Bh`>M~Cw=`ZoPCc9yu!C8A1PTB6*mRLQsrl|dVY zZXU1sp<48LuI@t?`R zWjT57LX(g1qTuRlFkb0bp5BmIvf<{>!9n74MiY8Zb2&N}3FK~?O-|vTl1mRR7oDYz zLghITc`yue>vVU(lUVU7F)Qn};^u6+^cEo!h8j^a5W4^HH&1#-jr`=k%ADEOU+~% zPdifr%v{bc>`ilQi=8?V#WfI;-K=THP`;T-Z-zepEmcX0$%}ry&MTTbr#gs!PA)Xx ztb=+3Qsu$Cuh_l-+XgKTJQ+P+AfHH`%&@hh6iRaarrFyfJ~{M^tF2W*6PZA*2j`h( zmBJXYFDc^=gIlLs6-0Q7MUrQ%C$K1j!<)IWg+*KaBH4?1OIaaq^{dY=s-X`jb5Sw9 zY+!W1HV7>uip3x+alJe<`-6LCr6$3IKJXsW6zi{*=s*gD^%!0Ff-l=(n&~j99uJ?# z-I&UnHLivA)OVOLeZ8YjC=LAam%dSy-Z74qx4U{->o>Ap!!OJA(Zo7+8?F`@Av*0JlIp-U;p2v3OI*o`5&3QDj&r~G%Y2wo5n z!!5xq^}!Pj7YZ8$u&lF`w-5sJ2jyjQvLt=b4>ZJ6ft1T#VQV3kMVxfv*^C+Dxt^l6 zIHY;Y#-gK!zK9u3p^Y29@x&Nw8x!qP?j0DKzr_4*}&EscotKuBEmM8%?bf@V`G zw>w$R!sG5ml9K3a>nYDj+r!Wp0h=`Ii=W5~Q;sHoSFe}LejG7-22otw)@C+}vVdRtsp=+s*x}bnX6Oc3lvwJ>BuRNO*u`%Rbx@uFEsW=x=ghQ;X z*{1yW(H(wkBweRu=>Q6&*wbwg3LsM*|J@cLDkms_h@UQtVq0d*gXWz)IpAaQN$05v*n%&W=01`|u z*?qW|oO-MdU&G5?>B-f;N#vtK4)H08Gd&O#2dnB1c&*+25ihFF(=~M}f9isJ2yUzr z{%C9%^r8q?bQ?6i4y_sCqlfi!YMdRoKowyw2f?Z`6N>1_+Tk)@(#p6@S5m^yM?aJ% zUB|`ASLaB;DnOol9gqX|oyh)mi9tp3`6ePsM_07Q+H!W8CnnoVcuS#ci@^vcIe17 zy+7xnQW0W0p?8MrX_l9XfW_iQ?PtYqgfxFF;^1q%2!508Do=me(Sk5x63N;6!@Udl z=TqiVyYUb<^uhEls~^5*-_fcrcGuf({_9Ab|Ae)o(FBLy`T~B_b>aPD^1xzPn*G|O zpnJC4);0Mf@0dRXhyn*_${TNG$V+PK7kQ^xv5dSN1ie{3cZ!_gC9Smb z2MxnT~wEe`8xykip$xpf;)`+&-M2FpJHL28GKH+ggguhxNOxeuG{Je@ciQLN%gXDnD{)FJkbeKP{YQznxpP;-j} zB;2y>JVq!lPbCdtkt%YCn@(RnIjc#hj}G=8m1;G`)pO^ejX6?$6&*aGow(zueGY~{ zn(x^?FA&?kR%<9eKFk^U^XvI;1I0MyO?3Ua(I^fqCK~`rZ8pM4KDtOp8ar{f6^f_$ zE#h*sQvsFBcD;CKy6)^a82aaMdh+V=c6+J=b9_D2@kMHZXnrlu=Wi!dZAEwaB#FiJ z#ANxS>vp7>rIDng70cQ$KBun6jwbr?v+wX!R9dqY)1~F%DJ*)Ja^{Dj(_WcH|Cwf&T?8%`yvFsRB&Q|A z5V?w4*0PQxQibNBR%&Zo?{w5%IY=&rGGh2skesDZCBDa)-WIovb zxH-`C!oV|n3{E1lhI@cUN6RQNLWzN*Uu-S)6{Dz(?h)TUAo<$K5I z&PaXz?$%LEr~9R~z`T71DhK$2B3*h>0mC%EwmA6}|aaD8UEYRKFyn{G9+c4YTx-0l$8H zGhv%x$IRB_D68{7OOhJT0~>B|-{!dZz&=On5Az(X0X=#=96oI$@~Fs|L+s^zGF^@Y zMZ9a7b_g?sJeHpvY^?(Q8EyIOe(PWu0x-C^Vffy|Ynlqykz72(Lr%D_@_(bMzRViZ zhil!lL$2bI0<(fY<+oy5neuk9eLGZ^T;jIPkHL8VyEYUqE!^A?h|!$;)*XzCM4pcj zx%Aw%jXE_(C0w+oOPV0lc1WMLY;JhhPcsfsSilYQ8X<;=ZZ>`g4K!W%X}e$2TU*q52l89Avn4)H`z<}Y>nm6}cg z>9J=dN-x@ITYuhO_wNg_7VDlnFJqWAor2{mk-DR@yPy1{gR*}Sl#07b-|)f_m#2jc z5MD6ARPMh(1~`pR*NaSFCnq9hM5pxl>fWwVl?nD{aY`IHTqMJ4P)@T^XkA5PI%x)d zmUYZ|8uKz0k?dvGH95zBRPa*&9|axr<}21B6nE_kuo(K?VKqF0RI$%CeV%p3$wks_m})CJ&-o3zYNWQT3U%UR%v(UR;$x zhFQUSQ%^QGZhzLVL0r}3C?X0VJUY{NxYBt(vtx{R6G_j-a?WAGsaQ_|AiAt?-ZVUH zWWzi9I#-*Yq)K9KTVg}FalNdo>BAeDfF^$0fEKYF9|?Rc6m^P362lF5ES9c(5PRY+aX zsq`Fm!4^pJ#A8DIcEN76ok>0FD4*tXmad1v?DfeDIRlgu{O zabH<}UePD~Rtcd#u5EmB%iYX)nSXG9@z32(dJhr`TmCEjQ16YbZ)c>es?|iW;1qw>Lkt6~C)x zbLhsNS~BK!BX>=~cE+e9p6v)5{ujpL3iYQ+SlL&NjF7VFzVkiwXl|OnC z|4Pmtc_D56HF&PnC64)dd!?`RT;e$9V=kVrhx^J5#bBeR(#4+6#fkA=KDbDkoil89QcS z98CElv$RO)Od-zrA51hw<)5;8R-|X*I$znQCnz&npX>Xxx6zk;kUsK*cCAg@i6t*x z>DrhI6F#qbv|4`K;opHA5&{CcG~`@f-nL8;ufotpIQh>61vqwPtHm?o4c& z-@ML$L|dTLFLus3LaX&9Zm_mIU4?Co_66y%mdDjvZ{y$FzC9?QVfB_yr0k9(?^=s{ zIoiaO{gQ1+)lso$)KM&Lxl%cJb;Q7>;^Hsvsju4pW4Im&MINkZt8nA5if>H8Fdn*>#2}1;fvn{6 z$$3465s`7gcO^C*%zzzFTQLg{Z%WN8C6YnzdBcz&jzz(4g8w_mVqFGtgNP{4ne2$W znEg)qxLm!Lwm8*V8%qDRLI(-t};P8J$b#RhWCVUlxMhHjk#x zWAEbwT;1W6?mKm)h23Ti%aK;GVa=IPJ!yVmy+%i!hFu-_;FsItFr>w~O1!o5hEK2k zqIc6#(m;1h2ig)>>ug8of6KOGgZh6X3yMz6_d5Axa#$DJ6@*w9L-~|6g^Jwtx-Ysx zQW0}V{t%gE>@kJDRFn$PQ1=lm>-%77G>Ne2gikAtJM1#Z*m3q93AJ)Hw=7g)L_k)i z1Awl1&ysV}%1?XbpBx>kDu{=*>6@$6#S0zl3^BCb{;J-52!hM&vkl!Qx+n`*tmfzS zm6U%=7AL!Ddv6mwC6@bK;R#e|yc=(Zocd5L`G3pcysv&LU4_Wth8wlguKIL>K-I9^ z48HNYn&wF~D(&oU@_wboX#5d&SmnqFT)pY>nwM(E+D(?-4ix#I={29wbzYYiYmPb}VMT6G0UIaret=R3Zn|D-Fow z5V|Ev9&D#YUvAmnWRH;m{dy&C2S#tx!l3Z`(MWJ~oKI_xA*MFMfiVAoO|I+*!uD z=~f`)_=Ts#O)a2(?DV9Al6dEWhZS)xPvNl94*7Dv*fKGHdTja6un^^s0(f>s_~_lJ zYt1GaA;MiW_d9xrS~Wg3q)aS^@5HcMA!yjeo&YU@?lx#8-jp*rqPub%|KuG6>tHv= z9o$)V`+5=)W^4UG3?SxRd!WDAh30B}$2ioo2{|Y`RN+&p3|;R4<*IReH0Do&mj$<2 z}?JSqI+k?vz8#9aa*ul#;BA`DrmUBx{DqgYn2qx8hvN^ojD4)DFH!Pcl#5qpSD z{Ig}e2-)?!r5opMOd+}G@7DuTM_8LRbhfi@&-lfOFUKI~T>HEmcrFLbUog^Gg@~so zoy%^dT4>1WuI3jj15d?v1VRY(A8~J89L9PY0zmwetBz)|lmWy8F>Mo=uA?Cd~K5l@~I2 z)|))OW{hwb(awOvWTv5GoGO%A@NTleKeqs_iIBYpLC+gN%@Rk*+0asYcmvZKfaqFo zV~jf9icBs1pnC>KL9i!ob^rUc*3>CzadU!Xq@+KUpNbKy>P4>fz98N!Mx${k;qMYY zg5jntADq%L(*s{n5j!*^cA!-TvfIuuj_O$T?1KsD#kQMK12*2+L&4q72la zdwS`w`6Ea+TNuJ(zt*w3n`@tkS9N`RMEFaE_XgBeoQjs6ID+bOxwXvA)IA)Jn4)0Q z)!GZ#lZ|1VV_0;^{8wIlGtwuu%^oy0)s^g-=7@d_H$wc0Z$j^H>xs%Vw5e9K`ZMVi zx7BT8;sP!NY9;yB-*9z_;J7P(s1<%d`la*lZvTk<6}gM7=L^#(gFCH>=PDi|Mn2%~ zz?(&7LZ=Kkshm>F9&5WCZIo31ymT(Wy-q z+f*NLgb>`<8_SZ5O4%)k`@yWE*rk;VM+V?>=Tmf1z4(TN@$F-L6mV9lNd$z*SbY}^ z;tF+OXBhI)qCRR=OPiS8`YHr_{Wj#3#y&DmRi~qS9{%RW8W!M#qOFa zp%r4PZZ=uvMo7kPz2!Ozx-s0t&bs7d)IGqK9WdXuU5nMKdn^kDFBZOcI1D9k`CF_pO;fh0FXRURJ_M;7FVb}3ZvMX-g{#5Vq+_BULf4V!r-SCMo zrpDLq(cr&BSZs1;R72Vx>{Dk6dlmD8`(*@7#~YQl5i6ZP1rP75@Oc<6k9--Lw1k<@ zqb$h0iA!r{8OhE3rvf~hM%o>iQ{@`1AltGb64QxhAnPu_vOOZD@p;n_Y*|ry+9}3c z;O;f&&?KXfXiz#|qo`f4&tKw?X_t!W0!R-3MK12A(*|frfr)Ng6uy$BS(EX<1+PTa z?0F^v!v`%|cO2l+-B8xBYCq0n;hP$&--|ql{HzSZ59my{&P!$elg$F|l(C5H#iF60 zkWIsj$W2*keeb6Y4C|o4%2ygc2)xxE_*N|)wLE%;`X`6{mvxmxWxT>jnr%N@jm zZWj9s>ND5zeN}g*TEqCv?6IzEGESVI(mqUKxZ--HXy|$+^&hWV#NAXiqAbfF0!AN@ zAN;ICP$!bPo>K`2hYNJq(9eb)Ze!{0LpE)h5K0!p%lv%OA~9Em5qL3T%rhq&;;F|a zr`*vHY<>mg`DcO&zSb2w*eH8z^21>qYGfmhePZM0xm#7cxk#2LXyFcuoyS((gpr@P z_czq3U*PTcYqW~iws&>T*rjWB;_HX4PQJ=TS+Xc^RmZ-Mw{qRZx{PdHumFBd8ZWhi zy$UKq`||a}@zOVe0DXCSDZ!%7Nr~B==5hpXvw$~y+J@QZMN0NYkv}4*xf8nj!l>b+ zR767^Zlxja6+?Np0`SM>v95x%ful`K#Vvb{v+Qo7B-F4$tRafow7_eT)Vi+|FQ!nH z?Nv^>mx|&GYtBDZ$5HGssqoDuxpuuCToieB8|aZ$>2Cqmf62&-7`k?^&(1D#{%K4- zJ+ofBu(tWC)>nV}b^*?FaoERgn80~nuCJp>v~WUXmK8|7X~5uZjZko)F4J*-t?S6( z^Vjee!9LZ?wjP)E4Uz@MUbw7i`7mTaM_*>hO@nIl*5%kvZyF0d3tsKGJ3U|Na#5Vs zFVN}9Sw#mQ?xnN^1K#Q=!9Q1q&GhE&T3QD|_l)yI5^<%;%CFGJIC^W_zxh z*H|{y3Tj@yCZMl+rtt@$<(#fl0*Ar2DX4hThTNlD2Fpqj0+2Ao(Jmd6F>#BEvg_7g z`Za45s@AjlSvsYM|MyzHmM?s7r2;P1$Y-O6S9LzZV<)w$T%L1T{=lW?zn=&{3E2pb z^kWJ>9PSvUzuPU2bFG@N8jB+>$`iG!K5wMGV(qN67JjeG79yHB+wkq7^q``|X3;}i zevv5}o{c;uewcYh@jgA7dn@zxlb;~QVq@8S)|VDXfkKC&INNR{ALCbZ-=uCn#-7N+ z;48SHc%a!?=qobnJh(1M!oi_M#EXRbI!l*cagMX%(>=o?@zL^-|sy<&-1>vQSdqv0itnN-}dR0fDti-uw{}M zRu@QwxMfmxF}bPNY}_#Lvk|E&PNG|_q5{@&r*%p$ze;hhWD3`g1;p7<2p9nnamY?G zqa^7{AyFlqjh*afH=tLUorzmKM$^YQgVRQupmKIX$ycgW>WHLsB$A<%r3RkSXs{_) zND8D6lUym19??Aj&SMvEW7GCYR-*}8O)fM%4l;)hIAt)SYAJ})P!-cp_MT}SW zF~I5DN+H`+0M!&h#z5O7H5(B#dg=fs>(Vp;C09-wHMyKwI5OQ!klY9q3z@diSqMix zO6bW)X`?mE#>BQ(tZ{plO|?f{c}_R;YX;I<+)KkV%0= zkSfW|gnRKgOJYuLGb$$)p}l=LIBCf!V9KC`ltx@+%FOT#C*sr=M>dIlC17r1j&aI}%)^r*yx!(9j*h%H=H!ST_s!KtLSNMMEDxY3qVMzV{$ z_p=hgFrop-$_oLbW(bBOnQ0=?ZO2~R%UrdawXsU6AmB|Ar5D}TNv&R5q#4i{Zf3aF zJ;TN*PF1i@ES5N=4-i7Dh@em+^C_yLa3Vry6LkWlC{j~|ii*TBY)67L@B5k^)pYQmF`ig>k3oIY_0jSPw_G9khF(bd+8l zoh4EwIhD1={C1JKIPGb4U3ixu!Au67OYruf*{q&4D8jI5*)23Fpla<}=OEcFr1PpF zAAouaX`vLO)KEnhnQ2j&^)_|JwFO!c>aSmSrv1#_rr4p6DsDPMgJ~%Xz;ra-Fh~ax zF=`|yUW=fdqbq>ID>aSOoKK?Ix~SnGofZx{HRXeb3bC15bsJbM+7)g$Es;7DPX#**4JF{`wrQ;L~wD3#9M%}&nF3ku%ioDPO03A+?Y zmuX6hvNqJ}Nm};)ME? zCqPP!zivcM@HUs8GUrB#>|jDF)hbl6c8`oU8g5Y=;cpK#T5h5_@FJ@T84Z*3BsNRY z?zAD(Epf^*aYQZ8;#l$Em@ZRX;!v1n5z&rSY9Iw>=}yl!4nRGP)R_id&2`BcN8RUz zOX^+}Q{Ae9q>j*t_ExS&a!4t5!DU|zaR&X6s~!SoMha-LCgvOGxIkn9z_cD#qh9rz zGmPoRU&qs})4YMFPP(*BXoLVTs@^M8-4po1h{ZUhQm; zXGYCVj2oFm20iQ0czKa*4KEj$)Cizl1R#_~1JPv!y9AU%y8>wJ!JeM96E!6QQV69X z`BHDRZQdo0QV$C(|5~6kxQyclO_rIOO5pJqHVv9o14nX+Z47tRYJ%2kN-ZOBNpwVZ ztM<$&=ZIc{EDJbZWHN54d@hcuIs?*76sgNxiqVvFEp3RoLPFCtcTo|`nG7Kqhd@Ru zr+1kt&JhSk6A|o16McIz%e4Fu-PYb? zjz$d;riGNM<7}doDZ64G!m)VNX@M+GL5)^w}D{vun2{N4%sPz z#!)??GO2WAir`J6Mo^nHB@i@nlu0f?qd{Y|%lt}3PLL{{0VU$7fxELZfuciK={fc< z#MCWhYBNGBbc&^Nt#+K9H5rCU(*@a}PJX$+uUjgL%5>b*(=F!W zstV4qdaM$4I$R7~0H-SC+muqrwpv=uQk@8n<7q>V&>bLlYBQQSL1N9)i=%qdrS2ID zB^K#(1_!oNz_0=xLAdK7MFGK0Vuu{aA73C-GFKluA2x@dE*O$_J}gKfLIO;7G{nFo z+6kA5F`zNVx-@dZEHu1D;{t^)j_^!QJjF9q9Z%9VyGchHt>%!y)08k{QkVAOR8>yW zRZdp$j{2TV{*tPt>BAgRrXLoBSnlGbY5vz!l@^*f0-9wmj&+CwA=*Qd&i$`(O3jg4 zn#8Cwns8+_OVds+tKhbcX0Xfjk!W`fy47k5pVY;Hsx`|r(P-0CXG=9oE|o2h4*n!F zvet=G^1p3+{V>uy&X|c^JELy_&qZ!QCZNF zlM7euEznYBd=%*k@gz+FyK|rFl)>8znZtVm*TAx9icZ_~>>|<=Z|%b^z|K>JPR5mO zr{29gdvJt~Mym;?t^r3I&5UjBj#iCsvvRoIDyPN9>3e*0NfLOho@^+Ijj^cC#*1Vj zlLd-V<*E<8PF&N{0#4IpF~L=crdS)(PU`+cH5zLiA5sXQKAm?;t@NTWRPmOqQmJgr ztqNKEenCe@5sg$DfL6xYZL+}C+!ra}0ShD>rbCoM!GHKnMX3$>|yA-u1b#sY{T>Id;8Rqd5is=$>v zAW#%`qIT=Rz=%#gy~qfCQkg}l#;@^;o6THqN|_{xlGF%EjM}N*v=~!C2{B$#40hmR zxssf&AqWMI>DghG$X3!DsuJ63!iiQx@)t?EI!(qHrAQ%lStboY7q&%rHm#?ytkI0n z(@vyyfmORnkU4qXtH&t<)Q$59f@W-xbCX=WFzhYdtzm-{!&EnbNN_#$V0Nymt;#|f zZ5A3{ot8Qe$%0#Ol4$G%bV^3X*2bkxY}r$VTBAfsxUD+x2O_A z0tXaUaJGq(N;cGo#Z0uv4a2EvHLfzs{F}Hq?hz%hm_}&hSd1T6jjnMGiiKV;sX3Q2 zo*C9j#l>Hj?O3CMccVqE@r$$IMmCZHIaeLG5#hB~!@;;gG0GXj2MDK;zOjxI1k-YuOfFv*TwTW(CioFScj5niaf-{#Olif?1Lhv*W_LM_ zYJC#l-cz^|%QiBEk)sHTAajWP0*|5GOh9N3l#n={AwVJ_N0JGZ29a3olFFWu?qMN* zO47nnY+=(%S}6-u2R-T}?2e6OXuFARoH#&m22os42I=uH2U|Lm%K~9mrO$hZ>V_^i zWVxo%=sl7}Xqvh>CqWY7$Qp$*y|*WtLK8xF+PaV9Ta>H;BK;mIW3&4DQuamlg#^20dyl6A2|< z*w|THi)Km%=btosg4D@e1UN`LroKnc!#3s1B#5-~6;((N4e3N1OY6qN<-S_0iT^>)tHz)V#~C1+E}t7;>abaI+o5b)ZR zLMj|xu8f8PUSQB<0Z$9~YkKkUFcYR7x`$o>IRi9un16lFDV-rwDQqK~*gZE&m9tni z6K6Wk;essSi-%GCZDa#7YEq6k3kJN6M{Y~G?e#@{Q>@U)=xZ(_UwZ0)#>6zgmcv{3J7&dqw)pDCGAZ% zl{WAaPPFbhC()ZxM2fN7IH-$wf-Bbwpg@l>V@f0xK++|(S`wJeA|kYZQ6D1Lvu*n% zFA54)H7bS%4vEz>rUoT9&C63P-4h3Siu$Q-2!ANLlS&$GR$4(Q+tih7VGBd(sAt)K{3s-NgQ1TP+LzE$BGwsDems>6n75}#ob+sy9IZr#ofI? zaknBtiv}s~@a6waGV^AVdwYBPR&MXz?GH*E$cSSLEIH8Q(Xh#DJ8f;=Ys?XxPKzX2 z)({-}oZ)?t+RR(WdhFypNlJJ8@W$c`Y`XgLX$-C)Up?KefMJu05O`W5l85Qu%0d8& zvCPh8Sl%cgW(ZT~HKS9IotA=S`r}cW4l~NesKoH0SRD_g%|BU~=WIKYkC%%R!|25g zoqQUWG30hj&`#Q8sym!z5MUw&mqTUL^B3pXF@eiP4eR)8pgW+aqXItxo}S`-li6Pl z%C77Op8uM~LtC=)4|k3l)_spr0-gv$3|72x86s4qJZUt_PoYq_Bj@oQ3BUd(`wX`> z*M3Q7qaAVU61TS#SmcqBsih-_8%M$~LMXvEpJ$NPY>w?>x#OwYxxcX~NmG;dGZShK zunTt-EHA(V&%1JzrjJP`dSnI zpX5qyqeId@R<5d1y55F2Im1|_VSviRH_L-e2O|JPUv#QmRcG>4!JsOwN|oE(Xyy7{ z{;4IKeu>IyMaDjJAH`d<}~9x0%P zCN>RO-;w2dPn)RdlsC}JcmO4)1JRhTdc;d+Fr>6)&qlBkG|EUx9#?B@(9%LAh> zPFVRf?pK3&nD-LML*F$1YpBzAWh?-BZ8LYP^8MvrV+7MTJyO_DKbACYSOkX_FX(UL z8hv!zLbCp>WLH3aX{*x|{sclx8Jcm`F3&Gpz!F3$6<$wE8ffudwOigy)R3O}b}9k8 zGo2lcZ%3p;$EFZB-zhi#OJ~H|uzdo0-_yYiwy3o^{mMgPua#TfWuof&!aPsT)Q1{L zMfKB$>yMW21uZ9}!rU8>!*I*nhn=UAKN_J3wJXv+EJ*0(3FSK{#(UzkAZjW~p3tMdCQ(P2Fa ze{;@eO>803RJNfn!ks^peqC+uF6B*i#l$pA=GF}&6)WSBS>jCJYWTd=bVaG?N*d-0 zo$8EK<`uW2Jlx099dm8VBvLhI9;oT}0`6Bwl7@@p^#sB|YI=Z#ZP+GcwE1BGX+!%A z%OV$+S1sZb9xR%~vkl6J^c5OtzFRWJAu`oMr{ly4*z5+5B})P+a5!U*LJ_fzW&=82 z{^2#t4FnSQcgVo{9fnRAE3TYhglZtAD|@Hd@;Jw3*1XvkTeQL0y-y0-Kj8)XmNYsx znQ$?J%4oeT?7Faq#GFs_8dRhyRIUm`IcGc09`}}iQAojTP9Ok? z+qkhTD1J?H*k#>cY5CIY$(>l$KfQ<~3AZs!aoSTP9X4z=?TX<9S?HFTlO};6yI^^!6JB_!g(tryu{Iq=rr`OA5rNShHkQ}HVw%p;SdXbGfDaZCg1bYVDhGQghU zZnjIsROPV_&N2#Axq<4Sf*CwLcqaN7!}2oj;jBk_VHKXH^iJU&Px ztW(?@Owsc69gC+SX#@mS*F4qckjd3gU68@^E3)m1t$B;fh!*iuN=vLUH>0JR@{A1k znEG$|GT3!aG>wBaAA$4b?AsOob++M~D4A+`PHkEUvK_;>Z|B~X?JTf^5ogE8T7mqMJlQGd zl5c@hO6fL4)R2SOh8Y_CDyzu95EVUvLs%*{{P|NxsBsrjWl|4l*O>Ync-h5g;(weu z>3*WVNbye)lb_xn9NYL~FeH$Nt0UIeQ&H@AAFo{Cwn#)?e(SX-UhzjXJhOxCDRiptymFXe;^M2BsiBsr09uk*e0{w@TuIs z9I{8;y(g{d*iiU*yIZ?^y7TP+lVr>^S|@ZW^(V>&?gi3-BD|OKOqcvD3EH?bvV9f8 z$(ubQ2M@CIuBS;v8oBeyJqF$`VffHtHu3EXMxRiku@s`&H~up+{L3HHGWdE^F=W}*5g#${OxYJygvd^HD6GHl!JwAyOWEDI zh7>+t{lx=97l!dKP*T)NNCOJRy4**cK^NdEMRaXo6@ zwOmz#?&Sj1z4vpQ&7-4bBMM^+uVjQiUQ+K2KkD4ICHV8pn|knO6b3IlT_%5`Xyt<- zVotkvdI?w(MR(4sXr=1Vf^G`0PUVd)dGluNq{I!Afn4ZvxA6|6_d-eOopUDY{k}b)9e{w5! zwXAp!k+a!11I=Wgo-uy?@DrPa$B;yW6qn*a&Zk5rM=falB!MAq_OB=CFJj&gj*wEQ zds+7HR6n~eID{{7V0}aayh0@~915F6WPcqkL8HR(dr*W^@zK90Zn*~Gd=ExN3|0R? zzs51ckf!)6^AF+a2Ua3+2gWbQesr^|Y^qIogFj@0dv~564|x(h#`v_AB0L znBi|=vb*PtJ5~BsjC(VV?{nH_ID!&TyZ^e-DbDbBJJ5@<#gE}xQFnNHAls#rSO?B& z)x%4txu^)l=@57BQh8_JzDq#8%sMj2jQ%@u|CeSBu{J7xzY7B>6C>v^a>Sw+YL9u} z?@m|p3HHar^0!*OkKHPRvpR5th>{&4Gb<2PrHjVPCr8W+Vy_Z!f0SC>2%3-B54gnB zR@ii)7Mh5*8Ev$KS5h^J^XaJ-$S!>ytgj6FJZIdatV1Y{eb8k1%Q9X#A-z%WG{W^g zlw!k`k;-B@Ro`jFipY9TyvHP#-zN^k_LZ7oks)AC{l@c5#&u#74UjyG1 zW*fsknjYseusFQy*GgJE>E8t-U=e7VxqH-cU<{TFQ7a<_OdhvJvlh^fhZ`T$Ek9HN zsS}wawYk=+UU8J5(M^TjH@v$hqiOw?)9r~IW!#tw1&H#!0Oe}28P$~$C)PGZN@I`x zS5d6y&YXvHO`;WD8Vbc(aM#QG+sl5tGX$Q@9|AJ=`1p9fv43;W?wlmH+J3ay{^M@+ zJKetOC0kNI-ILkVJJ4{rgLxV*@HgeHM0Hy-$WnL0@yG8 z0sa9HatDM@vxZ`cY)RWe7sBsf?#{2gWrBwvHRCVEvV;8FMBiP4U#}k@h3d8kKRjN> z4<_3oa7!PqfiIZ%n&&?n*u=iRkN=ID-FMF?dk%cLKcCDbi};{_;o9A15($FvdtJJj zwDk`4jQVhN5``>Q2EnwFU!^-t>aV_0)x3634yt%P8hnQSpc@lY`O|DFX7K+10X{fy z_XmHxp9mtFzE63+NLhvV!RAKE^yvxPLk{Lm16vi!G$%j&Y7X>k6v2$-8r*T%XaLmjCf?MqlSlRYcghJ#dhJCSR@qMUvEpY!aiQ*8la(;RfrNq9&uJ}zl zlZd$97?B|)IE#rmu&t4@4vq0IQ5Xz7$d0dQwYyAIiDSSioX)X;y>zKcuw2J4gi=}XOTM`KHc!&- zB3Qu1=(wwn_~$5@Q}JFG*}~{38S6QE-9En)hB%*^J4V#kQFWuEFk-lwFjzATe$r9b zT~`eLx|cLM7ZS)9Ur>eaq(jAVUlcBr9Wiv^b7*a|YhEoj9QxqUP#AW{oUe}y9vUjl zu_PkIh7BJmTtdkazmR)48;yDzb@;kKn)~`pTy{wO!+@Ln9110vJ8jjSufm7UW4%8h zi2=uoYIrml=;M-yFp9)0u>&uG!LPC-3^(VQr^x4GwCYLWvAOR}X6Sv@;6gI$S@7Y` zPpa#lpU@nlveSiz+J+&CXym;w7&p1sc5En!;RNe*B6v}|fV2rC?8Ta7wCtG(ix@YG zG71n!B;|7n9dLz^+=MLWfOcYX!2{%oq0f!`f}^7$H#-i71aNiDu6*d|=poqRM+}cA z*r%d_#YUs&cqDY$qF$sAVc2jg*Fy+D_Gyzq=!yFsGg0X1D65`Ed=M$YLwg+8qJ&LG zxcfd02D%8=b8TlD4V^qTK0!AJ4<1NUF0QHg6M?>rzU#JP zs2Ul5m)rN0nhMRKw2tPth1ye6a%$tIGld%qdySBE#(GrC*WMh6n0tJGJh-Yy|9H#n z|6tjCTRo~b5et5YfQzoyoAir*2pd6amYtHFne+#}hwY1*u@_r({RM9d(S{2MlGyC|b(r3UUlApAcMC4$wF7l$WNds&s4))A>Q924pIwF&x)V^_kc*94w3i;@QpC+H2M1uuPg7fOV$&UECK~AEDv)FiY53(dX2cdsY+(-sfmH`xzKoDiPgT}4f1P3l%}p8Q0z8XzZJMG2;k zZa_}&RxWpNp8s{&&3E%O$p`86ZTRBgbvpM6d-?7aX)S=y1Tui9QIOvQ_1fqY*N-QN zOy95Vdbesg6CK2H9t|`=cmWKH#>+d3%hQwAe~(8V<$WX zu>U8tOtzz&!O702-`v|%M-b`1U!6dXVZNBWzg%2hZ9FKbEup!vd$jSZ0ZWB@M^t3= zv3lk@UU+{_nhO9FWX7nY4>(|)jfa;_CdwpNG01-t2qhF*R}xZLFuBE4%;}ZC@=75; z8bwJA8!F>%aB$OxUBy#ZuZI}2Uxb74zf(F@NBPtX88c%n=`eD{SIQ2CmLw2B%#e_U zSgS2Xa*K?_+!4+>g=Y29)WmSEWkDjm)C7Y&r*hskuLxfb#GX}CMBQn%Oz(-dK)U8N z=ML|lvi}E^)^*w00&f+o*2K~HwI87uEd-f?5kaF-f4}x0GCSvJ(HP4B`ld>)TO=v9 z?WXac2q|&l4k!SkLDAmM84D&=w!-W|mMud6%(s#2kd=tR!;7oQ{a}da2zYzG0}H+; z_2NgqxR9N`2RZwi5kwaEp$`QTTu3o(zVuy2q|$(&`koHQzeM(3UcU7nTt2+?_M;Eo zEwrrcE_AI$6(2;1lIa`Uu+Tvocg?=6Oe_0fN9w+BJ-qc^`csgd>T1|Tu9%)=7aaK) zA2kOMM85pn9_nq~j(X|EeFox3X4K#8F6y&xhrhm;)?Ws$tPZJNGc%#CC4ZI@xxM?x z7u=5?IQ#Ih8`Qu3@*j7&TaQ7Ii|2lns_t{F_0T`7)@OH{2*_Wm1zq(oK?HtV31k;i z+tt*>s)0l>@#(062DcI;c=4A|X5esi%rf7mSy6hVD>ak-3oVMncfTj;-jOFkOv zZAX{l+Pv)TwT#$(_kHTS^e?cE$gn)G&0P-q*l>R7eP0?2d{h$dx$cQM_&9vgEhf8o zcoCV@he&8BkRR~2_E7UCiaOLAw14AZa{|^Ldp7$SWoF~M71aMA`96URkwWcUQ_tLb zR9AUaUtjPSIFp6iWXs;|fWm_zd4kBUFOhuDZsq=4kfB;j_h<7JlbxzoN(B=}b_L&1 zYZFAi2i3p4K-dg4Mt|_1eF*fryp#&;-xl-a+%(v7KD+=Q)qlLV_rnFhT~8yztb$;| zcaNv#Fh&sb%SIo}2~ktDARFr9B_owAd5fZKqf!hz9~pxyaZ&xgaa)gTK%eZ|)R{Of z5OIjw<8vOxL>qN7ArgwyK@$$;B2fP1a zu^#>}6h;(0wc3jG>uU}z*JnNk9K1?(;T8;xD$w6@R)8W(F>{j6S{rug7(pNRA?b}g)9oq(5vXdJxW*sfmTdPt*7|#$5rT`mcSj(?WbUiost*|(s81x+()%p~E zeq(P5SZreU>eT<-!@g-?Mc}t%?r^ThI@<*70fHbNfX@>%qX^yuTHmSfzSc; z;6?`6_^ycF3LEX!iis)r;9t<0{ZtGhxCorRc5*8C;*f82rRZ#1t5dx^ZPyFDO~=(| z3Vb`tWk=P`uzi|*bMtIQmnz&gC^+)%>oo2se0*`~UzJKl7Xbmw=C;A_qvaht*>-|$tio)pi#=!|XYtpq(?DMLiMuy8HH)RMt8d94HSAJ}XG zK@@C5Z(B_|d}l#qkwFAae0-C+F6C^sbB6jE&MXf)HnqQS!IyzdOH%HpPfR!aZhN&g zE}rhq_YnK*>g+_8db5Jy)9Itu*Ql99tb7K)6!#-5Gi_ep&8^qhK&*96|0Ls*1+cgZ zxJei`5Dp%yccNX7*^^5C8rbh#TF0zkb{JhC}q#TCg^5O}#PH zD#jS8OlE9NY5ZX1C*EWlq`s+Lx!@YH%DfgUS3YIsNQr|I=C@p>RaCgyluy%6C8c3z z;eVq)fdnZyD2Pj4I>S*mxodG}ep;P<3L+s~P4(*guBGxGUaIG{QpS-yQwF|xJV%mQ zX?(UXW)7{e@xVEZU{~Ied)2{PY(~)vslKt#raJ#V8d*x6)?j@{>ApV-rgc)4N>9yxg_;W=y$2Ay#VS z$OEsa#p5n9ggXD^P2vu4bIbnh{1dmK?!_GIQxb&(+8ccFmQo{uAADd-8r=Q>gIDt* z2K4SW;8!u6!_1@%A2w*gll*7U)a~0-7hPjT|KGK8>g=B3oI~INoiJOYu#nHbZ^dPH zpCy3SSphcVjylc~0Lt{qu-1k38oyZpS}M<4)1;=uryht~m&Z^WUG-DUtsau@78EwgJ{xs_w?l#_S@O;%ZgMw(zXIXxGx>3|FX!At+t zWrc-$kBoySB(XVP_kq&|3)5@98qr<4nlki1NX}PX+&i_nv#NEp!u>02A)^0iVt81B zOt1Q!pL%A%irc9^pq9s0o^0mnYNT03tXY#rpy(W63z)GY{Q!tLc*3m)*HremWK_^J zb}@W@9CsT~>}fe%OP}$774ORaS91aGa%$`vMe{M<^VG>yDj<`?n!V|%zY%5vK$t+# zWn+o64AkNCE$ClB(Cw--z!Gbu1s>`2gKY>m-D1#)Ks#Sm!CxSI4NW} z$t!Ah0fpt;X`gpKGkq>&iU}Ob35%hAsyLGG;U+ZfO@Z$jH&C3+gf9>?D~}mg$q=+R zdMbeMkZO>-3b*v(#OI(LiI2~4DAK(6)mYv@meXK``c7IzvRGdtsmd|0% zEa+>2uL-hjRP-7F!_Kys9T3Ol%e_w0bhl71-ulE?>V^$YGJ&{RCbZQ{_;hRGmL>lPpt%5;yk)T^H&XVg&#))lftIv;e8{2{ESerCR7og^Xb^cE%B)(2^zNfkqVwFU(e}SI|38laIbeyv0dJ zJ9$%Zg3de(IYd413>@}@m2X$dX!n@iL+BA`P%7Z7&8(@s0k+;gm2)=;>s%widJP!# zI6w8-0UI@FUXoDBNM)cJL{irO?!*SyB&K&8Iy7FbMmpMeIr)) z5&)8zzAvOPF9v_pe|;DGHCyN%ydB^>CCN8|88{Ig&-y&F#`F)yVy01&!g~RxGYd$4 ziZf%mz=4V{aSw%yO_LCh^11dSQOos}S6>Et!gQwwSo+OWJ;6*~gDaWY9G~?1Ux+3l zu&k2Vk*pgZ}WocGJeg%)X$Bc^Gm}ha$F`vjmSAUu{qNh3bY`{o?o4Ylsp&fx%wihJ<1x5 zl_)C7Bss)!MX~wDN2ppBTl6j&NpI#=$Np(8%^V642}GmVvOZ#&4X1FM4}eT?!OFR7rB))T781Xn=*!* znEgktArA%;ZL$?1vpO{D@uq;qOpgpw!@x9JW~OY>Sp~sa@bkj8@XC=Vh`YQR;u96! zf3cMh63=qcVO2NdIs2Y1bensw+|ojngPBRb!Xn4-*!Rn%-#hRQplGhFv#?e^Y@jG_ zo+(%l?km6P8U|O)sCc|7G(mdF!hW;j`NyKjNn^C9Y^4vDz{#de># zBbM<@y|`7t_Ch&L_rluv&cq&F&EIBW=c_ZfaaIv|Ynm8K>0E@-oPu~YtpDlzzjY5_d7cpI}itxA9ogShS!$6 z;tp8}Ml3Moow09LZ3br(8>NLBnbG#gSB}ac7^0Gn4>A*-%i`U zo0vXKSJcA<-isEV2N%C%xKDHZeopArCSDQrZw9Trtjzp+zFoRF?%;nf3FPE=YFQsD zc`M`X7t+Q#nvBat7j@{n2x{+k1As}Eaz;UWHs1n$@79cKMQoNc3i^FFjk`9h9l5;= zl~vD0e8gV&PHcMHuR)m~V5#ob)eZ2u$RGO1*0A3y_HE724SK$Rf?q0*oodj%B2|e| z@_X+cybu04RIHxf-_IPXye~N?8Mpi>4D9RZc`g&ms;YJRu=V%m?jRLqBZ}f0Sy|it z=XC;}wJTgzO+q6>+YWBK-7a+cI65+U&k1Ucjh4*dthAy?ACs1@5auPo+!fR!k?M&A zc4>YxhZmOS&!Z$ha3#^4P=g(e!NOrK!(98sK}~5TgByww256;3rpA&;0Wp5Z3>}Dg z7+?ETH80r~57nWAUCzm$r57I!lt+|EtF{rP&@2an2ry_4^HQOezDq4c82CHy zrrw)Do`#x|k{LiCos*g@i>3sxn4|cq8>?qYnD?h~lJ>Mf79ov!eEJex3^2#%JFNo4 z{HUBAQtAXw8LC?)GrO#7C^7d&D`kUhd_KRNb`xg`W(=+JOqoR$M<~f2hfM6nSsHhM zH15NvRnu>@RJ#hH>Y=v3-hkGK@xGMi0Bj@%Ol+xf=8U?t;c!knh4DP~i`otCXl=JnoZbB+yx zET@*(p!PL^$9pl>FTMHBEvE?yar0{@9yedTExTH;+dj;#N{?BDO+=mEeH%f6T1vfd zZ(;Yj^VhqsrLBUSPgZkJ=3SziIMpbnvI#?bYrn34U+wPAdjwv7^**>deO9XNoij1^ zTPhGcHe#MNLDfIcOrYz|?QIPU3v%~A7*y|FBns$osH+Lg0z0u7=8H12;)CxxvquiX zg7%vhtEXqCmG6vk4A@>0CI=U68r=P;MeoT?T_t`_oqT{-t5=JQQkh@ zZ9W=p?O7o71lqh3t|hYEU%UBqIj)pa)OimWZ$20C$XHrBWq9PEbGa(qsS*0oGmZr8 ztW*xHTz*2>98VKyUPZ|%Q`Q8iC;|pIIQenu4A>vi=1`SYn3dMNG%PH(+08F~;w&lZ zDs|FR`g{{$3~{OyurO1V|*I;@{wuq!p6ClM1LO zVo#T$)lp*0?fjm3!2V0_N)t&;M_~S`6;UTF`%?wh6gd}_Iu!(|64l88rs0!Mi78IB zcBum81jA9DBt1Y$GgN?(!!kM<5oigk9;$W^Nw!(!5#`Ct2E!@j7}9ZJiZGECa*A*y z09=H!^2k()R(om1SZP=k(dsHlaLU;U+D0NL;TX7Bh|~ZUc5I|<6z(P~{v9}Neh*Zl z;CKpOi@Sj;#4<~1RA_~~Vj1Zfr5a4kK@o~*y7D&+Nm>ed7)z@NL0{AFILHd}3@PT! z{W-5Q%x|wZ!EfNjZS>%P7C(Qk8qfvr%F61IGqHcB|7C_CLUZqYDqU`?pKD{^Mqz=7 zzgkZ}kz*~+`Fr*KQjwmo>pzeZ`1Le*aASW)H8{YR$AQ~R2%P^6=$&8NVvz|uyu=p^ z;5`;5>GcVE^s};d?pX7OtT!+Fe%u1UXKOA&zV)sGb}e->z1E6iQ*9fyD~G?TFV1w& z^=tVbuZtFYNpKDDnV61H>aF1IGo9BLg*4yrYpcu6i<{r=yyn*QtJvOb8(sm;Z@1Oc z2#Z7c%jkEq&5B_o#=U)y=hg3vy}zF}o)0bUyM2QEuWSx)$Co5ad$od}{a(rlrr(xq zW=(o}h5bF3*1)WR{$48u+u2*sCp*(cji8rji=#3fVq@I6o150zI;-vA=l+kU8#XPK ziR93Y{O(X{Ok2vI))8T-=D)LdThNtDD!ySf)Kf^uCqNM#uwSK(Qn(UIDaF=I%#(2V zdBLbh%Kps#F%=CZ5WOTZMUvc1A?*?_fCwe0(BbAm2@|s^5T?P+0ZEPv+_+WE>%Q#J zC{gR_@NvQ*!b~8$Odv%A1U>tQn*t96IStgRH_8?YiUf-^iAPq4pkG4<~lBw zYLin6tTavhB&OJ(%)Qhj8f-!<6=|EJW3!~ge>0yBvWSoEq{C%keCG&B!>OxSGgWDu z)vzoPDjuj9$3=~E0;G1)Yb{TIam%)y+{&#ZF1p zxzHP+gE(BA6DDi^9Z^0aFmL>iwgqYcwTd~s4z1?T@-!(^enbT|Dq8Uo-)$Q4wM{Hq zA`l`X^7s5`M2Ax(aY_}EDa`oj7-{8rs49Lc*odY1w zcHng$*!(n|J72n1K7u~d{3G~`g*B*Ck2|qX^n|pf@D_}~G-NhU_Rj6kaqQe?ANRHP zWw$7h(}-yulyESz=kfA1-)wGernhMr=(X&_>(AA!>(FzG*P01xjVd*7MNev5 zg-SuPDBbz#^FQY|e&VX?_GO>#>(T=U?Kml(Ttfv(#x*7tV4C>Hc|@T|+! ze8k)jw9t+-kFL|VwZL$tOblR)OIL=*h)J>2Ap60bqM@O2N3g_GRpqBaS@O5;08Tna zPF{OJZN(o}@td|Z-{)v*oGI}dfnVC#$$0`aF*s{j$@I;%C27e5{$6GZ($aau`M*5p zj&m-0V_?K1n{!jiQ!O>ir3JEO(=nzA*eBEI!w{=NU;Z#tbGj)AG;58~Okrnr+zN3j z#K=xD__x12^U4z8gFBT%CT7XR|!X4 zff+dzJ6A9@htQT2d7g62OnzB<+|4{$yM$cT&Oy2ah*Y4(MXyO&9>|$gQ^pVyaHf=_ zMU%?Z-6bF)_A6%CJiaP>CnXfqTnCn}IZw}4Qu0MXdK?v%rjzz3(6zxL5l+*cXQE66 zUYYPTq*x_pikU{y%lijy@Y{j+$HQXqhwaJR(v>5}&5rGlW2XR~4x;-+{X)_Q)(}d(l=%iths;QxvBeC)IZE?jNeWvtThtv{=7J-nWtS4X40w;O3{eb6v=rh@ndr}a#UA!jf|SK(r!YeR0B@y@xM+xSY=M86qAYZrgPGg>YAaJ3yAzs@HEQfG+f>xA97(y+>dn~0$PH>(EGc8zK_lXqX|sy6NIsynnFkX<^5 zEB)Xvq@J^oW&SE}-wt(yd}F^gf-mi-!LLKtQR~Yd!Q7yrD~~G=OYmdcN~bON7B{*< zRDy)#)BJ(15MTbYOkjP`dj8hizayR6z<>@Azdt*F{>j}+|5_GVpYitl>&W=w3chvN z%KUNMvP64tBcD(|=W3P_zf+B{@pk9y^+xl^uGf#3s;lcAk9s~oA1*3g)4M%tDZO&vR-vbnjY0Cq%buQ67yT8YG97SQ6$!6yK^G{ zHkZUw`nqPAlFZ}A4a?9NW2Iz1mYo*L`IS+Acg|vWn+&gJ_`6F5I3_f;Qcc>+73PW* zhz!+)N}09{%GWfFL`eK%Zt9b$B(77Ypq2W&3~u_196-%g15=x37)#ntod+qqI27|M z9|D$}mox>8xSdl45hj%R9o)2ZUK*m(Uvk(mR2@Nnp%`pcC}}PRd8FUwrWikJq!DS^ zEil44ZA;1+pvx#MBLu;4v2xTv&nXMb^8RPYexSl_Q4yby{UouizE?Z2jo;(lS8L~U zpX&ZbQ6J6f+FI6{>HhPT_36cf-k@F~g5Kknnf2gD#i*O9R!?+Uu^mp=VKBkqj)F4emAuQx;9s!k>a7hWs)#>>vUrwO3U zk?SqfljU2L`M0)hf}l53OIPq3#QC3CTpTY;_Ro_VikCrIP zgYE9B;-Z!`D;OQjMsg{h3MFW&FuZ66&^Mw-(zsEohC~}Zr_Mp(d)u4v1h%3I7Zm;}nvUkKcUC5!RBFb5ms|1Zr0iI{p2kmaYT=vzJiAEc&aK z9xBafHziYLYAx&H%^of8WG5~?0zb)^VU~z=L@hw6Ger_ERi+02easEx5SR`J9|<6* zQCJgKm?=k!vq~-6i8UH$LUR(prom*FrKK#jNV8=o|B0PEl?~8_NohbXDHp>1W)&%e zi2XQ*yByjTKEBGfpUcXiCWMswSnOV4Gd_W64XQ*P6+&v8GkBA8ar*Y z{J2W2KPz)LV>BEa9g-g$)mz(T_ z#qtxckEmQ@=PnTfkqrOq2FvEI}%qLRQt!+9!HibEG~V-8mw4)x3W{yDBS$m z)9vQ@x@-IKx~9|~zSq>nzyJ#U1Tu)R+M{K{T6-RpNdw0xGSO6=b{e-`zCpRi|j zci?nhbM+(cN2WVy*RcC$=VtjnzHa(-Xk}ZGC05{nZ#4(V4SZQk z>JNNgW$S-Mp1e4WiBB6qQlkfb;B+?;TW3jPDATx5TM%%5qDed>YzXvTu3Er|=`4}1 z8&+%ZZ#heD#LBDGiuv_YXM0wV+8rt}QzIR#G0vSTk5~+Y;6hHRe2N27u)>5-TS`e& z%E}XAtTcBx-TN&}=$_a~=?=?n}`axpPbK3Ku6xBjeW?>(O`k{ zt09I~&D1p_8x~Q9+5tdMWx!h(FYPwoFs6VF8z-Sh&#pSwVxEN&CRHXKiqtHNz(4}y zX4Pg&8IOvTv&{bk5@Ov^;4q?Ml)2Z+%&9CxW3joWaI@D4U^1JqAaerw2<54PjABW^ z6t03f7gO3^L;4mW2GM4id;lVuIQap4ehHm5(Xw>T3&1%#<46s^ze^|1bI#K?_ z(<5V1bI@o|NlMF=0h$kzX37`_^Ccq1cStl<9(`C|`d{z+1@}CbZ1pi0;E&u4 zHnR!J`m4sy?4{zL8^757Sr-XdwfQ2t&Y#aXGegA27dMLE3fiw6db-r*)sGu_n%P)4 zcf1Fk{ysm;1f%rMb~__t8!Yf8c#SUw;?@<6F0avJWaxvVT{b7rXyEwE-VQg)I-Q|LmOeeFkl!m!5-0 zMw}1tc_Tq9s=czR1)l}0bJ4x(o{5J#osV?vh$oZU;1%EQ4;w`KmyGP{*e;g~*jirB zw-(0NPEU;6TRA^o-{!r3i10bE4l@8J3uMh`KH6cFn7F2V+9i*Wi9#C>|{3Wf+U;zLqd9l;Pryoa#s*PSAV89V~X z-$ln2Q>fKh7|?h;xC>C<7C6jlDS55KG3YsqaCYcp_4AC}-c$7#4&>YgN0z@emyJJ&(GIk0$ zlta{nmdGJmV@qj3DnNWjTBZ3nD@xr1a;4}ZREpxJmf@)qtWbK@i>ZkLMpq@JLDI5p z?nVJ+EKDd?2HDsuI4EuSIQLKVc?st74yVHE!lP11Q-sa*0JKaqVYb+~LwEToZ= zEp&9$wHPXqmp)CQYV%kfYUJf}O7NFyaR{lq;mVBX3{t?UnPahQ!SMgYg!@&2ixBmd z6AA`W7Y^rwrb-BBSOPn>s=A^q1Y~Q-k%Cg5_M!UsIi1t4EL~cU;NCfl~LHQD?2#c|YJ7|7);W+sobt8@jIjP`=r> zip8gzZDFV07dKbi7dvmYToL0e+gZNEmuatVb%#wQ7Cxu`U@jX6FMrUzWqX)6JQ@alL)@8lg+9fg6A6*Ft zN3tVBx%V!u1}240_iRZUY?&)tb?7?aQa%I}q3PbzLHu6)-u(5mf4MVTufMiT&FZ(E zK1QD#R#^M^*Vl7={|znkbx(#li;>mxsJ0359ZizGLuRt8HhmBzd(kb2GiK-2zW4tt zC`hko={0yNMJ{4VctGJ^sVogk@sm};zi0xQ$JXxaz7WOhB5CzhnQJWaH$80$8&r+T zfdZtNJU!5~xv3egMpVZqWL)l4m@m=MhK#Y?yk7K{hhtDey35ko6z5EuCvax0;~oy` z%ShpGhB1=6AwFX4Lj!jIrhYMhD`5YVpz*1njZlkR6FIMAfgqj`@yryZM*1S1-c%zU zH@<}4JR-1ctQ=Flg7Kh;gP)s7D!UvwcGLAEweK=YET+O+^-WFBx&1IOs)wQ5MVP~h%R4`*;alxAAO>0J5{2JsI0O-hP&?|gO6^=yY z?=;Zi>gdEQb{pX-0xk_=(O(#7!8c0qt52Xx?Qsr;knc!GD^X{JE4oFdVhK>@%A__@ zBB05QXQK*O((uD*xtk)$hiftjln|xDP{_oRFO->j#rhyxcl?wKM9s8^g8Z=GJU)V=(-DZfk0G z&}?jcx6C?ItQNVtj?W66wIMN=h^ zS=cWLsxT&^2)a*qH0&Fz0}68jz}HqeF(L%ws%jA+khyB)P?1N;2IUZe7>Jogf@;OA znuv+m+@q2^jG%B>cBP8O5jj+qCaOq)I|Xs@6it|2_e+k76iNvOCKn`C5JG@a6`}-U zzn7KlF#~QCq$F}N1hUB_yJ$e=r67~58&8-a(M*igAeJMGFd4aI0)vE^e8K^xIiib^ zaNx=$hH|3BijN8+fRw6OvT4#RlGN@uGEZn6nX{CXA#B1qYi6_LfhR`H43>zDoIN0? zl#(i!Y&jZD@ zO^ir2sR$C?u>xem8P_0@#HKkLDv6#FbEM#jf|3}KxGTmQk)n{2vH&E`lRGh|7!WlP z17<0)CKYxgViB-OBpQR6Ag1CZM9NG=q)M*A-A!37tEW4mq<~ykWLZC|8Q=Xb|NPg# z{OzZQxBX`A;NhQ&wZYBJWq)(R)D|!LABwZZ zQDc5#VRh@>;TMBpzZgyLE-XA>nC@&GADrBto;5n%wey8halSoT**oh!C`OIOSGCq& zv-P&s+I{)rQ8B;TzG<|I>DBYAx$WD_?m=VmYP(sxZJaH1E}uN>UOrnap7owx6wTUK z?V`Wgdb)D?XjJS?|7qpy552d&*`=BGmoJK9y*oR9y7sI-cXTo8cCR`|ZwI53W-)AD z&Nio-gYIyx)tEmYbQZgfM$ve6+Q0qw+fn_)Y4PB4c~BG=gQMM|J6&|%Tr@uP&S!^j zFW$U(-~REpPyhPmpOc)%?%6#3YU*V9BUDKfr$pndDKWEf031r1L>QIJHKSq>A{z^P zj0gcCni~m;10W%la1HDWeT2IOBR2zGn@&jYm}3Ugn3*I7BM8_yDWK&TkpY2|u`+8A zXAqJ=OMq3&dke3m?Czd}X1B42j}?+LO7$#ah=j2w2jRNB7BUeSqI}dyrkaw18CfGZ zk`sqS34s%XIONLA2$?dBO8_K7s_=VKO`H+4(S2sBl!btN2V^FO0u?7EgVKGJZKO_t zd1BF7ucJ@QRGm%1UV2rLo0*C_M3sdyd&&uLKn$E=#!RYGvL*+J2`+9qRF;s45QQ|Q zl!Q$T7?TN%?>mvGxJsGBec+NKv4v2E3DkI!)jd)+VxycUA+Uzv*;0a&Wva{+l2zeR zvJu=!IGD!LGb&G^QUDWWP-GH-C?!*HGzF`vO3Dnlxdada9)gnWgCztZ7K|+*CsG3? zA5Pc%ovG~%@ zitU{bd$ql1{q3cb<@M3($z^@?VrjkB?;S05cHgXhXx~gt&n_LW%&#skwT~{lN5$6a zZGUcdZg;aY*WG=&w$&}>zwWf=o_@7>IIIn73-gVg^Umt?=0Uf%*7!sHNp1bGJ-;zK zy)ZpJ*L|=zyZpGhaj4*|+aM{pII>-l?SIlyd%o$H~Y@V8mq1Osbl)z<9DhVN@YtoFE{I zK4x$rmrzB3RDnv&5X{3Jsg47JAyZ09G-i%AR?`3?BG-G$0~0efi^^`1;Ob^zVq&v< zNQVfZ3Qm!k4X&0YxR_*3O6*A|V`b(fL}G+{QHuyvksx9LR*531B6v#aEO?6E1MC(}HIp#z*+yo&OPm8S#ImYsPAcFsWru`B z0%*)s1(+ZhQOGesqp~sWlTw6gQZOhe)L7MkeJ8-KEfb9`U8m~TU;gX2zy1Ev`{wOh z=k>Gh-ow2Y>y5?N&B4Oj^00XJ?&0uF=c>6`KiXI-rkfY-+3C(V-?zK1y~DNM>o-SF zy7N~Hjm7$~wmI{zvDlj0Jy|X0E{mQ5kfBdwty;SdfKis)o znJb1DxQsPC)cda2XtwsgogFOn zH+zGrg+b9eoV`4J_F(SmaP(#U`_(sB3+<;by5|QUcF$ItvxDYAzxSwn_~`AU(cg}) z|C)mSF6BrdMrGL)sx%fva!px?vMM>;jLTB9a>*RvAY(!Z#6jq~977ypL}VtgeIq3z zCJubh$fnE`JS)*YAt9DTQ3XPzssz=*n1DHOQUnqa2&kf}!eYfVc9$59W9cw(<>nSN1(ceos$z&?A7uBbm`G;8V5SDL zET+jg88NIRNvdE%WombktFlz7?#g{i2#In^MkSj#ft}RGS}`*V5fQU7PrwMo6C#?J zV#1X|6{5_N1X8*olpKk`gscoUCE=_VvQaSHB?4KM01p}_F2ST^rIg*6IlBOclyWjc zVqyUVEcc5slqM;u+%dDKM93sDs3YOtXBdJr!K;!cM6o31B;riwnu#eBh{D)KCM+D7 zNy+ySN_cjV8laSv%t^T%dvNHzSce2Cr$Ee+MO_qdFlBZog}~C7C|XDk>}x>0FH&&{ zW{zbBgGIU0oC7;J1Xn0n6%`b0fdG^-GP7th)v>dQDe3;0d_Ueze)-Qo|NY0^xAnJw zX}_D_oF5(yZx%Z@C&fwYY_CzPZT;cohohpl-Pmf)^cvqcwu;t+ZtKN&vkNbNnj0J! zuWEZwufD%M+$%OtdNcj{;==oGxAXk@0KT?c+oq3I&<^<*{InW9n^~9i^ayvr+agw z)zi({?_L$Pt^VT5R->4ktE~;%#h_U{nJu0^n|gEnU4Qyu@!eo>et7$4@sEqMm$l95 zV(sE$y4ii#Y0f=Y#k8wF#IRKk>D z?gV0@iYI__8C-P?V^1mHIRSj_e2?-VBS|8#17JZkDfdHAhG^vL$U;kiQv{+woP^_u^9-A=oT%IiP`-D022nWkT`_N zF=l5FmCArb5|N}?2mn`NF(pbVsagKFAQ70+#2FNTvI25~!HozY8092pxFhFm%51_K zvP&`}4o0f33}$#r(4-90%oM;u1tI}N%tj2VCV{ggvVR{7_>Trg@tZ$e7M{xS{I|u z*Mq5Iqks0I_F(m(KfQNQTUwj#J!v$H)}y&v?R2jF{Ndr_wQhT{Io++di`T1zne**; zUvFF%?fUEfizl`Av(2^J`QrQIQT@qj_e1~q)q~-}^sBY)!NQx-;!I~SI9^`qtlk`V zY7b_&8^zM|tF`H7QTwJiectE|hfBr#{_~x7ZLRfXZ=*H*?8n*0>#yJc?H|9~amkfK z^xs6OMN%HCW%Y`vBr5~JNW^2S5|E%;O)9X!$tz-VBO!w92cm#$q!3t0$-!h^N(oj8 zjF>f4NIFjL*&>$_T#7M;s1yLzD4>ZU$7_aEfpcJ2RyR>K z2v3xtSp|17NLevFMv#%YLCJ^>DKW835V8mX453MwkRVWL$|jLeB1f4g_2E71*fj^us)^*DpEQm{!G%e{ArJ!u)eIubCX$nW zR49-#xiA2X6~PiCQ4olxib@!>5=Tm70~0=)0n9SyYCM5aVi9Ah$|#g_f(sK$79eUQ z(iBDSmk&8o4$L|7zXO`g%#?t9-z`&$njMk%t4KQb>iU;|{O!T--<&)jJ!~Ei=TG`8 z#|xVWS6^&&mfQ2U?e*TH!AW!atIhhC&F$HCXV~f1e>j?LAHOTQi$_b3X4`WM&0^`Y zJG-z@+Zdh|H_sb$8*4MGtzN6yXdIueZl1OuF7?+3Yg^MN_07Tbz&bX?|`FaNMn`)Y60Uzt8??{s^!-3Oz$!=kp) zdfcpCHD^~|EwmPHN3G=_b{gN!_Gey9t=~RhKdirB?-rfo%l5*%`rzvA!q(#9?euqz z)xEvl=MT5u7mw<#xz(%r2leggnJ263(=Xd=7rkf2-t^j|qt{>0e)`uhf4y^2g(U;2 z-AlmvmL}20K@xpJk5Z5+gC@wyO;RQj#}Z8y zV%cNVv54EP6e~HwAeJQ=LrO5zW*S{BX_bV+Dw~l-T*p8XN)FkZR3TjC3T~f zORgtghQJVtH!+!`@gS5yT8dU!!L1ZU3RxO)1IPlUv8X^0L_w*M3^4~Lj}j07sS&EG z3iJXn0{E|W71mzSeT(i3M!OTBu5iRRMbwwnWTvx0zKrC zJW#~U6Qhr#q)zkk>{UcGxV93CFG*82UsFP;v1 zGu@{%N6Uwgcdz?P8~v%R-u&_TkFQqG|MBX2dKnqq|932N{KOr2!IEk%rrDAAY+r%4Z>({K&E9g%O)xTq-B;s z8bYX{#Z<@yB_cIc6skw59iBsI0)$fMLaNlDcof!}#1%r%Uffi)VUgnFkWeetl9Yr4 zH8TnljE>3}V#OL9o6uZ^&R~^FZ0M6>vO_@)ZV(hJg}4!lX@f#EQ~}0tFP?HnV*<4b zswFtbP|zWuxI*e+B{P6S8WV$8n(F{dmDVQ3ZXH=v zqFUuvOjJZvwDO<EhX969q6! zl9Uk=yhvTDUq1cg@2`KnXkB$SHn$c(yqF$!&sxLo>B`P%xb|+U)%&p6>9%)vUtA70 z`geAFul9#eU$%GGre9w6XZPCwzB*c;-QM}IJH5Txz38=e`>UU8`XwM%H_s(A|Z=YU#@vkpi{p0!mY^%G~*&S@p z%wEs+u3E?a_Sw|(Ot<}Lrt|&D+R5T#zdu-Rznbq~-5ZUL9u2#X-fn-pywl$syjg$o z_WAdtsgu)w>*I85vGby}+`bt8$D@b+&T9YFT6<@7`J{jR`FOP0-fM53p3J`6>#SX! zu63`E=a(1eujcQ(I{Ew0|NOHgOUbj>Zjo+g7Gh3;G)t&zw^U#zUJ|uQRIwUTH%i7a zNK{Y)RuD9WNF-6Eh}}XBCPJ+Uy{QF2@>)_hCrn6WF_C0c1j!Pjix^as7?IQ}t<*~l zns@^Qgk0zh3PdU_vw(7Il&Yd5XLW!~C^^*%(I8UPqYy#sN(dp~iJ%&sMMG0nH)b^E z14e-ES|A8iARvJVl%T3mmEuV(c_g!%sR8bU2(wwVB4$ELU{)y*VE_w(M3#z)iOPyZ z3}PvQK_w-^DONlc3IhNidq83Zgj}SKiUi3)p@3S9h!k;wqe_KA-OS8O%_0!5MKskg zv)BYpre;cUapsf~&D7N>bTDOMLt&F7h%%zGNHZ!yD=@DlBsG@^QE4=JfRGWfF~7B( zaw#=Z0Yrm?uq4GxO4$Gx=90ibQeucP*ryaFFx-$yw5rt$0xgH)VrFg@v(P<@xJwd5 z2|+zdphE>k^&sRdtfGh@B_csMX%;gw#42Rbqzr2Anmm}0R3ZQ=(9JAciR1DwzyA53 zKi_>lHQ#!*anzc9)$hK%zxw2Qzq7KlKmGY^_5AeC{GikO?)2hh?YRH?arbcHqTN27 z>9#w6>L2d!v`z=xS1%9O`mOd<`^9p5d;fFy?)CN-H+Q#MuYVXkJ8$n@K5xC- zJ?`#q?##YBTj?L2owbJj*_HQK8&`jPwLMteYOi*W@3lvr&h^a4`@M_dH}jh-t+|c< z@T52Uart5Y>(OPuf4{pndenZgytUeWGJkTmu)f*bxZ0a~@#O8yet+X=`Tnb;%Wu~g zr|+!4{rSKD_Sb*$Pwr~Bwb+5B->)WQ<+$hOH zPUDE=EHR=YX=Bzk0*V&sm;fU+ibNE1K$Dcr5K{QxiG`vpBm%`4oB^>0jiex`n|N?j zlcbZTYElS5P+X;seqeMn5Frb-3IPE$rGkK{WsW5?he{G_io{50q0uCZh)SWMFlKRS z^j1;~PIX!qkLu9M)gi*VK|@c8!3~m0afpc~22sG>1vv}JLU(7I5f-BDoZTpn@S&+_Ff5HZX`ZK$9hlDtJ&N36TXgljM+c7PE2VfBo}ce*5s_U@*8d z-`%`i-#Q!3tee`jyBvefN7 z?k}Ic-|hc2zc%}1ueIBHx7h!~+o|2z)vuo}TwMRh+oPlY_t)L84%d%r;!<M9ik+X*9mnLWlo+fOIBB7a7Chc$Oc!9 z4YauGje-DSB~i_a6~GyxUIi7_Ora_P>L%*U7QwnUGXZE~GFE7yXQfh85g@_Lq~r`$ z0~%1XRkdemNNnKQlc92o6Gp;w)&?wa3g}>zi9Vl1=vH|SG@tllW*$@k7mB-~77`6J zYgc&8X4ZJlk_(H_ncOuxC2N8sl_GGFC^A7BmDFSeDHF%Qv%85qywEHeO-e)&zfp0` zr4&O$R1h$clEp~R*{qaoNkCy;(;?=`tP(8&%tF~zsKkIEA*yKOnut(OdQ7z2kTeh>V3H|db}bfJEKh6zE7Vx> z1U|$l2x2l0WASnW7&J2n>FMg@)$QK${LIdIcf0>&r}On@@BF*r*52vv zz2Wlf<$i1U`Ck8MuYdY*w6MH?IQ^hE>i1WBy`}Em`O#W?y?@s0E-e3Hr9XXl?)s?r z#ms~G!P)uVV0&)R>vmR$&!2BRxR|>*-hZ+@|77XpbndWw`fg)+?d4$h{qErD)1&p# z+Ue5n*}MMW?DNv*BU#cxHO?g=x|ca$y>qIf@|0#B7Kr6V7<@ zs<4t;QlS(T2-(mr>P>`r0+GPPcvG>CNfX3ftpZ3ZLYF+L&(`qd2O=bbq{<45OHzu9 zaZHgWxx`{3b_;le3)3jnv z2@`mr1B3~ss0dI@6U`Gv>MYp=6@`F#jAO|(8pkmkEoUo9iU)VA;KeN!kzk;SQKAW~ zplC%1W6m^ZtWZ+`GbjV80_ZKAjX;59bixx&1P7pq(yfwG36qG8g)Yg89)uvY8;Hf) zgu+@(7|Bvnb`#4ksj5Q7Px;qRzx>zF-~Hw4%U4^+U%hI7_^|tGw!7V5c=5%>Qm@nQ ze16v*E?l>!p6?v*?DrSi8(+VEFyHO`v_E&Y*gakCp7nN@uOF{o^d3xKPPeApgYTbq z_P*L1jlSu;o*(o+^!jT@|7*4X&G+BFoo#P*+spm;@7}Htd&~XDC#@&fM|YRrzMTGJ zYkTFc8N z;-G)tZgoD--@kwPs(kpUx844$&e`;+-`bkHyK{Wf|MBmC|K)Gx_5hKIWRgsg@JWoxw3xXhh4}%Z z)P>P0V?h&cjB2P*i4%z~6p6_w9ug!fEiik+q{$0{1R@=2QY4}=+{T>7W`c?yG>A~F zOk^)4F(*-vOP~bi5S>7k%D7U6Fec9mil&gQLS%9`i4{K)KmYhnNm<_j6_9GYN#mADuNT} z4+9{O6p=zs;${g1Qx-vpfN7#;K}rs#QQg4 zrxJLJ$VOd*Ff>6}!!=Z}N=1~WRZ%gR5scB3km`~TgkbQ=sS?H_UWyhI5)o3Fo0Jf& z2+ck*Mjl82VX6G>&%gcS$M&7)!^`JW!;M$1g}0rVnVHu4^m6NLsdqg8YP7RG|HHlh zROi|L^8NMY`Q@wEEBlZ3_GUX95B6qSv+b8h?bEf{J8Mt3?jNsgUvw8{)><WbBwK(|G=+WiVg~3*P&^p}d^ykknmbx1sTaShhhP%DayVI+a zwLdO)N7I*|moAT&_MeTud~?)$f4G0v*?hjazJ7jkGW&jMZee!*eD3~E`}(wVwf}Nw z>8O2txVduJ-+Fs=@6F4f|M}-zuYN_A#QKKW#ERyLunK{QB!a<=WQUr5dSsjTgr6iNYor@QVs3;9Hq>hLd%>yVFBoU#ml8FRXG$RBRqA_TU2uMIq zLIaoxI^iB;%1{7E2!aGyRwUD#s*y}b6k$>ZBuh{YA<}~IreZNGLJ?IH5{yy-RUK0# zV`XUS3Ke}E=}t>^_DHC@sS0aVsl<^BO)OR(as^DPK9$!@0vC_wiUbjeRXj+^7!t#T zTMHOe5$GlpPn9B3qA(OHVlq$S*`*|k-zFCbEwiZ;$>bJ{S+aO`fn*aA&;b#*P&mg&hP zh{H{UpjGNw#VnYJS&c?jQ>KMnGFi$5Ed(fI!0#5HSrI|SoZ(PfsRxTsAFe)rF?Vv(e{;6GHP`Ab^|!xR+ihJAS6+_J&USxTIvKrv z*IgfO_xiJEqw6n^)*qbDE^dvcN5|c}{XuKzV(s$9hvB39?W>E^-s8>A^Kb8;UR>?0 zoR3D+@4BPW#@qhXXzJnW&ZzaiKX}}oTiSj1ZsGCM(+^jREBnt^JM;bS`Q48zZ@&C^ z{r2r|-~IBp$>?2*k|bJicYz31$*NYwB*B|xMq_9+L}(F;Ya|-f{=1$mgj7fwh=?W! za2to1L?{kJ)L>>+lCf1VQe%vtbS!435RAnIDMq0R(Eu>G6%*#jO6nV z&;*NU7*L`!lc6rn;+*&wFOsK+97Gl9Bmh_yx%Rs(?wjiCWT2mu;EKtYPS zD*#bC>rW+RGoykr2Lmb*3Sb(T4Tyn&0XIS17=#Kfnljy@m2ng+`Sx$W{Qd8LS$aFS zc<0VWf3tV?ZgqNXIJLUGb#mv6jk{;7#|!)Y#TT9P!?WS#`ybkGcjkM&)!lbH50}4t zJ3Kr->u&7LwDz97y}NzczW;1(aP;P6@9C($(A!ZrgFppl^pQ6-3|0$>Ue zH2ai9f?GhK%48RWFapVxh^#=69uP&csoub86h{g?FrgSkOi)up5j38Psi>ob0D_uO zM?r!_q==LNsYozQ=oz0P;3j4_kYGeO0B*)AR7)v_6atChPzodw6cvRNjG(zv%+)kP zEW@l(uaKzJXOUn8PV#rZdC|Z)5YJwstmLNA^Ox4(a&EsGG`Sbiw(?4C``Oxmp9d+i~t%c_^k1t=m zzc}4{I+*J9NB8#6zFxbY8NOWG-0m)}%`LuJUfY@9p1nGMIKT7t`;*0sxwS#J|J}RZ z(%Ht{somjde)#;!No%mQe0Trkbb2uTdTwp0d-=uH-S+J3wf6bm)yt=MXO}v?*$;C+ zeb;ZbzkL7t@qFv$sMYSh`?BA?{^{t=gYEyg_hI8`^knmg_gB5sZu{ZWxA*pr`#aq~ z4ZFjg?&$kRYm3L*D`%&}VRz%=`q9PqH?8Bt#q*7$;l18j?@oW^@m7Df-``(;zuD=w z?{}v5k6Uxkm$y##hVA}`-eGU!#oMc^y}cj*{@pKs`}NjuO~%GRNfMYC-Ix;9=*h+; z1>&Mfm{?6n8G)Rf_ksQ?npI+q2vK=JV^cG_-845*iW|Z3$;S8;kU$FRAfN=^s(3ax z)LM+JkQfVs%7Gfk8Y5VP^gx?vQ^xE8jhF{DXHdctDr(~h;~UhKprM(<5FiqajS~t5 zYNS*JLMX;c0dWN=q4pF&NhSyr>Tyl0E7Mrl)-)ACsH?arvzVyoqGpyu5U0m9hA@QS z5kevo2$BVArcQG=G1L;>#gGYe1a%~*k|MPvq~amLq^UxrH0pwiZmM*cC4wqWNY*Hz zWOETObh!J(nK)n_1FGmE<7ADvy3~zJQ=0-psu`sM;n^GkA>6VqVlu(J2hb}imPIVv zt!J|kb;9yam7AGmb3tvI3jwV=RnkqjYoGf8AMQ3RYfxEO@K)4Zem8Qn6x-khMBOUgiwW?4S_(E zM%^qH-WVV^F~tBmNI7wA<75_#6pO$9?U&#F{+IbbuD|J?_pdij`@Q3D`e%zfos;!9 zk6Ir-pS^FbJ)3TI`kleul}`6=`}pwUWH_8Tn_hnWcz1U7>$CQs&N_qRsipqiw->9! z!|wa}Ve9Tmf3W>{I2yd1>VE8=%^qJ&b=tibt7mJEPZpNf&zBxwoer;0F8Y_FQNJ~J zwDCi4(BA%!x%TSX<-*!h`{CRD(e%|1CzmH{Q|pL;?&y1WDE=l=7hop6^*gUCX6z+lHH?=F;dNQfO(8k z3xZl5p{VH-r&F1n1SJWrq*N7Ff*6EhlEu{w1TA3GkO-9qS5=`{W(uSTif5}ORE#nf zfn=IkX6>e;xN&ELpong6Le{@Gq^Y#zjI>YYS~z*eX#^8l0xc&h_&d8-sFJgb<%-z= z5i3S8h%m*RV^EYPm=$D!CwK8gQ6xvsVkt|AU=b!MDWQR;oGLh#ngS;?Bti{}N~4rI zlEN_(o0T zl7%S?6GF)5)Eq4xnAyMNU;g>qlh%3vcxSmgv(cIFPVdidF5m0?VbI>$+!-9UdXM(+ zeEs3U`St#A@xl7D{^`f}m)(p0S1%t8UtJHLyt(SW-WUz{uREiy>4lwdp7r{zoweiP zSLYWekEW*j{fkHYhrL%PSF^20yKm;NdUto{M(y*JL3{iDcbAW5x6WIq{pCCR&pX#U zz3%e;^Nr*7es^kce74!2?d{ID`@^G){`A7;=1O;Te)jIFw>>QsC9RYO8jE>!Vklpv(iv%7ez z5N?|U6?0CG#lXY}OI`<+WFpisre#Agj{y;GQk!RIos=Mu%$?+I2&T8JHGmWs(U@b- zqN&t|y0T&-?j=E3xoMTmWP-R8q96oP5Gg6;ltnZb754&hmju`3bVt+BHCdztuWu?; zBCsT^sWA|!VW^S=Nh=?); zsTn4VNorbHlU@i{CO8$yqCqlCVaei&0fA1TnrO~Nqlt((l|@q&v79h5$W&@ln`}C9 z2r5!dZc)r7X>bIpShiBcNT>^q5@M{f!GgOA!~{i4mc(pAmQu_#=bVa3sY`@PssSW% z6T)?ZNv0-1%}P#|J-Nb6QHn_lAc{iG)mcgqrKI{ma&DNkh!se&NywPDBm*Y=Q`|zB zy{a`JL4y{fTU9F!6G{rCcvcrtNhZdmQIeM&Qx!#mm0AqNGz&#?&5}e#U1+sL5qcKW z@_+vP=b!)a<5zdCUn~rdj@~?6I6mzjce*E|yYtA~zbw0lC zoiDaK{b6T$x!peQ_dd)|&ks-c*V~<8`|4hI;luXqYHQ)u#=my^!^^jCPG0nef0};s zpa1ulP*|HFtBxhOt%eE_k-*v+VuQpgv4JJ?Nm(vb(5g=E z41obcMFioKU55lzO(Uj&DzY#~5o0Dz0^`J82H+xEVr4lJ6wxXr6;pw-p|H4$5EVp~ zK|~Er03u330Yfst5hMn4T8Juw5E@DyngAqqfE617P#uIiNU~G~2}Og67*iq@OqG5@ zGB`CMxT5wDBHbZEBn72G1#MVGR1rcgUL^%4cs=G|lk}|6zy?rpd`eLpKrA$sqy|*D zumI{*QIP~qB%p4C6U#pG5HsA^_tU=U2GpeDCsrdps0t_Z~ukW>Tk`=^{lp#Vz( zl|jXoqEHuPgBql!3V;d{6%@-NGA?wINX1M;U5zP`CO{}LizRnNh{Q@&vqM!YGmAp= zOew4*C%H6|1fqzTMQtQxlCS^~Gt*csi3i>oJX?~SfBCQ9?p#d0{IUOU_uqVR)>+!S z?sZ#N{r#og*Y9WdPFJ@NhfAB=dxPtz-;cJA`;R}gUOc_L99?|wKV3hX8*M)BA8mJc zo;@AD>>jVJe0AL3x_Gy~{qoV{i@Uw8m9^`^O7HZ+#zuF4deH0a-QOOKS|=N)|N7{B z^zv!@eRt40Z7=M#zT92voV=Mj?k=^K_nwTpYrVx*fA7m-zc)8{KD>K6GkmnRbHBUT zX>GoJ@b!oJ(ay<(2X~*gd%fMw{_N?@(&~%Nt&7#(;(2H4%kJlu^Rv#^`|IuYi}k(X z*4&@^?Ou2F^G3VBH|Q+Rv}^bc@=?P-qoVE5QknmMOR!6tE_cV&y4GQbd5^hE-)6oB6(kKwU{mO%S>u6m0|oidIl$nOFu&1?X`SjsYC1u}Sa( zO1zZTC_QpBkNN+n3Rxo4_~D_JRkF%arNQJN7ds1+em#FGQ6D(=asQe3>Y z7&E=`0;o13lEs}u8bYnNsuEKX(ltP0#Q;eGH+d2vqL{j4LL#9bh))(9NctrAYw7|+ z7Sje|2A%Y(Vvf9}0}4a{Nkz!0f$FMtIUpNNsv;UaX31rel%|0oBMKr5g{cBrA5836 z$r%hZF+Z5B!Xh)-U;n?Kzx!_b`fBdQ(*D*D9~QUfdf(6WPNrrzcF#`sW;Txx+e^Fc zofrErW=}Vk=6ff@;fs@vd%KI9+uJjH`4Xww^rNj&FiNRwz|Ee zz1CrW`fxNiefMH%`Q>11bZ2I%_h!_;JGHa)!|vXE@7;&9;c;tveWthic<|Nc^5Ar4 zzPtSPZujH!jSm}Vdka(DwFe)6TJJ3_zQ6zd_k+3qlcTc_gR|bV%SZn<*MEGraP|1# zFQzX~-%s^URxh@`I-MD`zu4`pP4)Zd7dzM6SFP6h-h8Xy+nZTgzVl#X^z3QXn9h=o(c)Gl>bwKS@Z5RxvS}B*v`q z=0-Rck%J&m4`Q zrYj^#kOTrrQjxmSBu*i8q9Fl5L4cxWK=jPWDkMc+QKN{uio>-C8_H6bW~IF*DG6@;o)bqgRmBosJe zLseH*W(Gr%N+ur0ttK+KK$TKlzzV3%-BpZ|Ej3NUOt_f_PZ}!}5lV3vRlg-YQE8G( zAR1MIG2g063IyRoV+9dFYpdLp0g-?!#9_&;1f{vr5yUKj4lqT)LJXDCTz~)~fYIDR zfLT!LhN>zdu~ukJ7*oheBor_ST>_1hx5^|16@&qGplKZ>3B>{k3YoynnBz^fI8J~4 z<+r~-f7#z^%?{^RdZ#a5ExdU4=;W)J{kg%}+|KmL@!Cn}VsyB?yno&8KG@xPIK9{D zogY2yewZFjZ$0fUU0hsu=XO`m|GhsN%${uSopi51K55NPukB6^mS0>vZx1)7XOG{m zEI)kOdH3M)+m}0E&VBvE`_@Kx=Da`HYyIh{wYvN2`_|d<{NDMa{h99G!~gZ|^yj7h zNBd_R{m$y>@sn16u{V6weK5OneDS*19-h2^aWvIl`*Qlt%JJUl^5usIoxRqBy>|EW z?&r1b{q5E6px1f3x!T`cdb}{2>1{7uwB~nuD>LWA+0P5T2XFdYTVKpyY@Gk)=l}QT zUnwDaMKjAWSxAZm--7BcSy*RdBE1e(3Pz9#F$N(S>X1;Slu4!_fs7tp@wTR0tn%V5>$ggL?msMB?lEzGl!@sWirYoiliwhOyJsp4m4Hm zHC1XqC{PcCxEK@)_l#iXYEG@Pnlq}nPj+Oes_+Cv38DGlRk?MMkW!+%E8rB~1VE9T zH9}$oA`$L|Dm4(q5xfG52G-2RI+#wZ15x@X@iGQ8%_2aND0aY<-L_jX!63(4$eIk;f5TTHSkh}o(RKD))) zYbTWx7e8!O>eg0mlB#p=J%8K%VVvwer_Q-`?_2c>W?HS0^hl4YrAMvj`8;o>Z_O0% zcV`M+%YD}`T)cRGJl9#C8n~00>&*>~eU!PgI(X{+p3c$EQtxExZhu>UrhjTR+v{{) zC}%V2z9Hw6+tY(<>D%oWt`uG$>&ZY0(n;?0q{TTb`=><6dM z{Q4h1{cB**s3Z;*d@ez zGd$vwC;@L(#_wVWBBzB8ISHMiP3 zvf?a_e$d@_D_ux`(sAnCwds}gmF|^Xe`mVAGu3&+xjeG6vC+AjDO^imxwSgAGM!y5 zkF1XN&84o)^cFt6yE;^IPEEb;jO8}^yKY~ZTuxmWN|)Q6;jTsJOlIR|>7!fAeSH&c zbGLdk%afP7PZf%(xs?aK9pz%rrE>P_>TBo9@ANLUcV&j1$t%~_+LlWL&d0BeLD&3d%%ZoSN?Nwd5ldPpF9r--ZK# z!3adS4NO{O65zG|fJF&J`gj!peygf89ZnxdPz;F<|1RBl@tb}H)JF|21bDm!4GO?4 ziywm%R9FaB=i?NzE{)dI!)0gHpY_;#cX|Q*!_;AUdQ{1ZP||Vh}8v zKn+)l12w=>>~^O*?$#+D-D>lRXG#+7FE(XgH3|Q!uaRDZofI(nJQ;4=p762P-qQ4R zoBkm~(_VXMZ6lf9*7SYeYN*}PO`GE6wa<9E3Z{9C&(>>h#=9(2Wtq$Wac#CiO?BRN zQCQQ8=OklUl_vSa#HR~WF^YnFUr6^5O+V<3o{`RO>g88%z(2Qn*Ij2akD)2dr5i-x zxmglH=ciNl!yDcI344^uh3sldN*%mEKy6G!5LfmkBxv~^HgxQ%TC@a)R@uCF^eS&y zoO_dZd7!o8u1{MQR^j|jq~VtdZfIkXR%Y+H>muE*iOrTT=|?nB+^$w6b*XWWoD=`~avvsV=% zaX5Ib7jQ5LfeLa8YXC$z0i!k~bl<5sk@D>Ce&az(mK9l9Uww%F1RRRy{oYE7Z)Cb7 zfKRK&M+sD?Rr}2l4`2x6FfEXr2G1jDXiND* z$wQbvwv)t*OckGiN~B*}nVKFy8VbY{Uw!+B4HA!JHNS*{BZypNy6Hqq_7S>j55DR~ z0Tmv5kb?)T0VKq{qPhy45+nlhB<&zV1-Lo8GGx&BK^2KZoDP9k#rwx>fLVv{KSf3K zSSgDdA?;K|x;#K~U>t|yOUQqv)O3K7$B<`v5SqwhK}qt4dTuX6msjEsOovQg?~smo zA=Z9DO1?%Y#Y5MMbOwMgk?*NWBA)C#rx5Y#nSkg<%MW+beK`yR=57K-=^pLOnP9J} zV;nl~d&=&Qwvw&FytqDdEzqcKp_&)0!<~H29Ew1MBzv>@`d> zN^CPv-=Zu-%fGax`muglzm+$Uk=T{@T$9<>1LunL+?RVXO&V=lMq!tletU{v*A>nX z|F+u`Zt3;+m$2%t-_wWnE%M7Y`&$g%Gp;6^*tc#+eku7+Y~!GNt)xYN27GO)yAAZO z_pvY6@z83Mo6crzIl=rTel)i>-QPbnII1z`6?md{Kj<~j#kjutssGTJ?;ge(BU*X zG1bfEY^uen&&-RvIbRGaGyvMa{{fW8RmVM)UIs@q$t^^bY;T$;EAHgc`)(ymm3MvT!``9@<1&GLNNWou# zaRk6b?zKAA;4zZfj~BdAc2&WE7;DAe@GMdXUi^bifN z=&bT9tDDmm+3OEYWgT#)=joF3*LGtK>HY3}7Bn;g2f3DIBJ%}IOtNo6pUq+{o=dV3 zICr@E=k11Ur>?2Gf1PfBF>@KnCwzS6fK_)wwuuXG!u%T~+{K%YUw*Dw*m^qdV;e|B zl$YpV^;$FI)0q5_3B!)Y_`?jVFdI9GI%5w#EvNr%9I+)+yRgJB8kKYD>G5M@hsB5K zvjav_p9n(s2-wQ(`t@e7@+OA{chB>u!-ebD#3jluARE#&jD;HKNTFG~C%E~d=CE5+ zZW&*H2haB50g&K-Q>IxTY7db?>*jK+)#Af?QU5q z$wLp3z(N|W_fSwF88`kHN<}<;2Z9>qwMPcjN^(iL)O=2!zj;;>I@F3V{@~&WXQjyY zxuci@c113HmVOowt#^PD1*a5IgD<5lO3dlXnsr;}foH5K7(e%Z^#o{c{ z4nP0CeE(&-TZL6~*cFf&uEY|Nt5a|i}?2*4zIPP82JgQh4$ zk{;97pHQZ?r#F`?1qo46x3{-F{}bH%ZJBm*?s#D#bo9bvbr#Mq5kWU0Dy#vDQ~hZA z5o8{z;|b$b;0C&MJtS;rR?x7;15VJq;^cuI#2=Al$3P&m9uRBpNfo|my}XwrkK$52 z+~9gNifa!PL8-Nh-^)kn)~?$H%t)c!PUuAe`|^_`C+G8BdUUvK&wl2}fvW4CPG%|4sPk zaAHM;=lF{V1^aWYO$QOJnhXYL&@yx$AJwdSM5)uKslzm%xQ z$&0JN0l@UNH0Nj725^N27Kqs6^VE4*H85=j9T2! zUq9UZIIn)Iu~d-|mB7|_F7$c3<{$lfMezy0)D-g)zr@_kjJb-0zoXFBiT&h8-$d$U z>|O|R%*T#3q$1C47^%lL+rMyC75cMjvPMYQ+}Mnf`+3K5c7CA!@3zB<5d&_qkaa}2 z^^eO_v_Y8m$|aM%$A;GUIBCoz3ORZ4OX+IT!427Qqia-e>{0b+(nv4+Z1ta4J9{&%{p_axnjEAna~-+*vvf@`q*y)UKB+sKYXO zAOQIUeg#pEX$MkL{tkzd_u!~gMg=s}pl-@aFGr9DY9s5=2=S^uL2W7tdS|vKDj+2^ z5&~q6fB;xB^wfc@_|O{QTcvOC6XdGeq_l*DBZpdAuP4=jr*OrqQSta(HJbiWajiAz zrM{u0C7pW+2PJ0+vv_>;VyKkla?rOKX<8m|Cz5VjaU7Mm%MoKa`a8X(!zP>(K?&}D~2gT9YokE{d zT(ylUrSFc5iq7Dre#WaR7_+FvarD{l-5NA04XV~H&@F&_PCMZwH3l)3BU+SJ;O6KDzVQ{wX{as zuvF;bH@RIN8@OU*q;>S*E5bB!wKbi7U50+Ov;!!~XYo%Gu>wh#-;`gKK!LR01&~;1 zJOUbT%b?m#fHa_rr|A~ZO3Qkd0#&1`=ac?ZQy9(?!Ri1cTJ(rhz&lFeF^Hyt%SWf_ zsgl#m*fl$bt0`LR+kpiM!MTp+U`=MZ{(D#mZw`r&ZKedTFZK*Wc}A`R^c_;>&*7eyg{=&%eM;xBH>~=+8M^el90| zr32#@vLUP8#sed0D@)^+l-=Fn6!pY!wo?2LDI_aqA56(K>_43(@Sn7=3OQPa>8p=g z@KKcJ7uP@APH^R^6pl$9&aNjWn476bdM~kMCn2>4xGdW~V+UcG%|@}L`5OLWeB`g6 zlG;~OtA{kDPR^UU+c(24xWuOr{zGC_{@UMtlhgU0<|kE#ylm237}Oc>AAQEg9Cnte z&4$4L=2*-QpmU95Wgv5k08z0yDEugz{G<}C+6juEmz0&26_*wN-wP+9zK*Ywlfz)v zF7KFf;+}cZP(l8CC9RJ3 zD;I7m@T2ZjHhHCJ7I6Pt5Z$|Yx>Rs6^(P&7&q$J9fOR+kPlCspau)sJLE2{r=lj2N z6K{_z_pPvdGJ)Ug>ZTn2{S%=}%+FWB0or0K-N}=1#3=P)!AH^&VLADzLt}s-FkE9s z5*Q)y(1r{^i60rA;RY~LJS-x_FO9D5J@iZvl?!cXzWkI(T-|(b^TF5uKp$gx7iCl- zceMKEvcvm+K!})__zsi756Z+d8lp&H+q%;hy;H&tklu&h)|d>0J@dJF)D;$X-;t=w zb9nLlS2egeJcScZMFsAR$DX%wJitI+j`z>#w}D83qO8Qna*o2Wy~8?A)iZw$9( zhAqy`Eumg~ZJF6|Lo`o0i1=Ye{mv{y?`LLXRmREhD=zbtSbvuiCh)P2QA()BK{?3* z+(4>6v?~4%&1>E?2%bC-Dnt>$Ll<0Z!)lg9_9c(q>Sh&3=yP!Rtb;GVz|4$+|2_X} zt&AsD`<|B0{)YVnn|UKDg!W$C!XHBH4)dk9c^Z~~XqxXfi--yZ+I`KV`?oV%Z(GVy z_=U(YKmDKj^$0S;WqTyM{#lvZy^yfSbz7y87tf=OF@3G3us@n#ZT>r0INlgbUHO)m zqecdoH5u|Lt92*Symfjmmd%x)O~?IM>oh&1#m#?(Pn`LW9%D$wVfT!pol9wJ&HPl0 z$R8g&egEN&2XkkA6KLUznSyNeA&!Sh(6`-ZW8OhRW~wa*JDRjLUrXRWwYN9iG$x$s z7Ao#@jp*8#Zi=6_UKyV(+jo)v<*>_Nh@0mfzq>B;Jb0x;X=WVJsU#2AQ+>>RK=6>{ z$aFZbPxX~JV22Y<6Rn+0Q6MYx;flGtD@;@9?Za+|4a8UL=g9CJ!0QwQN1pc|<;YD% zM5p*>KXicDj2M-J`}&38jeAX*HU&9(QX$jlIn*3D?l%y0$O2^5c?S|D$92lYEAt_f zzJGOq#OXQHdU4s?`@YgpRKhjl_R5J%N=jbt6y8sl2v($X@c zj5c-sw|G63xe|K&@2^8x(;%j%5Fnhn0D9|Ta+brX!@==-UiPfMQ`$O z>FD*sp?j;?sIvQ5D!KZ-?XF}SzhI9%5CJz3PmkyEkO}}ogrtbW7f^(Fs``w?Yt<|5 zeUS+I;B#?$9)#6GOwdK=<0PiRAG>5`O~E{IGqxZ#a^Q-R!>HkFPsr!b|#z>#fShgy)`Z}{u{W4VU6K1UT~PJ?zscB2`h!Hxqa1Wn8yw3eu}q6NK|+w9W_Z54R#%MNiOF ziv!|d-RJ47Akzj*9Kk(Um*f?Inlcsinn8rZ%B|z(r~|}r71ACYaPwV)9vb?Jz+sGx zH5SZHhiY64XZ@4INfsG?fCo>?RawEd-5hOX^4}jH*zVSy&rCf+vXKaZd%-AiysAZnTwOQ}=9^K5^fUP?D`6oYd``Zn_1f`JvJjzlH2w${VGenM-m-uNq2X`8 zfB*oz(WX?IeqyV=wi|xhvwJ$r(90vajjLh)j^N9UVWWF1IT=d%$6FCD#g3dGu0{(j z@8rOj{Avv!*W~EGj8>m%JE@i9u|71A|8f+?nFcOljZ3q35x}SV_!cO%NUGI~TkKOS ztO4#=Eis<8w6(3=j@9n%U9DfOH@E!wbtS!Uau#a8vK$;_Z(odCwSRkXP1woFk{!da zX=G;r*Z}^?hiV`$T8R}D-wlEu0Uky|YI;-XnbZ+Lr^jia6&02^c&dQdM4IhMVNU>J6T*yP-f!#y)Uw1um1y!uf(b03z><~yY9WJ zcult}x2v6*V`TawGF^`PT)hm5JXQ68WFH#?R2yp3rcC!x=K%+jaOOtT$& z4f{NO!Y!|_2Igk!7FiniJwwwTf})}pnq^W(9}QJ9YM17aRrNLL%N0D;Nafgsy~@f4 zWhp)~cz(o`MunIC%7{+@n|`pz_R;q2I6SsxAkW}_Ky3N`*y{f36_^fR`IY=*svhSe zfIRK1>E~WOZ^%*(GSaw7VFI7jf~OsD?$`;T>x$b7quC{8bo%#}7Bkcfze@f>LI{#F zPs1QG3h+CPN1uQS%)~=pjQ11YK9vBcaiJ$62wj{ToKZ&LHmEXj=}G?560>{djMM3L zNO~_APr(+*Lgz~+Uh*)Ymmr#x+}>70C7!-lRgDXsCjAhd@>bIhfkRXMJGasvHtk?Q z-_r3bsPXfCU#@%eLNb>(YH7r5Q%GzdGrJU&-~&`OL94H8??Y@FbZ_IW{%5(hTPqFy=@XZn&}cxt z7aH`!(z9uP%@<)Ba%vIs=0Dw#(B3VbG`k~XcS`GJ zRZ6PM6H7j)C=8o(FsO97a~*H-cOMYy&$9Rs#IoOn;afCYIcdG>4h*^8Qz^*r7+|W6 z#krOB;dR9A`e*($&OfYi63^tjGakIOk!OwEG$x?J#|L#OsKryoXUG;!o6;BK1*6a+ zq9&AUOTUlrFX#5d>Qi0sWA*kY&3Kwx=I#_%UfW+yURqiD6Dix1=39*UY&kvG zwT%l$&-9(MXT0j$>K09~+m@jowijMbe4>}Dx9#oih55WAWH4ToB-d8G_wCR{%)79! zJ}8~R0?RhhEZ!>eFJa56Y7N>27NI?tzE)d56Q?f*k;@Y`XB&CKXZ_|odMciO#-@pdpt>q$$qD@eRGoS65xmy6Z5q>(puHr@w-}!IvLli*tlDGl+P%(FS$1>d$;