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/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 + diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..2a8187d --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +#github: tanthanhdev +#ko_fi: devphan +custom: ["https://www.paypal.me/ThanhPhan481"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..75e1465 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: Markdown CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + 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/.github/workflows/update-frontmatter.yml b/.github/workflows/update-frontmatter.yml new file mode 100644 index 0000000..1a187f3 --- /dev/null +++ b/.github/workflows/update-frontmatter.yml @@ -0,0 +1,60 @@ +name: Update Markdown Frontmatter + +on: + push: + paths: + - 'docs/**/*.md' + - 'i18n/**/*.md' + pull_request: + paths: + - 'docs/**/*.md' + - 'i18n/**/*.md' + schedule: + # Run at 00:00 UTC every Monday + - cron: '0 0 * * 1' + # Allow manual runs from GitHub interface + workflow_dispatch: + +jobs: + update-frontmatter: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + # Fetch full history to allow creating commits + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm install gray-matter glob + + - name: Run update script + run: | + chmod +x tools/update-frontmatter.js + node tools/update-frontmatter.js + + - name: Check for changes + id: git-check + run: | + git diff --exit-code || echo "changes=true" >> $GITHUB_OUTPUT + + - name: Commit changes if any + if: steps.git-check.outputs.changes == 'true' + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add -A + git commit -m "chore: update markdown frontmatter [skip ci]" + + - name: Push changes + if: steps.git-check.outputs.changes == 'true' + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffb7886 --- /dev/null +++ b/.gitignore @@ -0,0 +1,92 @@ +# Node modules (if using any JS tools) +node_modules/ + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# C# +bin/ +obj/ +*.user +*.suo +*.userprefs +*.sln.docstates +.vs/ + +# 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 + +# 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/ + +# Docker +.docker-env +.docker-cache +docker-compose.override.yml + +# Environment variables +.env.local +.env.* +.env +!.env.example +docker/environments/databases/.env + +# Not follow website +!website/ \ No newline at end of file diff --git a/.next/trace b/.next/trace new file mode 100644 index 0000000..2fe9d4c --- /dev/null +++ b/.next/trace @@ -0,0 +1 @@ +[{"name":"next-dev","duration":336397,"timestamp":257582122435,"id":1,"tags":{},"startTime":1749254546436,"traceId":"a69c61c90c63e8b2"}] diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..fc17f9a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,52 @@ +# Tech Notes Hub Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender characteristics, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [CONTACT EMAIL]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +## Summary + +* Treat everyone with respect and courtesy +* Gracefully accept constructive feedback +* Focus on helping others and growing the community +* Don't use offensive language or imagery +* Don't harass, threaten, or discriminate against anyone +* If you witness inappropriate behavior, report it to project maintainers diff --git a/CODE_OF_CONDUCT_vi.md b/CODE_OF_CONDUCT_vi.md new file mode 100644 index 0000000..d7d7a31 --- /dev/null +++ b/CODE_OF_CONDUCT_vi.md @@ -0,0 +1,52 @@ +# Quy tắc Ứng xử của Tech Notes Hub + +## Cam kết của chúng tôi + +Nhằm thúc đẩy một môi trường mở và thân thiện, chúng tôi - những người đóng góp và duy trì dự án - cam kết đảm bảo việc tham gia vào dự án và cộng đồng của chúng tôi là một trải nghiệm không quấy rối cho tất cả mọi người, bất kể tuổi tác, kích thước cơ thể, khuyết tật, dân tộc, đặc điểm giới tính, bản dạng và biểu hiện giới tính, trình độ kinh nghiệm, quốc tịch, ngoại hình cá nhân, chủng tộc, tôn giáo, hoặc bản dạng và khuynh hướng tình dục. + +## Tiêu chuẩn của chúng tôi + +Các ví dụ về hành vi góp phần tạo ra môi trường tích cực bao gồm: + +* Sử dụng ngôn ngữ chào đón và hòa nhập +* Tôn trọng các quan điểm và kinh nghiệm khác nhau +* Nhã nhặn chấp nhận phê bình mang tính xây dựng +* Tập trung vào điều tốt nhất cho cộng đồng +* Thể hiện sự đồng cảm với các thành viên khác trong cộng đồng + +Các ví dụ về hành vi không thể chấp nhận từ người tham gia bao gồm: + +* Việc sử dụng ngôn ngữ hoặc hình ảnh mang tính gợi dục và sự chú ý hoặc tiến triển tình dục không được chào đón +* Trolling, bình luận xúc phạm/miệt thị, và tấn công cá nhân hoặc mang tính chính trị +* Quấy rối công khai hoặc riêng tư +* Công bố thông tin cá nhân của người khác, chẳng hạn như địa chỉ vật lý hoặc điện tử, mà không được phép rõ ràng +* Các hành vi khác có thể hợp lý được coi là không phù hợp trong môi trường chuyên nghiệp + +## Trách nhiệm của chúng tôi + +Người duy trì dự án có trách nhiệm làm rõ các tiêu chuẩn về hành vi có thể chấp nhận được và được mong đợi sẽ thực hiện các hành động khắc phục phù hợp và công bằng để đáp lại bất kỳ trường hợp hành vi không thể chấp nhận nào. + +Người duy trì dự án có quyền và trách nhiệm xóa, chỉnh sửa hoặc từ chối nhận xét, commit, mã, chỉnh sửa wiki, issues và các đóng góp khác không phù hợp với Quy tắc Ứng xử này, hoặc tạm thời hoặc vĩnh viễn cấm bất kỳ người đóng góp nào vì các hành vi khác mà họ cho là không phù hợp, đe dọa, xúc phạm hoặc có hại. + +## Phạm vi + +Quy tắc Ứng xử này áp dụng trong cả không gian dự án và không gian công cộng khi một cá nhân đại diện cho dự án hoặc cộng đồng của dự án. Ví dụ về việc đại diện cho một dự án hoặc cộng đồng bao gồm việc sử dụng địa chỉ email chính thức của dự án, đăng thông qua tài khoản mạng xã hội chính thức, hoặc làm đại diện được chỉ định tại một sự kiện trực tuyến hoặc ngoại tuyến. Đại diện của một dự án có thể được người duy trì dự án xác định và làm rõ thêm. + +## Thực thi + +Các trường hợp hành vi lạm dụng, quấy rối hoặc không thể chấp nhận khác có thể được báo cáo bằng cách liên hệ với nhóm dự án tại [EMAIL LIÊN HỆ]. Tất cả khiếu nại sẽ được xem xét và điều tra, và sẽ dẫn đến phản hồi được coi là cần thiết và phù hợp với hoàn cảnh. Nhóm dự án có nghĩa vụ duy trì tính bảo mật đối với người báo cáo về một sự cố. Các chi tiết cụ thể hơn về chính sách thực thi có thể được đăng riêng. + +Người duy trì dự án không tuân theo hoặc thực thi Quy tắc Ứng xử một cách thiện chí có thể phải đối mặt với hậu quả tạm thời hoặc vĩnh viễn do các thành viên lãnh đạo khác của dự án quyết định. + +## Ghi nhận + +Quy tắc Ứng xử này được điều chỉnh từ [Contributor Covenant](https://www.contributor-covenant.org), phiên bản 1.4, có tại https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +## Tóm tắt + +* Đối xử với mọi người bằng sự tôn trọng và lịch sự +* Chấp nhận các góp ý mang tính xây dựng một cách nhã nhặn +* Tập trung vào việc giúp đỡ người khác và phát triển cộng đồng +* Không sử dụng ngôn ngữ hoặc hình ảnh xúc phạm +* Không quấy rối, đe dọa hoặc phân biệt đối xử với bất kỳ ai +* Nếu bạn chứng kiến hành vi không phù hợp, hãy báo cáo cho người quản lý dự án diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a764099..fbbe7f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,48 +1,89 @@ # 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-hub.git +git clone https://github.com/your-username/tech-notes.git 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` +- `refactor: reorganize folder structure` +- `remove: delete duplicate notes in aws folder` +- `docs: add instructions for creating pull requests` + +#### 🧠 Additional Tips: + +- 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` ### 5. Commit & Push @@ -52,36 +93,50 @@ 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` (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`, but no suffix is needed if in the /i18n/[language_code]/ folder +* Notes should be grouped by docs folders (e.g., `docs/algorithms/`, `docs/aws/`, `docs/design-patterns/`) + +### 📂 Code Snippets Structure + +Code snippets must follow this directory structure for proper display on the website: + +``` +snippets/[category]/[topic-name]/[filename].[extension] +``` + +Examples: +- `snippets/algorithms/graph-traversal/graph_traversal.py` +- `snippets/devops/ci-cd/ci-cd.sh` +- `snippets/databases/mongodb/mongodb_query.js` + +The `[topic-name]` folder must match the slug of your blog post for snippets to appear automatically. All files inside this folder will be displayed as code snippets on the corresponding blog post page. ## 🤝 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/tanthanhdev/tech-notes-hub/issues) if you have questions, ideas, or feedback. - -Thanks for contributing to **Tech Notes Hub**! 🙌 +If you have questions or ideas, please [create a new issue](https://github.com/tech-notes-hub/tech-notes/issues). -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. +Thank you again for contributing to **Tech Notes Hub**! 🙌 diff --git a/CONTRIBUTING_vi.md b/CONTRIBUTING_vi.md index df4d326..6c2710c 100644 --- a/CONTRIBUTING_vi.md +++ b/CONTRIBUTING_vi.md @@ -1,88 +1,142 @@ -# Đóng góp vào Tech Notes Hub +# Đóng góp cho 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! 🎉 -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. +Trước hết, 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 giá trị hơn cho cộng đồng lập trình viên. -## 🚀 Cách bạn có thể đóng góp +## 🚀 Cách Đóng góp -Có nhiều hình thức để bạn tham gia: +Có nhiều cách để bạn tham gia: -- 📚 Thêm ghi chú kỹ thuật hoặc chủ đề mới -- 💡 Cải thiện các phần giải thích hoặc đoạn mã hiện có -- 🐛 Báo lỗi hoặc đề xuất cải tiến -- ✨ Dọn dẹp, chuẩn hóa định dạng nội dung -- 🌍 Dịch các ghi chú sang ngôn ngữ khác (tính năng sắp ra mắt) +- 📚 Thêm các ghi chú kỹ thuật hoặc chủ đề mới +- 💡 Cải thiện giải thích hoặc code snippets hiện có +- 🐛 Báo cáo lỗi hoặc đề xuất cải tiến +- ✨ Dọn dẹp và chuẩn hóa định dạng nội dung +- 🌍 Dịch ghi chú sang các ngôn ngữ khác -## 📝 Hướng dẫn đóng góp +## 📝 Hướng dẫn Đóng góp -Vui lòng tuân theo các hướng dẫn sau để đảm bảo tính nhất quán và dễ quản lý: +Vui lòng tuân thủ các hướng dẫn sau để đảm bảo tính nhất quán và khả năng bảo trì: ### 1. Fork Repository -Nhấn nút "Fork" ở góc trên bên phải trang GitHub để tạo một bản sao của repository vào tài khoản của bạn. +Nhấp vào nút "Fork" ở góc trên bên phải của trang GitHub để tạo bản sao của repository trong tài khoản của bạn. -### 2. Clone về máy +### 2. Clone về Máy của Bạn ```bash -git clone https://github.com/ten-cua-ban/tech-notes-hub.git +git clone https://github.com/your-username/tech-notes.git cd tech-notes-hub ``` -### 3. Tạo nhánh mới +### 3. Tạo Nhánh Mới -Đặ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: +Đặt tên nhánh của bạn rõ ràng, mô tả ngắn gọn những gì bạn sẽ thêm hoặc sửa đổi: ```bash -git checkout -b feature/them-thuat-toan-do-thi +git checkout -b feature/add-graph-algorithms ``` -### 4. Thực hiện thay đổi +### 🧩 Quy tắc Đặt tên Nhánh -* Tuân theo cấu trúc thư mục và file có sẵn -* Ghi chú dùng định dạng Markdown (`.md`) -* Mã nguồn nên đặt trong khối mã (fenced code block) ví dụ: \`\`\`python -* 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 +Tên nhánh nên tuân theo cấu trúc này: + +```bash +/ +``` + +| Loại | Ý nghĩa | Ví dụ | +| ---------- | ---------------------------------------------------- | --------------------------------- | +| `feature` | Thêm ghi chú/phần mới | `feature/add-docker-notes` | +| `fix` | Sửa lỗi nội dung, lỗi chính tả, ví dụ | `fix/typo-in-graph-note` | +| `update` | Cập nhật hoặc cải thiện ghi chú hiện có | `update/aws-ec2-note` | +| `refactor` | Tái cấu trúc tệp/nội dung mà không thay đổi ý chính | `refactor/reorganize-folders` | +| `remove` | Xóa nội dung lỗi thời hoặc không 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 của Bạn + +* Tuân theo cấu trúc thư mục và tệp hiện có +* Ghi chú nên sử dụng định dạng Markdown (`.md`) +* Code nên được đặt trong khối code có hàng rào, ví dụ: \`\`\`python +* Giữ các giải thích ngắn gọn và rõ ràng +* Thêm nhận xét nội tuyến nếu cần thiết + +### 💬 Quy tắc Viết Commit Message + +Viết các commit message 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 lỗi chính tả trong design-patterns.md` +- `update: cải thiện ví dụ tìm kiếm nhị phân` +- `refactor: sắp xếp lại cấu trúc thư mục` +- `remove: xóa ghi chú trùng lặp trong thư mục aws` +- `docs: thêm hướng dẫn tạo pull request` + +#### 🧠 Mẹo Bổ sung: + +- Bạn có thể viết bằng **tiếng Anh** hoặc **tiếng Việt** +- **Tránh commit mơ hồ** như: `update 1`, `fix bug`, `test` +- Nếu liên quan đến issue, thêm số issue vào cuối: + 👉 `fix: lỗi chính tả trong aws-note #12` ### 5. Commit & Push ```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 -Quay lại repository gốc và nhấn **"Compare & Pull Request"**. Nhớ điền: +Quay lại repository gốc và nhấp vào **"Compare & Pull Request"**. Hãy nhớ bao gồm: -* Tiêu đề ngắn gọn, rõ ràng -* Mô tả chi tiết về nội dung bạn thêm/sửa -* Đề cập đến issue liên quan nếu có +* Tiêu đề rõ ràng, ngắn gọn +* Mô tả chi tiết về những gì bạn đã thêm/sửa đổi +* Tham chiếu đến các issue liên quan nếu có -## ✅ Kiểm tra trước khi gửi Pull Request +## ✅ Danh sách kiểm tra trước khi Gửi Pull Request Trước khi gửi, hãy đảm bảo: -* [ ] Định dạng nội dung đúng và theo cấu trúc dự án +* [ ] Nội dung được định dạng đúng và tuân theo cấu trúc dự án * [ ] Không có lỗi chính tả hoặc liên kết hỏng -* [ ] Đoạn mã (nếu có) hoạt động chính xác -* [ ] Không chứa thông tin nhạy cảm hoặc tài sản độc quyền +* [ ] Các đoạn mã (nếu có) hoạt động đúng +* [ ] Không có thông tin nhạy cảm hoặc tài sản độc quyền được đưa vào -## 📁 Quy tắc đặt tên file & thư mục +## 📁 Quy ước Đặt tên Tệp & 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` -* Ghi chú nên được nhóm theo thư mục chuyên đề (ví dụ: `algorithms/`, `aws/`, `design-patterns/`) +* Sử dụng chữ thường và dấu gạch ngang cho tên tệp và thư mục: `graph_traversal.md` (ngoại trừ các tệp mã như C# sử dụng PascalCase như `GraphTraversal.cs`, hoặc Java sử dụng camelCase như `GraphTraversal.java`) +* Đối với bản dịch, thêm hậu tố ngôn ngữ: `graph_traversal_vi.md`, nhưng không cần hậu tố nếu trong thư mục /i18n/[mã_ngôn_ngữ]/ +* Ghi chú nên được nhóm theo các thư mục docs (ví dụ: `docs/algorithms/`, `docs/aws/`, `docs/design-patterns/`) -## 🤝 Quy tắc ứng xử +### 📂 Cấu trúc Code Snippets -Tôn trọng, cởi mở và xây dựng trong mọi tương tác. Chúng ta đang xây dựng một không gian học tập thân thiện và hòa nhập cho tất cả lập trình viên. +Code snippets phải tuân theo cấu trúc thư mục này để hiển thị đúng trên website: -## 📩 Cần hỗ trợ? +``` +snippets/[danh-mục]/[tên-chủ-đề]/[tên-tệp].[phần-mở-rộng] +``` -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). +Ví dụ: +- `snippets/algorithms/graph-traversal/graph_traversal.py` +- `snippets/devops/ci-cd/ci-cd.sh` +- `snippets/databases/mongodb/mongodb_query.js` -Một lần nữa, cảm ơn bạn đã đóng góp cho **Tech Notes Hub**! 🙌 +Thư mục `[tên-chủ-đề]` phải khớp với slug của bài viết blog để snippets hiển thị tự động. Tất cả các tệp trong thư mục này sẽ được hiển thị dưới dạng code snippets trên trang bài viết blog tương ứng. -``` +## 🤝 Quy tắc Ứng xử + +Hãy tôn trọng, cởi mở và mang tính xây dựng trong tất cả các tương tác. Chúng tôi đang xây dựng một không gian học tập thân thiện và hòa nhập cho tất cả các lập trình viên. + +## 📩 Cần Trợ giúp? + +Nếu bạn có câu hỏi hoặc ý tưởng, vui lòng [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**! 🙌 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/PULL_REQUEST_RULES.md b/PULL_REQUEST_RULES.md index c9eb636..c0ce0cb 100644 --- a/PULL_REQUEST_RULES.md +++ b/PULL_REQUEST_RULES.md @@ -6,6 +6,36 @@ 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 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,10 +52,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 @@ -36,23 +65,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` +- 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 ## 📢 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 bea746b..d20bfa7 100644 --- a/PULL_REQUEST_RULES_vi.md +++ b/PULL_REQUEST_RULES_vi.md @@ -6,6 +6,36 @@ 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 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,10 +52,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/es/`) - 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 @@ -36,23 +65,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: `cay-nhi-phan.md` -- 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/`,...) - ---- +- Đặ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 ## 📢 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 8d280b4..5ae244c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -# 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. +📄 This README is also available in other languages: + +- 🇻🇳 [Tiếng Việt](README_vi.md) + --- ## 📚 Overview @@ -19,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 @@ -29,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. @@ -40,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 @@ -49,13 +47,51 @@ 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. ---- +**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). + +### 🛠️ Utilities + +This repository contains utility scripts in the `/tools` directory to help maintain the documentation. + +#### Updating Markdown Frontmatter + +The `update-frontmatter.js` script automatically updates metadata in Markdown files: + +```bash +# First, install Node.js dependencies +npm install + +# Run the script (updates all Markdown files) +node tools/update-frontmatter.js + +# Update a specific file +node tools/update-frontmatter.js algorithms/sorting-algorithms.md +``` + +#### Generating Table of Contents + +The `generate_summary.py` script automatically creates the SUMMARY.md file: + +```bash +# Run the script to generate a new table of contents +python tools/generate_summary.py +``` + +For more information about available tools, see the [Tools README](tools/README.md). ## 🤝 Contribution @@ -67,20 +103,20 @@ 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 -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. ---- +## 📝 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. ---- - ## 📬 Contact If you have any questions or suggestions, feel free to open an issue or contact the maintainer: diff --git a/README_WEBSITE.md b/README_WEBSITE.md new file mode 100644 index 0000000..72207f9 --- /dev/null +++ b/README_WEBSITE.md @@ -0,0 +1,41 @@ +# 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**: 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**: Meta tags and OpenGraph support for better search engine visibility +- **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 + +## 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/README_vi.md b/README_vi.md new file mode 100644 index 0000000..65d70ca --- /dev/null +++ b/README_vi.md @@ -0,0 +1,129 @@ +# 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. + +📄 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ế. + +**Để 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). + +### 🛠️ Công cụ tiện ích + +Kho lưu trữ này chứa các script tiện ích trong thư mục `/tools` để hỗ trợ việc duy trì tài liệu. + +#### Cập nhật Frontmatter cho file Markdown + +Script `update-frontmatter.js` tự động cập nhật metadata trong các file Markdown: + +```bash +# Đầu tiên, cài đặt các gói phụ thuộc Node.js +npm install + +# Chạy script (cập nhật tất cả file Markdown) +node tools/update-frontmatter.js + +# Cập nhật một file cụ thể +node tools/update-frontmatter.js algorithms/sorting-algorithms.md +``` + +#### Tạo mục lục tự động + +Script `generate_summary.py` tự động tạo file SUMMARY.md: + +```bash +# Chạy script để tạo mục lục mới +python tools/generate_summary.py +``` + +Để biết thêm thông tin về các công cụ có sẵn, hãy xem [README của Tools](tools/README.md). + +## 🤝 Đó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). + +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.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. + +## 📬 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ẻ!** 🚀 diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..f2396d3 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,35 @@ +# Tech Notes Hub + +## Table of Contents + +### Algorithms + +- [Graph Traversal Algorithms](docs/algorithms/graph-traversal.md) +- [Sorting Algorithms](docs/algorithms/sorting-algorithms.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/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..1a0b754 --- /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="${MYSQL_PORT_EXPOSE:-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="${POSTGRES_PORT_EXPOSE:-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="${MONGO_PORT_EXPOSE:-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="${REDIS_PORT_EXPOSE:-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/docs/README.md b/docs/README.md new file mode 100644 index 0000000..dac2e6e --- /dev/null +++ b/docs/README.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..009b0cf --- /dev/null +++ b/docs/algorithms/graph-traversal.md @@ -0,0 +1,156 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Graph Traversal +description: Guide about Graph Traversal +--- +# 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. diff --git a/docs/algorithms/sorting-algorithms.md b/docs/algorithms/sorting-algorithms.md new file mode 100644 index 0000000..afc7ae7 --- /dev/null +++ b/docs/algorithms/sorting-algorithms.md @@ -0,0 +1,130 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Sorting Algorithms +description: Guide about Sorting Algorithms +--- +# Sorting Algorithms + +Sorting is one of the most fundamental operations in computer science. This article explores various sorting algorithms, their implementations, and performance characteristics. + +## Quick Sort + +Quick Sort is a divide-and-conquer algorithm that works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays according to whether they are less than or greater than the pivot. + +```python +def quick_sort(arr): + if len(arr) <= 1: + return arr + pivot = arr[len(arr) // 2] + left = [x for x in arr if x < pivot] + middle = [x for x in arr if x == pivot] + right = [x for x in arr if x > pivot] + return quick_sort(left) + middle + quick_sort(right) + +# Example usage +arr = [3, 6, 8, 10, 1, 2, 1] +print(quick_sort(arr)) # Output: [1, 1, 2, 3, 6, 8, 10] +``` + +**Time Complexity**: +- Best Case: O(n log n) +- Average Case: O(n log n) +- Worst Case: O(n²) + +## Merge Sort + +Merge Sort is another divide-and-conquer algorithm that divides the input array into two halves, recursively sorts them, and then merges the sorted halves. + +```python +def merge_sort(arr): + if len(arr) <= 1: + return arr + + mid = len(arr) // 2 + left = merge_sort(arr[:mid]) + right = merge_sort(arr[mid:]) + + return merge(left, right) + +def merge(left, right): + result = [] + i = j = 0 + + while i < len(left) and j < len(right): + if left[i] < right[j]: + result.append(left[i]) + i += 1 + else: + result.append(right[j]) + j += 1 + + result.extend(left[i:]) + result.extend(right[j:]) + return result +``` + +**Time Complexity**: +- Best Case: O(n log n) +- Average Case: O(n log n) +- Worst Case: O(n log n) + +## Bubble Sort + +Bubble Sort is a simple comparison-based algorithm that repeatedly steps through the list, compares adjacent elements, and swaps them if they are in the wrong order. + +```python +def bubble_sort(arr): + n = len(arr) + for i in range(n): + # Flag to optimize if array is already sorted + swapped = False + + for j in range(0, n - i - 1): + if arr[j] > arr[j + 1]: + arr[j], arr[j + 1] = arr[j + 1], arr[j] + swapped = True + + # If no swapping occurred in this pass, array is sorted + if not swapped: + break + + return arr +``` + +**Time Complexity**: +- Best Case: O(n) - when array is already sorted +- Average Case: O(n²) +- Worst Case: O(n²) + +## Comparison of Sorting Algorithms + +| Algorithm | Time Complexity (Best) | Time Complexity (Average) | Time Complexity (Worst) | Space Complexity | Stable | +|-----------|------------------------|---------------------------|-------------------------|-----------------|--------| +| Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n) | No | +| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes | +| Bubble Sort | O(n) | O(n²) | O(n²) | O(1) | Yes | +| Insertion Sort | O(n) | O(n²) | O(n²) | O(1) | Yes | +| Selection Sort | O(n²) | O(n²) | O(n²) | O(1) | No | +| Heap Sort | O(n log n) | O(n log n) | O(n log n) | O(1) | No | +| Counting Sort | O(n+k) | O(n+k) | O(n+k) | O(k) | Yes | +| Radix Sort | O(nk) | O(nk) | O(nk) | O(n+k) | Yes | + +Where: +- n is the number of elements +- k is the range of the input + +## When to Use Each Algorithm + +- **Quick Sort**: General-purpose sorting, works well for arrays that fit in memory +- **Merge Sort**: When stability is needed and O(n log n) worst-case is required +- **Bubble Sort**: Educational purposes or very small datasets +- **Insertion Sort**: Small datasets or nearly sorted arrays +- **Heap Sort**: When consistent performance is needed without worst-case scenarios +- **Counting/Radix Sort**: When the range of input values is limited + +## Conclusion + +Choosing the right sorting algorithm depends on your specific requirements, including the size of the dataset, memory constraints, and whether stability is important. In practice, most programming languages implement hybrid sorting algorithms that combine the benefits of different approaches. diff --git a/docs/databases/relational.md b/docs/databases/relational.md new file mode 100644 index 0000000..518babb --- /dev/null +++ b/docs/databases/relational.md @@ -0,0 +1,125 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Relational +description: Guide about Relational +--- +# 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" diff --git a/docs/design-patterns/factory.md b/docs/design-patterns/factory.md new file mode 100644 index 0000000..121e454 --- /dev/null +++ b/docs/design-patterns/factory.md @@ -0,0 +1,484 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Factory +description: Guide about Factory +--- +# 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) diff --git a/docs/design-patterns/observer.md b/docs/design-patterns/observer.md new file mode 100644 index 0000000..234896d --- /dev/null +++ b/docs/design-patterns/observer.md @@ -0,0 +1,412 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Observer +description: Guide about Observer +--- +# 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) diff --git a/docs/design-patterns/singleton.md b/docs/design-patterns/singleton.md new file mode 100644 index 0000000..4f6e003 --- /dev/null +++ b/docs/design-patterns/singleton.md @@ -0,0 +1,234 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Singleton +description: Guide about Singleton +--- +# 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) diff --git a/docs/devops/ci-cd.md b/docs/devops/ci-cd.md new file mode 100644 index 0000000..437396e --- /dev/null +++ b/docs/devops/ci-cd.md @@ -0,0 +1,170 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Ci Cd +description: Guide about Ci Cd +--- +# 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..6144822 --- /dev/null +++ b/docs/linux/bash-scripting.md @@ -0,0 +1,333 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Bash Scripting +description: Guide about Bash Scripting +--- +# 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..ab48962 --- /dev/null +++ b/docs/system-design/microservices.md @@ -0,0 +1,475 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Microservices +description: Guide about Microservices +--- +# 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..c0c5cb0 --- /dev/null +++ b/docs/testing/unit-testing.md @@ -0,0 +1,475 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Unit Testing +description: Guide about Unit Testing +--- +# 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/init.txt b/i18n/init.txt new file mode 100644 index 0000000..e69de29 diff --git a/i18n/vi/README.md b/i18n/vi/README.md new file mode 100644 index 0000000..1980eb0 --- /dev/null +++ b/i18n/vi/README.md @@ -0,0 +1,53 @@ +# Tech Notes Hub + +Chào mừng đến với Tech Notes Hub! Đây là kho lưu trữ toàn diện các ghi chú kỹ thuật, đoạn mã và ví dụ bao gồm nhiều chủ đề trong phát triển phần mềm, thiết kế hệ thống và khoa học máy tính. + +## Mục đích + +Tech Notes Hub nhằm mục đích: + +- Cung cấp các giải thích rõ ràng, ngắn gọn về các khái niệm kỹ thuật quan trọng +- Cung cấp các ví dụ mã thực tế bằng nhiều ngôn ngữ lập trình +- Phục vụ như một tài liệu tham khảo cho các nhà phát triển ở mọi cấp độ kỹ năng +- Tạo ra một cơ sở kiến thức hợp tác cho cộng đồng công nghệ + +## Cấu trúc kho lưu trữ + +Kho lưu trữ được tổ chức thành các phần chính sau: + +### Algorithms (Thuật toán) +Cài đặt và giải thích về các thuật toán phổ biến, bao gồm tìm kiếm, sắp xếp, duyệt đồ thị, và nhiều hơn nữa. + +### Databases (Cơ sở dữ liệu) +Ghi chú về hệ thống cơ sở dữ liệu, tối ưu hóa truy vấn, mô hình hóa dữ liệu và các phương pháp tốt nhất cho cả cơ sở dữ liệu SQL và NoSQL. + +### Design Patterns (Mẫu thiết kế) +Giải thích chi tiết và cài đặt các mẫu thiết kế phần mềm trên nhiều ngôn ngữ lập trình. + +### DevOps +Thông tin về tích hợp liên tục, triển khai, container hóa và quản lý cơ sở hạ tầng. + +### Linux +Hướng dẫn về quản trị hệ thống Linux, lập trình shell và các công cụ dòng lệnh. + +### System Design (Thiết kế hệ thống) +Các cách tiếp cận để thiết kế các hệ thống phần mềm có khả năng mở rộng, đáng tin cậy và dễ bảo trì. + +### Testing (Kiểm thử) +Các phương pháp tốt nhất cho kiểm thử đơn vị, kiểm thử tích hợp và phát triển hướng kiểm thử. + +## Cách sử dụng kho lưu trữ này + +Mỗi phần chứa các tệp markdown với giải thích và đoạn mã. Bạn có thể: + +1. Duyệt qua các phần để tìm chủ đề quan tâm +2. Sử dụng các ví dụ mã làm tham khảo cho dự án của riêng bạn +3. Đóng góp bằng cách thêm nội dung mới hoặc cải thiện tài liệu hiện có + +## Đóng góp + +Chúng tôi hoan nghênh đóng góp từ cộng đồng! Vui lòng xem tệp [CONTRIBUTING_vi.md](../CONTRIBUTING_vi.md) của chúng tôi để biết hướng dẫn về cách đóng góp. + +## Giấy phép + +Dự án này được cấp phép theo Giấy phép MIT - xem tệp [LICENSE.txt](../LICENSE.txt) để biết chi tiết. diff --git a/i18n/vi/algorithms/graph-traversal.md b/i18n/vi/algorithms/graph-traversal.md new file mode 100644 index 0000000..840ac1c --- /dev/null +++ b/i18n/vi/algorithms/graph-traversal.md @@ -0,0 +1,156 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Graph Traversal +description: Guide about Graph Traversal +--- +# 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. diff --git a/i18n/vi/algorithms/sorting-algorithms.md b/i18n/vi/algorithms/sorting-algorithms.md new file mode 100644 index 0000000..3eca2ab --- /dev/null +++ b/i18n/vi/algorithms/sorting-algorithms.md @@ -0,0 +1,130 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Sorting Algorithms +description: Guide about Sorting Algorithms +--- +# Thuật toán sắp xếp + +Sắp xếp là một trong những hoạt động cơ bản nhất trong khoa học máy tính. Bài viết này khám phá các thuật toán sắp xếp khác nhau, cách triển khai và đặc điểm hiệu suất của chúng. + +## Quick Sort (Sắp xếp nhanh) + +Quick Sort là một thuật toán chia để trị hoạt động bằng cách chọn một phần tử 'trục' từ mảng và phân vùng các phần tử khác thành hai mảng con theo điều kiện chúng nhỏ hơn hoặc lớn hơn trục. + +```python +def quick_sort(arr): + if len(arr) <= 1: + return arr + pivot = arr[len(arr) // 2] + left = [x for x in arr if x < pivot] + middle = [x for x in arr if x == pivot] + right = [x for x in arr if x > pivot] + return quick_sort(left) + middle + quick_sort(right) + +# Ví dụ sử dụng +arr = [3, 6, 8, 10, 1, 2, 1] +print(quick_sort(arr)) # Kết quả: [1, 1, 2, 3, 6, 8, 10] +``` + +**Độ phức tạp thời gian**: +- Trường hợp tốt nhất: O(n log n) +- Trường hợp trung bình: O(n log n) +- Trường hợp xấu nhất: O(n²) + +## Merge Sort (Sắp xếp trộn) + +Merge Sort là một thuật toán chia để trị khác, chia mảng đầu vào thành hai nửa, sắp xếp đệ quy chúng, và sau đó trộn các nửa đã sắp xếp. + +```python +def merge_sort(arr): + if len(arr) <= 1: + return arr + + mid = len(arr) // 2 + left = merge_sort(arr[:mid]) + right = merge_sort(arr[mid:]) + + return merge(left, right) + +def merge(left, right): + result = [] + i = j = 0 + + while i < len(left) and j < len(right): + if left[i] < right[j]: + result.append(left[i]) + i += 1 + else: + result.append(right[j]) + j += 1 + + result.extend(left[i:]) + result.extend(right[j:]) + return result +``` + +**Độ phức tạp thời gian**: +- Trường hợp tốt nhất: O(n log n) +- Trường hợp trung bình: O(n log n) +- Trường hợp xấu nhất: O(n log n) + +## Bubble Sort (Sắp xếp nổi bọt) + +Bubble Sort là một thuật toán đơn giản dựa trên so sánh, lặp đi lặp lại qua danh sách, so sánh các phần tử liền kề và hoán đổi chúng nếu chúng không đúng thứ tự. + +```python +def bubble_sort(arr): + n = len(arr) + for i in range(n): + # Cờ để tối ưu hóa nếu mảng đã được sắp xếp + swapped = False + + for j in range(0, n - i - 1): + if arr[j] > arr[j + 1]: + arr[j], arr[j + 1] = arr[j + 1], arr[j] + swapped = True + + # Nếu không có hoán đổi nào xảy ra trong lần này, mảng đã được sắp xếp + if not swapped: + break + + return arr +``` + +**Độ phức tạp thời gian**: +- Trường hợp tốt nhất: O(n) - khi mảng đã được sắp xếp +- Trường hợp trung bình: O(n²) +- Trường hợp xấu nhất: O(n²) + +## So sánh các thuật toán sắp xếp + +| Thuật toán | Độ phức tạp (Tốt nhất) | Độ phức tạp (Trung bình) | Độ phức tạp (Xấu nhất) | Độ phức tạp không gian | Ổn định | +|------------|------------------------|--------------------------|------------------------|------------------------|---------| +| Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n) | Không | +| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Có | +| Bubble Sort | O(n) | O(n²) | O(n²) | O(1) | Có | +| Insertion Sort | O(n) | O(n²) | O(n²) | O(1) | Có | +| Selection Sort | O(n²) | O(n²) | O(n²) | O(1) | Không | +| Heap Sort | O(n log n) | O(n log n) | O(n log n) | O(1) | Không | +| Counting Sort | O(n+k) | O(n+k) | O(n+k) | O(k) | Có | +| Radix Sort | O(nk) | O(nk) | O(nk) | O(n+k) | Có | + +Trong đó: +- n là số lượng phần tử +- k là phạm vi của giá trị đầu vào + +## Khi nào sử dụng từng thuật toán + +- **Quick Sort**: Sắp xếp đa năng, hoạt động tốt cho các mảng vừa với bộ nhớ +- **Merge Sort**: Khi cần tính ổn định và yêu cầu trường hợp xấu nhất O(n log n) +- **Bubble Sort**: Mục đích giáo dục hoặc tập dữ liệu rất nhỏ +- **Insertion Sort**: Tập dữ liệu nhỏ hoặc mảng gần như đã được sắp xếp +- **Heap Sort**: Khi cần hiệu suất ổn định mà không có kịch bản xấu nhất +- **Counting/Radix Sort**: Khi phạm vi giá trị đầu vào bị giới hạn + +## Kết luận + +Việc chọn thuật toán sắp xếp phù hợp phụ thuộc vào yêu cầu cụ thể của bạn, bao gồm kích thước của tập dữ liệu, giới hạn bộ nhớ và liệu tính ổn định có quan trọng hay không. Trong thực tế, hầu hết các ngôn ngữ lập trình triển khai các thuật toán sắp xếp kết hợp kết hợp lợi ích của các phương pháp khác nhau. diff --git a/i18n/vi/databases/relational.md b/i18n/vi/databases/relational.md new file mode 100644 index 0000000..3937bf3 --- /dev/null +++ b/i18n/vi/databases/relational.md @@ -0,0 +1,125 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Relational +description: Guide about Relational +--- +# 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" diff --git a/i18n/vi/design-patterns/factory.md b/i18n/vi/design-patterns/factory.md new file mode 100644 index 0000000..a66f1ce --- /dev/null +++ b/i18n/vi/design-patterns/factory.md @@ -0,0 +1,484 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Factory +description: Guide about Factory +--- +# 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) diff --git a/i18n/vi/design-patterns/observer.md b/i18n/vi/design-patterns/observer.md new file mode 100644 index 0000000..ffa226f --- /dev/null +++ b/i18n/vi/design-patterns/observer.md @@ -0,0 +1,412 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Observer +description: Guide about Observer +--- +# 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) diff --git a/i18n/vi/design-patterns/singleton.md b/i18n/vi/design-patterns/singleton.md new file mode 100644 index 0000000..f9d06bb --- /dev/null +++ b/i18n/vi/design-patterns/singleton.md @@ -0,0 +1,234 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Singleton +description: Guide about Singleton +--- +# 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) diff --git a/i18n/vi/devops/ci-cd.md b/i18n/vi/devops/ci-cd.md new file mode 100644 index 0000000..d079247 --- /dev/null +++ b/i18n/vi/devops/ci-cd.md @@ -0,0 +1,170 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Ci Cd +description: Guide about Ci Cd +--- +# 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.md b/i18n/vi/linux/bash-scripting.md new file mode 100644 index 0000000..55cd383 --- /dev/null +++ b/i18n/vi/linux/bash-scripting.md @@ -0,0 +1,333 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Bash Scripting +description: Guide about Bash Scripting +--- +# 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.md b/i18n/vi/system-design/microservices.md new file mode 100644 index 0000000..a0259b2 --- /dev/null +++ b/i18n/vi/system-design/microservices.md @@ -0,0 +1,475 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Microservices +description: Guide about Microservices +--- +# 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.md b/i18n/vi/testing/unit-testing.md new file mode 100644 index 0000000..5fa254c --- /dev/null +++ b/i18n/vi/testing/unit-testing.md @@ -0,0 +1,475 @@ +--- +author: Tech Notes Hub +tags: 'learning, technology, programming' +update: '2025-06-06' +date: '2025-06-06' +title: Unit Testing +description: Guide about Unit Testing +--- +# 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/package-lock.json b/package-lock.json new file mode 100644 index 0000000..dcc75da --- /dev/null +++ b/package-lock.json @@ -0,0 +1,639 @@ +{ + "name": "tech-notes", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "glob": "^11.0.2", + "gray-matter": "^4.0.3" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "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": "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/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==", + "dev": 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==", + "dev": true, + "license": "MIT" + }, + "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/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "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/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", + "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "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-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..44af9e6 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "glob": "^11.0.2", + "gray-matter": "^4.0.3" + } +} 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/algorithms/sorting-algorithms/sorting_algorithms.c b/snippets/algorithms/sorting-algorithms/sorting_algorithms.c new file mode 100644 index 0000000..05d731b --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.c @@ -0,0 +1,587 @@ +#include +#include +#include +#include + +/** + * Bubble Sort + * Time complexity: O(n^2) + */ +void bubbleSort(int arr[], int n) { + for (int i = 0; i < n - 1; i++) { + // Flag to optimize for already sorted arrays + int swapped = 0; + + for (int j = 0; j < n - i - 1; j++) { + if (arr[j] > arr[j + 1]) { + // Swap elements + int temp = arr[j]; + arr[j] = arr[j + 1]; + arr[j + 1] = temp; + swapped = 1; + } + } + + // If no elements were swapped in this iteration, the array is already sorted + if (swapped == 0) { + break; + } + } +} + +/** + * Selection Sort + * Time complexity: O(n^2) + */ +void selectionSort(int arr[], int n) { + for (int i = 0; i < n - 1; i++) { + // Find the smallest element in the unsorted part + int min_idx = i; + + for (int j = i + 1; j < n; j++) { + if (arr[j] < arr[min_idx]) { + min_idx = j; + } + } + + // Swap the smallest element with the first element in the unsorted part + if (min_idx != i) { + int temp = arr[i]; + arr[i] = arr[min_idx]; + arr[min_idx] = temp; + } + } +} + +/** + * Insertion Sort + * Time complexity: O(n^2) + */ +void insertionSort(int arr[], int n) { + for (int i = 1; i < n; i++) { + int key = arr[i]; + int j = i - 1; + + // Move elements greater than key to the right by one position + while (j >= 0 && arr[j] > key) { + arr[j + 1] = arr[j]; + j--; + } + + arr[j + 1] = key; + } +} + +/** + * Merge two sorted subarrays - helper function for Merge Sort + */ +void merge(int arr[], int l, int m, int r) { + int i, j, k; + int n1 = m - l + 1; + int n2 = r - m; + + // Create temporary arrays + int L[n1], R[n2]; + + // Copy data into temporary arrays + for (i = 0; i < n1; i++) { + L[i] = arr[l + i]; + } + for (j = 0; j < n2; j++) { + R[j] = arr[m + 1 + j]; + } + + // Merge the temporary arrays + i = 0; // Initial index of first subarray + j = 0; // Initial index of second subarray + k = l; // Initial index of merged subarray + + while (i < n1 && j < n2) { + if (L[i] <= R[j]) { + arr[k] = L[i]; + i++; + } else { + arr[k] = R[j]; + j++; + } + k++; + } + + // Copy remaining elements of L[] if any + while (i < n1) { + arr[k] = L[i]; + i++; + k++; + } + + // Copy remaining elements of R[] if any + while (j < n2) { + arr[k] = R[j]; + j++; + k++; + } +} + +/** + * Merge Sort + * Time complexity: O(n log n) + */ +void mergeSort(int arr[], int l, int r) { + if (l < r) { + // Find the middle point + int m = l + (r - l) / 2; + + // Sort first and second halves + mergeSort(arr, l, m); + mergeSort(arr, m + 1, r); + + // Merge the sorted halves + merge(arr, l, m, r); + } +} + +/** + * Utility function to call mergeSort + */ +void mergeSortWrapper(int arr[], int n) { + mergeSort(arr, 0, n - 1); +} + +/** + * Partition function - helper function for Quick Sort + */ +int partition(int arr[], int low, int high) { + // Choose pivot as the last element + int pivot = arr[high]; + int i = (low - 1); // Index of the smaller element + + for (int j = low; j <= high - 1; j++) { + // If current element is less than or equal to pivot + if (arr[j] <= pivot) { + i++; // Increment index of smaller element + + // Swap arr[i] and arr[j] + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + } + + // Swap arr[i + 1] and arr[high] (place pivot in the correct position) + int temp = arr[i + 1]; + arr[i + 1] = arr[high]; + arr[high] = temp; + + return (i + 1); +} + +/** + * Quick Sort + * Time complexity: O(n log n) average, O(n^2) worst case + */ +void quickSort(int arr[], int low, int high) { + if (low < high) { + // pi is the partition index, arr[pi] is already in the correct position + int pi = partition(arr, low, high); + + // Recursively sort elements before and after partition + quickSort(arr, low, pi - 1); + quickSort(arr, pi + 1, high); + } +} + +/** + * Utility function to call quickSort + */ +void quickSortWrapper(int arr[], int n) { + quickSort(arr, 0, n - 1); +} + +/** + * Heapify function - helper function for Heap Sort + */ +void heapify(int arr[], int n, int i) { + int largest = i; // Initialize largest as root + int left = 2 * i + 1; // Left child index = 2*i + 1 + int right = 2 * i + 2; // Right child index = 2*i + 2 + + // If left child is greater than root + if (left < n && arr[left] > arr[largest]) { + largest = left; + } + + // If right child is greater than root + if (right < n && arr[right] > arr[largest]) { + largest = right; + } + + // If largest is not root + if (largest != i) { + // Swap i and largest + int temp = arr[i]; + arr[i] = arr[largest]; + arr[largest] = temp; + + // Heapify recursively on the affected child subtree + heapify(arr, n, largest); + } +} + +/** + * Heap Sort + * Time complexity: O(n log n) + */ +void heapSort(int arr[], int n) { + // Build heap (heapify) + for (int i = n / 2 - 1; i >= 0; i--) { + heapify(arr, n, i); + } + + // One by one extract elements from heap + for (int i = n - 1; i > 0; i--) { + // Move current root to end + int temp = arr[0]; + arr[0] = arr[i]; + arr[i] = temp; + + // Call max heapify on the reduced heap + heapify(arr, i, 0); + } +} + +/** + * Counting Sort + * Time complexity: O(n + k) with k being the range of input elements + */ +void countingSort(int arr[], int n) { + // Find the maximum and minimum elements + int max = arr[0]; + int min = arr[0]; + for (int i = 1; i < n; i++) { + if (arr[i] > max) { + max = arr[i]; + } + if (arr[i] < min) { + min = arr[i]; + } + } + + int range = max - min + 1; + + // Create count array and result array + int* count = (int*)calloc(range, sizeof(int)); + int* output = (int*)malloc(n * sizeof(int)); + + // Count occurrences of each element + for (int i = 0; i < n; i++) { + count[arr[i] - min]++; + } + + // Update count[i] to contain the actual position of this value in output + for (int i = 1; i < range; i++) { + count[i] += count[i - 1]; + } + + // Build output array + for (int i = n - 1; i >= 0; i--) { + output[count[arr[i] - min] - 1] = arr[i]; + count[arr[i] - min]--; + } + + // Copy output array to arr + for (int i = 0; i < n; i++) { + arr[i] = output[i]; + } + + // Free memory + free(count); + free(output); +} + +/** + * Utility function for Radix Sort - sorting based on the digit at position exp + */ +void countingSortForRadix(int arr[], int n, int exp) { + int output[n]; + int count[10] = {0}; // 0-9 digits + + // Count occurrences of each digit + for (int i = 0; i < n; i++) { + count[(arr[i] / exp) % 10]++; + } + + // Update count to contain the actual position of this value in output + for (int i = 1; i < 10; i++) { + count[i] += count[i - 1]; + } + + // Build output array + for (int i = n - 1; i >= 0; i--) { + int digit = (arr[i] / exp) % 10; + output[count[digit] - 1] = arr[i]; + count[digit]--; + } + + // Copy output array to arr + for (int i = 0; i < n; i++) { + arr[i] = output[i]; + } +} + +/** + * Radix Sort + * Time complexity: O(d * (n + b)) with d being the number of digits and b being the base + * Note: This algorithm only works with non-negative integers + */ +void radixSort(int arr[], int n) { + // Check if array is empty + if (n <= 1) return; + + // Separate array into negative and positive parts for separate handling + int hasNegative = 0; + for (int i = 0; i < n; i++) { + if (arr[i] < 0) { + hasNegative = 1; + break; + } + } + + if (hasNegative) { + // Count number of negative and positive numbers + int countNeg = 0; + for (int i = 0; i < n; i++) { + if (arr[i] < 0) countNeg++; + } + int countPos = n - countNeg; + + // Create separate arrays for negative and positive numbers + int* negArr = (int*)malloc(countNeg * sizeof(int)); + int* posArr = (int*)malloc(countPos * sizeof(int)); + + // Separate array + int negIdx = 0, posIdx = 0; + for (int i = 0; i < n; i++) { + if (arr[i] < 0) { + negArr[negIdx++] = -arr[i]; // Get absolute value + } else { + posArr[posIdx++] = arr[i]; + } + } + + // Sort negative and positive arrays using radixSort + if (countNeg > 0) radixSort(negArr, countNeg); + if (countPos > 0) radixSort(posArr, countPos); + + // Combine results + for (int i = 0; i < countNeg; i++) { + arr[i] = -negArr[countNeg - 1 - i]; // Reverse and change sign + } + for (int i = 0; i < countPos; i++) { + arr[countNeg + i] = posArr[i]; + } + + // Free memory + free(negArr); + free(posArr); + + return; + } + + // Find the largest number to know the number of digits + int max = arr[0]; + for (int i = 1; i < n; i++) { + if (arr[i] > max) { + max = arr[i]; + } + } + + // Perform counting sort for each digit + for (int exp = 1; max / exp > 0; exp *= 10) { + countingSortForRadix(arr, n, exp); + } +} + +/** + * Bucket Sort + * Time complexity: O(n + k) with k being the number of buckets + */ +void bucketSort(int arr[], int n) { + if (n <= 0) return; + + // Find the largest and smallest values + int max = arr[0]; + int min = arr[0]; + for (int i = 1; i < n; i++) { + if (arr[i] > max) max = arr[i]; + if (arr[i] < min) min = arr[i]; + } + + // Calculate the range and number of buckets + int range = max - min + 1; + int bucketCount = (n < 10) ? n : 10; // Number of buckets, maximum 10 + double bucketSize = (double)range / bucketCount; + + // Create array of buckets + int** buckets = (int**)malloc(bucketCount * sizeof(int*)); + int* bucketSizes = (int*)calloc(bucketCount, sizeof(int)); + + // Initialize buckets with initial size of 0 + for (int i = 0; i < bucketCount; i++) { + buckets[i] = (int*)malloc(n * sizeof(int)); // Maximum size is n + } + + // Distribute elements into buckets + for (int i = 0; i < n; i++) { + int bucketIndex = (int)((arr[i] - min) / bucketSize); + if (bucketIndex >= bucketCount) bucketIndex = bucketCount - 1; + buckets[bucketIndex][bucketSizes[bucketIndex]++] = arr[i]; + } + + // Sort each bucket and merge + int arrIndex = 0; + for (int i = 0; i < bucketCount; i++) { + if (bucketSizes[i] > 0) { + // Sort bucket using insertion sort + for (int j = 1; j < bucketSizes[i]; j++) { + int key = buckets[i][j]; + int k = j - 1; + while (k >= 0 && buckets[i][k] > key) { + buckets[i][k + 1] = buckets[i][k]; + k--; + } + buckets[i][k + 1] = key; + } + + // Add sorted elements from bucket to the original array + for (int j = 0; j < bucketSizes[i]; j++) { + arr[arrIndex++] = buckets[i][j]; + } + } + } + + // Free memory + for (int i = 0; i < bucketCount; i++) { + free(buckets[i]); + } + free(buckets); + free(bucketSizes); +} + +/** + * Shell Sort + * Time complexity: depends on the gap sequence, usually O(n log^2 n) + */ +void shellSort(int arr[], int n) { + // Start with large gap, then decrease + for (int gap = n/2; gap > 0; gap /= 2) { + // Perform insertion sort with gap + for (int i = gap; i < n; i++) { + // Add arr[i] to the sorted elements with gap + // Save arr[i] in temp and create a gap at position i + int temp = arr[i]; + + // Shift the sorted elements with gap until the correct position for temp is found + int j; + for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) { + arr[j] = arr[j - gap]; + } + + // Place temp (original arr[i]) in the correct position + arr[j] = temp; + } + } +} + +/** + * Utility function to print array + */ +void printArray(int arr[], int n) { + for (int i = 0; i < n; i++) { + printf("%d ", arr[i]); + } + printf("\n"); +} + +/** + * Utility function to copy array + */ +void copyArray(int source[], int dest[], int n) { + for (int i = 0; i < n; i++) { + dest[i] = source[i]; + } +} + +/** + * Main function to test sorting algorithms + */ +int main() { + // Test array + int arr[] = {64, 34, 25, 12, 22, 11, 90}; + int n = sizeof(arr) / sizeof(arr[0]); + int arrCopy[n]; + + printf("Original array: "); + printArray(arr, n); + + // Bubble Sort + copyArray(arr, arrCopy, n); + bubbleSort(arrCopy, n); + printf("Bubble Sort: "); + printArray(arrCopy, n); + + // Selection Sort + copyArray(arr, arrCopy, n); + selectionSort(arrCopy, n); + printf("Selection Sort: "); + printArray(arrCopy, n); + + // Insertion Sort + copyArray(arr, arrCopy, n); + insertionSort(arrCopy, n); + printf("Insertion Sort: "); + printArray(arrCopy, n); + + // Merge Sort + copyArray(arr, arrCopy, n); + mergeSortWrapper(arrCopy, n); + printf("Merge Sort: "); + printArray(arrCopy, n); + + // Quick Sort + copyArray(arr, arrCopy, n); + quickSortWrapper(arrCopy, n); + printf("Quick Sort: "); + printArray(arrCopy, n); + + // Heap Sort + copyArray(arr, arrCopy, n); + heapSort(arrCopy, n); + printf("Heap Sort: "); + printArray(arrCopy, n); + + // Counting Sort + copyArray(arr, arrCopy, n); + countingSort(arrCopy, n); + printf("Counting Sort: "); + printArray(arrCopy, n); + + // Radix Sort + copyArray(arr, arrCopy, n); + radixSort(arrCopy, n); + printf("Radix Sort: "); + printArray(arrCopy, n); + + // Bucket Sort + copyArray(arr, arrCopy, n); + bucketSort(arrCopy, n); + printf("Bucket Sort: "); + printArray(arrCopy, n); + + // Shell Sort + copyArray(arr, arrCopy, n); + shellSort(arrCopy, n); + printf("Shell Sort: "); + printArray(arrCopy, n); + + return 0; +} \ No newline at end of file diff --git a/snippets/algorithms/sorting-algorithms/sorting_algorithms.cpp b/snippets/algorithms/sorting-algorithms/sorting_algorithms.cpp new file mode 100644 index 0000000..773ea25 --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.cpp @@ -0,0 +1,513 @@ +#include +#include +#include +#include +#include + +/** + * Collection of popular sorting algorithms implemented in C++ + */ +class SortingAlgorithms { +public: + /** + * Bubble Sort + * Time complexity: O(n^2) + */ + static std::vector bubbleSort(const std::vector& arr) { + std::vector result = arr; // Create a copy to avoid modifying the original array + int n = result.size(); + + for (int i = 0; i < n - 1; i++) { + // Flag to optimize for already sorted arrays + bool swapped = false; + + for (int j = 0; j < n - i - 1; j++) { + if (result[j] > result[j + 1]) { + // Swap elements + std::swap(result[j], result[j + 1]); + swapped = true; + } + } + + // If no elements were swapped in this loop, the array is already sorted + if (!swapped) break; + } + + return result; + } + + /** + * Selection Sort + * Time complexity: O(n^2) + */ + static std::vector selectionSort(const std::vector& arr) { + std::vector result = arr; // Create a copy to avoid modifying the original array + int n = result.size(); + + for (int i = 0; i < n - 1; i++) { + // Find the minimum element in the unsorted part of the array + int minIdx = i; + + for (int j = i + 1; j < n; j++) { + if (result[j] < result[minIdx]) { + minIdx = j; + } + } + + // Swap the minimum element with the first element of the unsorted part + if (minIdx != i) { + std::swap(result[i], result[minIdx]); + } + } + + return result; + } + + /** + * Insertion Sort + * Time complexity: O(n^2) + */ + static std::vector insertionSort(const std::vector& arr) { + std::vector result = arr; // Create a copy to avoid modifying the original array + int n = result.size(); + + for (int i = 1; i < n; i++) { + int key = result[i]; + int j = i - 1; + + // Move elements greater than key one position ahead + while (j >= 0 && result[j] > key) { + result[j + 1] = result[j]; + j--; + } + + result[j + 1] = key; + } + + return result; + } + + /** + * Merge Sort + * Time complexity: O(n log n) + */ + static std::vector mergeSort(const std::vector& arr) { + std::vector result = arr; // Create a copy to avoid modifying the original array + + if (result.size() <= 1) return result; + + mergeSort(result, 0, result.size() - 1); + return result; + } + +private: + /** + * Helper function for Merge Sort + */ + static void mergeSort(std::vector& arr, int left, int right) { + if (left >= right) return; + + // Find the middle point + int mid = left + (right - left) / 2; + + // Sort first and second halves + mergeSort(arr, left, mid); + mergeSort(arr, mid + 1, right); + + // Merge the sorted halves + merge(arr, left, mid, right); + } + + /** + * Merge two sorted subarrays + */ + static void merge(std::vector& arr, int left, int mid, int right) { + // Find the sizes of two subarrays to be merged + int n1 = mid - left + 1; + int n2 = right - mid; + + // Create temporary arrays + std::vector L(n1); + std::vector R(n2); + + // Copy data into temporary arrays + for (int i = 0; i < n1; i++) { + L[i] = arr[left + i]; + } + for (int j = 0; j < n2; j++) { + R[j] = arr[mid + 1 + j]; + } + + // Merge the temporary arrays + int i = 0, j = 0; + int k = left; + + while (i < n1 && j < n2) { + if (L[i] <= R[j]) { + arr[k] = L[i]; + i++; + } else { + arr[k] = R[j]; + j++; + } + k++; + } + + // Copy remaining elements of L[] if any + while (i < n1) { + arr[k] = L[i]; + i++; + k++; + } + + // Sao chép các phần tử còn lại của R[] nếu có + while (j < n2) { + arr[k] = R[j]; + j++; + k++; + } + } + +public: + /** + * Quick Sort + * Time complexity: O(n log n) average, O(n^2) worst case + */ + static std::vector quickSort(const std::vector& arr) { + std::vector result = arr; // Create a copy to avoid modifying the original array + + if (result.size() <= 1) return result; + + quickSort(result, 0, result.size() - 1); + return result; + } + +private: + /** + * Partition function for Quick Sort + */ + static int partition(std::vector& arr, int low, int high) { + // Select the last element as pivot + int pivot = arr[high]; + int i = low - 1; + + for (int j = low; j < high; j++) { + // If the current element is less than or equal to pivot + if (arr[j] <= pivot) { + i++; + std::swap(arr[i], arr[j]); + } + } + + std::swap(arr[i + 1], arr[high]); + return i + 1; + } + + /** + * Helper function for Quick Sort + */ + static void quickSort(std::vector& arr, int low, int high) { + if (low < high) { + // pi is the partition index, arr[pi] is already in the correct position + int pi = partition(arr, low, high); + + // Recursively sort elements before and after pivot + quickSort(arr, low, pi - 1); + quickSort(arr, pi + 1, high); + } + } + +public: + /** + * Heap Sort + * Time complexity: O(n log n) + */ + static std::vector heapSort(const std::vector& arr) { + std::vector result = arr; // Create a copy to avoid modifying the original array + int n = result.size(); + + // Build heap (heapify) + for (int i = n / 2 - 1; i >= 0; i--) { + heapify(result, n, i); + } + + // One by one extract an element from heap + for (int i = n - 1; i > 0; i--) { + // Move current root to end + std::swap(result[0], result[i]); + + // Call max heapify on the reduced heap + heapify(result, i, 0); + } + + return result; + } + +private: + /** + * Heapify function for Heap Sort + */ + static void heapify(std::vector& arr, int n, int i) { + int largest = i; // Initialize largest as root + int left = 2 * i + 1; // Left child index = 2*i + 1 + int right = 2 * i + 2; // Right child index = 2*i + 2 + + // If left child is larger than root + if (left < n && arr[left] > arr[largest]) { + largest = left; + } + + // If right child is larger than root + if (right < n && arr[right] > arr[largest]) { + largest = right; + } + + // If largest is not root + if (largest != i) { + std::swap(arr[i], arr[largest]); + + // Recursively heapify the affected sub-tree + heapify(arr, n, largest); + } + } + +public: + /** + * Counting Sort + * Time complexity: O(n + k) with k being the range of input values + */ + static std::vector countingSort(const std::vector& arr) { + if (arr.empty()) return {}; + + // Find the maximum and minimum elements + int max = *std::max_element(arr.begin(), arr.end()); + int min = *std::min_element(arr.begin(), arr.end()); + int range = max - min + 1; + + std::vector count(range, 0); + std::vector output(arr.size()); + + // Count the occurrences of each element + for (int i = 0; i < arr.size(); i++) { + count[arr[i] - min]++; + } + + // Update count[i] to contain the actual position of this value in output + for (int i = 1; i < count.size(); i++) { + count[i] += count[i - 1]; + } + + // Build the output array + for (int i = arr.size() - 1; i >= 0; i--) { + output[count[arr[i] - min] - 1] = arr[i]; + count[arr[i] - min]--; + } + + return output; + } + + /** + * Radix Sort + * Time complexity: O(d * (n + b)) with d being the number of digits in the largest number, b being the base + */ + static std::vector radixSort(const std::vector& arr) { + if (arr.empty()) return {}; + + // Check for negative numbers + bool hasNegative = false; + for (int num : arr) { + if (num < 0) { + hasNegative = true; + break; + } + } + + if (hasNegative) { + // Split into negative and positive lists + std::vector negatives; + std::vector positives; + + for (int num : arr) { + if (num < 0) { + negatives.push_back(-num); // Get the absolute value + } else { + positives.push_back(num); + } + } + + // Sort the absolute values of negative numbers + auto sortedNegatives = radixSort(negatives); + // Sort positive numbers + auto sortedPositives = radixSort(positives); + + // Combine: negative numbers (reversed and signed) + positive numbers + std::vector result; + for (int i = sortedNegatives.size() - 1; i >= 0; i--) { + result.push_back(-sortedNegatives[i]); + } + result.insert(result.end(), sortedPositives.begin(), sortedPositives.end()); + + return result; + } + + // Find the maximum number of digits + int max = *std::max_element(arr.begin(), arr.end()); + std::vector result = arr; + + // Perform counting sort for each digit + for (int exp = 1; max / exp > 0; exp *= 10) { + countingSortByDigit(result, exp); + } + + return result; + } + +private: + /** + * Helper function for Radix Sort - sort based on the digit at position exp + */ + static void countingSortByDigit(std::vector& arr, int exp) { + int n = arr.size(); + std::vector output(n); + std::vector count(10, 0); // 0-9 digits + + // Count the occurrences of each digit + for (int i = 0; i < n; i++) { + int digit = (arr[i] / exp) % 10; + count[digit]++; + } + + // Update count to contain the actual position + for (int i = 1; i < 10; i++) { + count[i] += count[i - 1]; + } + + // Build the output array + for (int i = n - 1; i >= 0; i--) { + int digit = (arr[i] / exp) % 10; + output[count[digit] - 1] = arr[i]; + count[digit]--; + } + + // Copy the output array to arr + for (int i = 0; i < n; i++) { + arr[i] = output[i]; + } + } + +public: + /** + * Bucket Sort + * Time complexity: O(n + k) with k being the number of buckets + */ + static std::vector bucketSort(const std::vector& arr, int bucketCount = 10) { + if (arr.empty()) return {}; + + // Find the maximum and minimum values + int minVal = *std::min_element(arr.begin(), arr.end()); + int maxVal = *std::max_element(arr.begin(), arr.end()); + + // Create buckets + double range = (maxVal - minVal + 1) / static_cast(bucketCount); + std::vector> buckets(bucketCount); + + // Distribute elements into buckets + for (int num : arr) { + int bucketIndex = static_cast((num - minVal) / range); + if (bucketIndex >= bucketCount) bucketIndex = bucketCount - 1; + buckets[bucketIndex].push_back(num); + } + + // Sort each bucket and concatenate + std::vector result; + for (auto& bucket : buckets) { + bucket.sort(); // Sort bucket using std::list's sort function + result.insert(result.end(), bucket.begin(), bucket.end()); + } + + return result; + } + + /** + * Shell Sort + * Time complexity: O(n log^2 n) + */ + static std::vector shellSort(const std::vector& arr) { + std::vector result = arr; // Create a copy to avoid modifying the original array + int n = result.size(); + + // Start with large gaps, then decrease + for (int gap = n/2; gap > 0; gap /= 2) { + // Perform insertion sort with gap + for (int i = gap; i < n; i++) { + // Add arr[i] to the sorted elements with gap + // Save arr[i] in temp and create a gap at position i + int temp = result[i]; + + // Shift the sorted elements with gap until the correct position for temp is found + int j; + for (j = i; j >= gap && result[j - gap] > temp; j -= gap) { + result[j] = result[j - gap]; + } + + // Place temp (original arr[i]) in the correct position + result[j] = temp; + } + } + + return result; + } +}; + +/** + * Utility function to print vector + */ +void printVector(const std::vector& arr) { + for (int num : arr) { + std::cout << num << " "; + } + std::cout << std::endl; +} + +/** + * Main function to test sorting algorithms + */ +int main() { + // Vector test + std::vector arr = {64, 34, 25, 12, 22, 11, 90}; + + std::cout << "Original array: "; + printVector(arr); + + std::cout << "Bubble Sort: "; + printVector(SortingAlgorithms::bubbleSort(arr)); + + std::cout << "Selection Sort: "; + printVector(SortingAlgorithms::selectionSort(arr)); + + std::cout << "Insertion Sort: "; + printVector(SortingAlgorithms::insertionSort(arr)); + + std::cout << "Merge Sort: "; + printVector(SortingAlgorithms::mergeSort(arr)); + + std::cout << "Quick Sort: "; + printVector(SortingAlgorithms::quickSort(arr)); + + std::cout << "Heap Sort: "; + printVector(SortingAlgorithms::heapSort(arr)); + + std::cout << "Counting Sort: "; + printVector(SortingAlgorithms::countingSort(arr)); + + std::cout << "Radix Sort: "; + printVector(SortingAlgorithms::radixSort(arr)); + + std::cout << "Bucket Sort: "; + printVector(SortingAlgorithms::bucketSort(arr)); + + std::cout << "Shell Sort: "; + printVector(SortingAlgorithms::shellSort(arr)); + + return 0; +} \ No newline at end of file diff --git a/snippets/algorithms/sorting-algorithms/sorting_algorithms.cs b/snippets/algorithms/sorting-algorithms/sorting_algorithms.cs new file mode 100644 index 0000000..1d1ec35 --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.cs @@ -0,0 +1,513 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SortingAlgorithms +{ + public class SortingAlgorithms + { + /// + /// Bubble Sort + /// Time complexity: O(n^2) + /// + public static int[] BubbleSort(int[] arr) + { + int[] result = (int[])arr.Clone(); + int n = result.Length; + + for (int i = 0; i < n; i++) + { + bool swapped = false; + + for (int j = 0; j < n - i - 1; j++) + { + if (result[j] > result[j + 1]) + { + // Swap elements + int temp = result[j]; + result[j] = result[j + 1]; + result[j + 1] = temp; + swapped = true; + } + } + + // If no swapping occurred in this pass, the array is already sorted + if (!swapped) + break; + } + + return result; + } + + /// + /// Selection Sort + /// Time complexity: O(n^2) + /// + public static int[] SelectionSort(int[] arr) + { + int[] result = (int[])arr.Clone(); + int n = result.Length; + + for (int i = 0; i < n - 1; i++) + { + // Find the minimum element in the unsorted part + int minIdx = i; + for (int j = i + 1; j < n; j++) + { + if (result[j] < result[minIdx]) + { + minIdx = j; + } + } + + // Swap the found minimum element with the first element + int temp = result[minIdx]; + result[minIdx] = result[i]; + result[i] = temp; + } + + return result; + } + + /// + /// Insertion Sort + /// Time complexity: O(n^2) + /// + public static int[] InsertionSort(int[] arr) + { + int[] result = (int[])arr.Clone(); + int n = result.Length; + + for (int i = 1; i < n; i++) + { + int key = result[i]; + int j = i - 1; + + // Move elements greater than key one position ahead + while (j >= 0 && result[j] > key) + { + result[j + 1] = result[j]; + j--; + } + result[j + 1] = key; + } + + return result; + } + + /// + /// Merge Sort + /// Time complexity: O(n log n) + /// + public static int[] MergeSort(int[] arr) + { + int[] result = (int[])arr.Clone(); + if (result.Length <= 1) + return result; + + return MergeSortHelper(result); + } + + private static int[] MergeSortHelper(int[] arr) + { + if (arr.Length <= 1) + return arr; + + int mid = arr.Length / 2; + int[] left = new int[mid]; + int[] right = new int[arr.Length - mid]; + + // Fill left and right subarrays + Array.Copy(arr, 0, left, 0, mid); + Array.Copy(arr, mid, right, 0, arr.Length - mid); + + left = MergeSortHelper(left); + right = MergeSortHelper(right); + + return Merge(left, right); + } + + private static int[] Merge(int[] left, int[] right) + { + int[] result = new int[left.Length + right.Length]; + int i = 0, j = 0, k = 0; + + // Merge the two arrays + while (i < left.Length && j < right.Length) + { + if (left[i] <= right[j]) + { + result[k++] = left[i++]; + } + else + { + result[k++] = right[j++]; + } + } + + // Copy remaining elements + while (i < left.Length) + { + result[k++] = left[i++]; + } + + while (j < right.Length) + { + result[k++] = right[j++]; + } + + return result; + } + + /// + /// Quick Sort + /// Time complexity: O(n log n) average, O(n^2) worst case + /// + public static int[] QuickSort(int[] arr) + { + int[] result = (int[])arr.Clone(); + if (result.Length <= 1) + return result; + + QuickSortHelper(result, 0, result.Length - 1); + return result; + } + + private static void QuickSortHelper(int[] arr, int low, int high) + { + if (low < high) + { + // pi is the partitioning index + int pi = Partition(arr, low, high); + + // Recursively sort elements before and after partition + QuickSortHelper(arr, low, pi - 1); + QuickSortHelper(arr, pi + 1, high); + } + } + + private static int Partition(int[] arr, int low, int high) + { + int pivot = arr[high]; // Choose the last element as pivot + int i = low - 1; // Index of smaller element + + for (int j = low; j < high; j++) + { + // If current element is smaller than or equal to pivot + if (arr[j] <= pivot) + { + i++; + + // Swap arr[i] and arr[j] + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + } + + // Swap arr[i+1] and arr[high] (pivot) + int temp1 = arr[i + 1]; + arr[i + 1] = arr[high]; + arr[high] = temp1; + + return i + 1; + } + + /// + /// Heap Sort + /// Time complexity: O(n log n) + /// + public static int[] HeapSort(int[] arr) + { + int[] result = (int[])arr.Clone(); + int n = result.Length; + + // Build max heap + for (int i = n / 2 - 1; i >= 0; i--) + { + Heapify(result, n, i); + } + + // Extract elements from heap one by one + for (int i = n - 1; i > 0; i--) + { + // Move current root to end + int temp = result[0]; + result[0] = result[i]; + result[i] = temp; + + // Call heapify on the reduced heap + Heapify(result, i, 0); + } + + return result; + } + + private static void Heapify(int[] arr, int n, int i) + { + int largest = i; // Initialize largest as root + int left = 2 * i + 1; // left = 2*i + 1 + int right = 2 * i + 2; // right = 2*i + 2 + + // If left child is larger than root + if (left < n && arr[left] > arr[largest]) + largest = left; + + // If right child is larger than largest so far + if (right < n && arr[right] > arr[largest]) + largest = right; + + // If largest is not root + if (largest != i) + { + int swap = arr[i]; + arr[i] = arr[largest]; + arr[largest] = swap; + + // Recursively heapify the affected sub-tree + Heapify(arr, n, largest); + } + } + + /// + /// Counting Sort + /// Time complexity: O(n + k) where k is the range of input elements + /// + public static int[] CountingSort(int[] arr) + { + if (arr.Length == 0) + return new int[0]; + + // Find the maximum and minimum element in the array + int max = arr.Max(); + int min = arr.Min(); + int range = max - min + 1; + + // Create a count array and result array + int[] count = new int[range]; + int[] output = new int[arr.Length]; + + // Store count of each element + for (int i = 0; i < arr.Length; i++) + { + count[arr[i] - min]++; + } + + // Change count[i] so that count[i] now contains the actual + // position of this element in output array + for (int i = 1; i < count.Length; i++) + { + count[i] += count[i - 1]; + } + + // Build the output array + for (int i = arr.Length - 1; i >= 0; i--) + { + output[count[arr[i] - min] - 1] = arr[i]; + count[arr[i] - min]--; + } + + return output; + } + + /// + /// Radix Sort + /// Time complexity: O(d * (n + b)) with d being the number of digits and b being the base + /// + public static int[] RadixSort(int[] arr) + { + if (arr.Length == 0) + return new int[0]; + + // Handle negative numbers + bool hasNegative = arr.Any(val => val < 0); + if (hasNegative) + { + // Separate into negative and positive arrays + int[] neg = arr.Where(val => val < 0).Select(val => -val).ToArray(); + int[] pos = arr.Where(val => val >= 0).ToArray(); + + // Sort absolute values of negative numbers + if (neg.Length > 0) + { + neg = RadixSort(neg); + // Reverse and negate + Array.Reverse(neg); + for (int i = 0; i < neg.Length; i++) + { + neg[i] = -neg[i]; + } + } + + // Sort positive numbers + if (pos.Length > 0) + { + pos = RadixSort(pos); + } + + // Merge negative and positive arrays + int[] result = new int[arr.Length]; + Array.Copy(neg, 0, result, 0, neg.Length); + Array.Copy(pos, 0, result, neg.Length, pos.Length); + return result; + } + + // Find the maximum number to know the number of digits + int max = arr.Max(); + + // Copy the array + int[] result = (int[])arr.Clone(); + + // Do counting sort for every digit + for (int exp = 1; max / exp > 0; exp *= 10) + { + CountingSortByDigit(result, exp); + } + + return result; + } + + private static void CountingSortByDigit(int[] arr, int exp) + { + int n = arr.Length; + int[] output = new int[n]; + int[] count = new int[10]; // Count array for digits 0-9 + + // Store count of occurrences in count[] + for (int i = 0; i < n; i++) + { + int digit = (arr[i] / exp) % 10; + count[digit]++; + } + + // Change count[i] so that count[i] now contains the actual + // position of this digit in output[] + for (int i = 1; i < 10; i++) + { + count[i] += count[i - 1]; + } + + // Build the output array + for (int i = n - 1; i >= 0; i--) + { + int digit = (arr[i] / exp) % 10; + output[count[digit] - 1] = arr[i]; + count[digit]--; + } + + // Copy the output array to arr[], so that arr[] now + // contains sorted numbers according to current digit + for (int i = 0; i < n; i++) + { + arr[i] = output[i]; + } + } + + /// + /// Bucket Sort + /// Time complexity: O(n + k) where k is the number of buckets + /// + public static int[] BucketSort(int[] arr, int numBuckets = 10) + { + if (arr.Length == 0) + return new int[0]; + + // Find min and max values + int minValue = arr.Min(); + int maxValue = arr.Max(); + + // Create buckets + double bucketRange = (double)(maxValue - minValue + 1) / numBuckets; + List[] buckets = new List[numBuckets]; + for (int i = 0; i < numBuckets; i++) + { + buckets[i] = new List(); + } + + // Distribute elements into buckets + foreach (int val in arr) + { + int bucketIndex = (int)Math.Floor((val - minValue) / bucketRange); + // Handle case for max value + if (bucketIndex == numBuckets) + { + bucketIndex = numBuckets - 1; + } + buckets[bucketIndex].Add(val); + } + + // Sort individual buckets and collect them + int index = 0; + int[] result = new int[arr.Length]; + for (int i = 0; i < numBuckets; i++) + { + if (buckets[i].Count > 0) + { + // Sort each bucket + int[] bucketArray = buckets[i].ToArray(); + int[] sortedBucket = InsertionSort(bucketArray); + + // Add to result array + foreach (int val in sortedBucket) + { + result[index++] = val; + } + } + } + + return result; + } + + /// + /// Shell Sort + /// Time complexity: depends on the gap sequence, usually O(n log^2 n) + /// + public static int[] ShellSort(int[] arr) + { + int[] result = (int[])arr.Clone(); + int n = result.Length; + + // Start with a big gap, then reduce the gap + for (int gap = n / 2; gap > 0; gap /= 2) + { + // Do a gapped insertion sort for this gap size + for (int i = gap; i < n; i++) + { + // Save result[i] in temp and make a hole at position i + int temp = result[i]; + + // Shift earlier gap-sorted elements up until the correct + // location for result[i] is found + int j; + for (j = i; j >= gap && result[j - gap] > temp; j -= gap) + { + result[j] = result[j - gap]; + } + + // Put temp (the original result[i]) in its correct location + result[j] = temp; + } + } + + return result; + } + + public static void Main(string[] args) + { + // Test array + int[] testArray = { 64, 34, 25, 12, 22, 11, 90 }; + + Console.WriteLine("Original array: [{0}]", string.Join(", ", testArray)); + Console.WriteLine("Bubble Sort: [{0}]", string.Join(", ", BubbleSort(testArray))); + Console.WriteLine("Selection Sort: [{0}]", string.Join(", ", SelectionSort(testArray))); + Console.WriteLine("Insertion Sort: [{0}]", string.Join(", ", InsertionSort(testArray))); + Console.WriteLine("Merge Sort: [{0}]", string.Join(", ", MergeSort(testArray))); + Console.WriteLine("Quick Sort: [{0}]", string.Join(", ", QuickSort(testArray))); + Console.WriteLine("Heap Sort: [{0}]", string.Join(", ", HeapSort(testArray))); + Console.WriteLine("Counting Sort: [{0}]", string.Join(", ", CountingSort(testArray))); + Console.WriteLine("Radix Sort: [{0}]", string.Join(", ", RadixSort(testArray))); + Console.WriteLine("Bucket Sort: [{0}]", string.Join(", ", BucketSort(testArray, 5))); // Using 5 buckets + Console.WriteLine("Shell Sort: [{0}]", string.Join(", ", ShellSort(testArray))); + } + } +} diff --git a/snippets/algorithms/sorting-algorithms/sorting_algorithms.go b/snippets/algorithms/sorting-algorithms/sorting_algorithms.go new file mode 100644 index 0000000..1557ff1 --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.go @@ -0,0 +1,453 @@ +package main + +import ( + "fmt" + "math" +) + +// BubbleSort implements the bubble sort algorithm +// Time complexity: O(n^2) +func BubbleSort(arr []int) []int { + result := make([]int, len(arr)) + copy(result, arr) + n := len(result) + + for i := 0; i < n; i++ { + swapped := false + + for j := 0; j < n-i-1; j++ { + if result[j] > result[j+1] { + result[j], result[j+1] = result[j+1], result[j] + swapped = true + } + } + + // If no swapping occurred in this pass, the array is already sorted + if !swapped { + break + } + } + return result +} + +// SelectionSort implements the selection sort algorithm +// Time complexity: O(n^2) +func SelectionSort(arr []int) []int { + result := make([]int, len(arr)) + copy(result, arr) + n := len(result) + + for i := 0; i < n; i++ { + minIdx := i + for j := i + 1; j < n; j++ { + if result[j] < result[minIdx] { + minIdx = j + } + } + // Swap the found minimum element with the first element + result[i], result[minIdx] = result[minIdx], result[i] + } + return result +} + +// InsertionSort implements the insertion sort algorithm +// Time complexity: O(n^2) +func InsertionSort(arr []int) []int { + result := make([]int, len(arr)) + copy(result, arr) + n := len(result) + + for i := 1; i < n; i++ { + key := result[i] + j := i - 1 + + // Move elements of result[0..i-1], that are greater than key, + // to one position ahead of their current position + for j >= 0 && result[j] > key { + result[j+1] = result[j] + j-- + } + result[j+1] = key + } + return result +} + +// MergeSort implements the merge sort algorithm +// Time complexity: O(n log n) +func MergeSort(arr []int) []int { + result := make([]int, len(arr)) + copy(result, arr) + + if len(result) <= 1 { + return result + } + + // Recursive mergesort + return mergeSortHelper(result) +} + +func mergeSortHelper(arr []int) []int { + if len(arr) <= 1 { + return arr + } + + mid := len(arr) / 2 + left := mergeSortHelper(arr[:mid]) + right := mergeSortHelper(arr[mid:]) + + return merge(left, right) +} + +func merge(left, right []int) []int { + result := make([]int, 0, len(left)+len(right)) + i, j := 0, 0 + + for i < len(left) && j < len(right) { + if left[i] <= right[j] { + result = append(result, left[i]) + i++ + } else { + result = append(result, right[j]) + j++ + } + } + + // Append remaining elements + result = append(result, left[i:]...) + result = append(result, right[j:]...) + + return result +} + +// QuickSort implements the quick sort algorithm +// Time complexity: O(n log n) average, O(n^2) worst case +func QuickSort(arr []int) []int { + result := make([]int, len(arr)) + copy(result, arr) + + if len(result) <= 1 { + return result + } + + quickSortHelper(result, 0, len(result)-1) + return result +} + +func quickSortHelper(arr []int, low, high int) { + if low < high { + // pi is partitioning index + pi := partition(arr, low, high) + + // Recursively sort elements before and after partition + quickSortHelper(arr, low, pi-1) + quickSortHelper(arr, pi+1, high) + } +} + +func partition(arr []int, low, high int) int { + pivot := arr[high] // Choose the last element as pivot + i := low - 1 // Index of smaller element + + for j := low; j < high; j++ { + // If current element is smaller than the pivot + if arr[j] <= pivot { + i++ + arr[i], arr[j] = arr[j], arr[i] + } + } + + // Swap the pivot element with the element at (i+1) + arr[i+1], arr[high] = arr[high], arr[i+1] + return i + 1 +} + +// HeapSort implements the heap sort algorithm +// Time complexity: O(n log n) +func HeapSort(arr []int) []int { + result := make([]int, len(arr)) + copy(result, arr) + n := len(result) + + // Build max heap + for i := n/2 - 1; i >= 0; i-- { + heapify(result, n, i) + } + + // Extract elements from heap one by one + for i := n - 1; i > 0; i-- { + // Move current root to end + result[0], result[i] = result[i], result[0] + + // Call heapify on the reduced heap + heapify(result, i, 0) + } + + return result +} + +// heapify a subtree rooted with node i which is an index in arr[] +func heapify(arr []int, n, i int) { + largest := i // Initialize largest as root + left := 2*i + 1 // left = 2*i + 1 + right := 2*i + 2 // right = 2*i + 2 + + // If left child is larger than root + if left < n && arr[left] > arr[largest] { + largest = left + } + + // If right child is larger than largest so far + if right < n && arr[right] > arr[largest] { + largest = right + } + + // If largest is not root + if largest != i { + arr[i], arr[largest] = arr[largest], arr[i] + + // Recursively heapify the affected sub-tree + heapify(arr, n, largest) + } +} + +// CountingSort implements the counting sort algorithm +// Time complexity: O(n + k) where k is the range of the non-negative key values +func CountingSort(arr []int) []int { + if len(arr) == 0 { + return []int{} + } + + result := make([]int, len(arr)) + copy(result, arr) + + // Find the maximum and minimum element in the array + max := result[0] + min := result[0] + for _, val := range result { + if val > max { + max = val + } + if val < min { + min = val + } + } + + range_of_elements := max - min + 1 + + // Create a count array to store the count of each element + count := make([]int, range_of_elements) + output := make([]int, len(result)) + + // Store the count of each element + for i := 0; i < len(result); i++ { + count[result[i]-min]++ + } + + // Store the cumulative count + for i := 1; i < len(count); i++ { + count[i] += count[i-1] + } + + // Build the output array + for i := len(result) - 1; i >= 0; i-- { + output[count[result[i]-min]-1] = result[i] + count[result[i]-min]-- + } + + return output +} + +// RadixSort implements the radix sort algorithm +// Time complexity: O(d * (n + b)) where d is the number of digits and b is the base +func RadixSort(arr []int) []int { + if len(arr) == 0 { + return []int{} + } + + // Handle negative numbers by splitting into negative and positive arrays + hasNegative := false + for _, val := range arr { + if val < 0 { + hasNegative = true + break + } + } + + if hasNegative { + neg := []int{} + pos := []int{} + + // Split into negative and positive arrays + for _, val := range arr { + if val < 0 { + neg = append(neg, -val) // Get absolute value + } else { + pos = append(pos, val) + } + } + + // Sort both arrays + if len(neg) > 0 { + neg = RadixSort(neg) + // Reverse and negate + for i, j := 0, len(neg)-1; i < j; i, j = i+1, j-1 { + neg[i], neg[j] = neg[j], neg[i] + } + for i := range neg { + neg[i] = -neg[i] + } + } + + if len(pos) > 0 { + pos = RadixSort(pos) + } + + // Merge negative and positive arrays + return append(neg, pos...) + } + + // Find the maximum number to know number of digits + max := arr[0] + for _, val := range arr { + if val > max { + max = val + } + } + + // Do counting sort for every digit + result := make([]int, len(arr)) + copy(result, arr) + exp := 1 + + // Perform counting sort for each digit + for max/exp > 0 { + countingSortByDigit(result, exp) + exp *= 10 + } + + return result +} + +func countingSortByDigit(arr []int, exp int) { + n := len(arr) + output := make([]int, n) + count := make([]int, 10) + + // Store count of occurrences in count[] + for i := 0; i < n; i++ { + digit := (arr[i] / exp) % 10 + count[digit]++ + } + + // Change count[i] so that count[i] now contains actual + // position of this digit in output[] + for i := 1; i < 10; i++ { + count[i] += count[i-1] + } + + // Build the output array + for i := n - 1; i >= 0; i-- { + digit := (arr[i] / exp) % 10 + output[count[digit]-1] = arr[i] + count[digit]-- + } + + // Copy the output array to arr[], so that arr[] now + // contains sorted numbers according to current digit + for i := 0; i < n; i++ { + arr[i] = output[i] + } +} + +// BucketSort implements the bucket sort algorithm +// Time complexity: O(n + k) where k is the number of buckets +func BucketSort(arr []int, numBuckets int) []int { + if len(arr) == 0 { + return []int{} + } + + // Find min and max values + min := arr[0] + max := arr[0] + for _, val := range arr { + if val < min { + min = val + } + if val > max { + max = val + } + } + + // Create buckets + bucketRange := float64(max-min) / float64(numBuckets) + buckets := make([][]int, numBuckets) + for i := range buckets { + buckets[i] = []int{} + } + + // Distribute elements into buckets + for _, val := range arr { + idx := int(math.Floor(float64(val-min) / bucketRange)) + // Handle edge case for max value + if idx == numBuckets { + idx = numBuckets - 1 + } + buckets[idx] = append(buckets[idx], val) + } + + // Sort individual buckets and merge them + result := []int{} + for _, bucket := range buckets { + if len(bucket) > 0 { + sortedBucket := InsertionSort(bucket) + result = append(result, sortedBucket...) + } + } + + return result +} + +// ShellSort implements the shell sort algorithm +// Time complexity: depends on the gap sequence, usually O(n log^2 n) +func ShellSort(arr []int) []int { + result := make([]int, len(arr)) + copy(result, arr) + n := len(result) + + // Start with a big gap, then reduce the gap + for gap := n / 2; gap > 0; gap /= 2 { + // Do a gapped insertion sort for this gap size + for i := gap; i < n; i++ { + // Save result[i] in temp and make a hole at position i + temp := result[i] + + // Shift earlier gap-sorted elements up until the correct + // location for result[i] is found + var j int + for j = i; j >= gap && result[j-gap] > temp; j -= gap { + result[j] = result[j-gap] + } + + // Put temp (the original result[i]) in its correct location + result[j] = temp + } + } + return result +} + +func main() { + // Test array + testArray := []int{64, 34, 25, 12, 22, 11, 90} + + fmt.Println("Original array:", testArray) + fmt.Println("Bubble Sort:", BubbleSort(testArray)) + fmt.Println("Selection Sort:", SelectionSort(testArray)) + fmt.Println("Insertion Sort:", InsertionSort(testArray)) + fmt.Println("Merge Sort:", MergeSort(testArray)) + fmt.Println("Quick Sort:", QuickSort(testArray)) + fmt.Println("Heap Sort:", HeapSort(testArray)) + fmt.Println("Counting Sort:", CountingSort(testArray)) + fmt.Println("Radix Sort:", RadixSort(testArray)) + fmt.Println("Bucket Sort:", BucketSort(testArray, 5)) // Using 5 buckets + fmt.Println("Shell Sort:", ShellSort(testArray)) +} diff --git a/snippets/algorithms/sorting-algorithms/sorting_algorithms.java b/snippets/algorithms/sorting-algorithms/sorting_algorithms.java new file mode 100644 index 0000000..f725b4d --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.java @@ -0,0 +1,550 @@ +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.Collections; + +/** + * Collection of popular sorting algorithms implemented in Java + */ +public class SortingAlgorithms { + + /** + * Bubble Sort + * Time complexity: O(n^2) + * + * @param arr Array to be sorted + * @return Sorted array + */ + public static int[] bubbleSort(int[] arr) { + int[] result = Arrays.copyOf(arr, arr.length); + int n = result.length; + + for (int i = 0; i < n - 1; i++) { + // Flag to optimize for already sorted arrays + boolean swapped = false; + + for (int j = 0; j < n - i - 1; j++) { + if (result[j] > result[j + 1]) { + // Swap elements + int temp = result[j]; + result[j] = result[j + 1]; + result[j + 1] = temp; + swapped = true; + } + } + + // If no elements were swapped in this pass, the array is already sorted + if (!swapped) break; + } + + return result; + } + + /** + * Selection Sort + * Time complexity: O(n^2) + * + * @param arr Array to be sorted + * @return Sorted array + */ + public static int[] selectionSort(int[] arr) { + int[] result = Arrays.copyOf(arr, arr.length); + int n = result.length; + + for (int i = 0; i < n - 1; i++) { + // Find the smallest element in the unsorted part of the array + int minIdx = i; + + for (int j = i + 1; j < n; j++) { + if (result[j] < result[minIdx]) { + minIdx = j; + } + } + + // Swap the smallest element with the first element in the unsorted part + int temp = result[minIdx]; + result[minIdx] = result[i]; + result[i] = temp; + } + + return result; + } + + /** + * Insertion Sort + * Time complexity: O(n^2) + * + * @param arr Array to be sorted + * @return Sorted array + */ + public static int[] insertionSort(int[] arr) { + int[] result = Arrays.copyOf(arr, arr.length); + int n = result.length; + + for (int i = 1; i < n; i++) { + int key = result[i]; + int j = i - 1; + + // Move elements greater than key one position ahead + while (j >= 0 && result[j] > key) { + result[j + 1] = result[j]; + j--; + } + + result[j + 1] = key; + } + + return result; + } + + /** + * Merge Sort + * Time complexity: O(n log n) + * + * @param arr Array to be sorted + * @return Sorted array + */ + public static int[] mergeSort(int[] arr) { + int[] result = Arrays.copyOf(arr, arr.length); + mergeSort(result, 0, result.length - 1); + return result; + } + + private static void mergeSort(int[] arr, int left, int right) { + if (left < right) { + // Find the middle point + int mid = left + (right - left) / 2; + + // Sort first and second halves + mergeSort(arr, left, mid); + mergeSort(arr, mid + 1, right); + + // Merge the sorted halves + merge(arr, left, mid, right); + } + } + + private static void merge(int[] arr, int left, int mid, int right) { + // Find the size of the two subarrays to merge + int n1 = mid - left + 1; + int n2 = right - mid; + + // Create temporary arrays + int[] L = new int[n1]; + int[] R = new int[n2]; + + // Copy data into temporary arrays + for (int i = 0; i < n1; i++) { + L[i] = arr[left + i]; + } + for (int j = 0; j < n2; j++) { + R[j] = arr[mid + 1 + j]; + } + + // Merge the temporary arrays + int i = 0, j = 0; + int k = left; + + while (i < n1 && j < n2) { + if (L[i] <= R[j]) { + arr[k] = L[i]; + i++; + } else { + arr[k] = R[j]; + j++; + } + k++; + } + + // Copy remaining elements of L[] if any + while (i < n1) { + arr[k] = L[i]; + i++; + k++; + } + + // Copy remaining elements of R[] if any + while (j < n2) { + arr[k] = R[j]; + j++; + k++; + } + } + + /** + * Quick Sort + * Time complexity: O(n log n) average, O(n^2) worst case + * + * @param arr Array to be sorted + * @return Sorted array + */ + public static int[] quickSort(int[] arr) { + int[] result = Arrays.copyOf(arr, arr.length); + quickSort(result, 0, result.length - 1); + return result; + } + + private static void quickSort(int[] arr, int low, int high) { + if (low < high) { + // pi is the partition index, arr[pi] is already in the correct position + int pi = partition(arr, low, high); + + // Recursively sort elements before and after pivot + quickSort(arr, low, pi - 1); + quickSort(arr, pi + 1, high); + } + } + + private static int partition(int[] arr, int low, int high) { + // Choose pivot as the last element + int pivot = arr[high]; + int i = low - 1; // Index of the element smaller than pivot + + for (int j = low; j < high; j++) { + // If current element is less than or equal to pivot + if (arr[j] <= pivot) { + i++; + + // Swap arr[i] and arr[j] + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + } + + // Swap arr[i+1] and arr[high] (place pivot in the correct position) + int temp = arr[i + 1]; + arr[i + 1] = arr[high]; + arr[high] = temp; + + return i + 1; + } + + /** + * Heap Sort + * Time complexity: O(n log n) + * + * @param arr Array to be sorted + * @return Sorted array + */ + public static int[] heapSort(int[] arr) { + int[] result = Arrays.copyOf(arr, arr.length); + int n = result.length; + + // Build heap (heapify) + for (int i = n / 2 - 1; i >= 0; i--) { + heapify(result, n, i); + } + + // One by one extract an element from heap + for (int i = n - 1; i > 0; i--) { + // Move current root to end + int temp = result[0]; + result[0] = result[i]; + result[i] = temp; + + // Call max heapify on the reduced heap + heapify(result, i, 0); + } + + return result; + } + + private static void heapify(int[] arr, int n, int i) { + int largest = i; // Initialize largest as root + int left = 2 * i + 1; // Left child index = 2*i + 1 + int right = 2 * i + 2; // Right child index = 2*i + 2 + + // If left child is larger than root + if (left < n && arr[left] > arr[largest]) { + largest = left; + } + + // If right child is larger than root + if (right < n && arr[right] > arr[largest]) { + largest = right; + } + + // If largest is not root + if (largest != i) { + int swap = arr[i]; + arr[i] = arr[largest]; + arr[largest] = swap; + + // Recursively heapify the affected sub-tree + heapify(arr, n, largest); + } + } + + /** + * Counting Sort + * Time complexity: O(n + k) with k being the range of input elements + * + * @param arr Array to be sorted + * @return Sorted array + */ + public static int[] countingSort(int[] arr) { + if (arr.length == 0) return new int[0]; + + // Find the largest and smallest elements + int max = arr[0], min = arr[0]; + for (int i = 1; i < arr.length; i++) { + if (arr[i] > max) max = arr[i]; + if (arr[i] < min) min = arr[i]; + } + + int range = max - min + 1; + + // Create count array and output array + int[] count = new int[range]; + int[] output = new int[arr.length]; + + // Count occurrences of each element + for (int i = 0; i < arr.length; i++) { + count[arr[i] - min]++; + } + + // Update count[i] to contain the actual position of this value in output + for (int i = 1; i < count.length; i++) { + count[i] += count[i - 1]; + } + + // Build output array + for (int i = arr.length - 1; i >= 0; i--) { + output[count[arr[i] - min] - 1] = arr[i]; + count[arr[i] - min]--; + } + + return output; + } + + /** + * Radix Sort + * Time complexity: O(d * (n + b)) with d being the number of digits in the largest number, b being the base + * + * @param arr Array to be sorted (only applies to non-negative integers) + * @return Sorted array + */ + public static int[] radixSort(int[] arr) { + if (arr.length == 0) return new int[0]; + + // Check for negative numbers + boolean hasNegative = false; + for (int i = 0; i < arr.length; i++) { + if (arr[i] < 0) { + hasNegative = true; + break; + } + } + + if (hasNegative) { + // Separate into negative and positive lists + List negatives = new ArrayList<>(); + List positives = new ArrayList<>(); + + for (int value : arr) { + if (value < 0) { + negatives.add(-value); // Get absolute value + } else { + positives.add(value); + } + } + + // Convert list to array + int[] negArray = new int[negatives.size()]; + for (int i = 0; i < negatives.size(); i++) { + negArray[i] = negatives.get(i); + } + + int[] posArray = new int[positives.size()]; + for (int i = 0; i < positives.size(); i++) { + posArray[i] = positives.get(i); + } + + // Sort each array + int[] sortedNegs = radixSort(negArray); + int[] sortedPos = posArray.length > 0 ? radixSort(posArray) : new int[0]; + + // Combine: negative (reverse and sign) + positive + int[] result = new int[arr.length]; + int index = 0; + + // Add negative (reverse and sign) + for (int i = sortedNegs.length - 1; i >= 0; i--) { + result[index++] = -sortedNegs[i]; + } + + // Add positive + for (int i = 0; i < sortedPos.length; i++) { + result[index++] = sortedPos[i]; + } + + return result; + } + + // Case with only non-negative numbers + int[] result = Arrays.copyOf(arr, arr.length); + + // Find the largest number to know the number of digits + int max = getMax(result); + + // Perform counting sort for each digit + for (int exp = 1; max / exp > 0; exp *= 10) { + countingSortByDigit(result, exp); + } + + return result; + } + + private static void countingSortByDigit(int[] arr, int exp) { + int n = arr.length; + int[] output = new int[n]; + int[] count = new int[10]; // 0-9 digits + + // Count occurrences of each digit + for (int i = 0; i < n; i++) { + count[(arr[i] / exp) % 10]++; + } + + // Update count to contain the actual position + for (int i = 1; i < 10; i++) { + count[i] += count[i - 1]; + } + + // Build output array + for (int i = n - 1; i >= 0; i--) { + int digit = (arr[i] / exp) % 10; + output[count[digit] - 1] = arr[i]; + count[digit]--; + } + + // Copy output array to arr + for (int i = 0; i < n; i++) { + arr[i] = output[i]; + } + } + + private static int getMax(int[] arr) { + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + if (arr[i] > max) { + max = arr[i]; + } + } + return max; + } + + /** + * Bucket Sort + * Time complexity: O(n + k) with k being the number of buckets + * + * @param arr Array to be sorted + * @return Sorted array + */ + public static int[] bucketSort(int[] arr) { + return bucketSort(arr, 5); // Default 5 elements per bucket + } + + public static int[] bucketSort(int[] arr, int bucketSize) { + if (arr.length == 0) return new int[0]; + + // Find the largest and smallest values + int minValue = arr[0]; + int maxValue = arr[0]; + + for (int i = 1; i < arr.length; i++) { + if (arr[i] < minValue) { + minValue = arr[i]; + } else if (arr[i] > maxValue) { + maxValue = arr[i]; + } + } + + // Create buckets + int bucketCount = (maxValue - minValue) / bucketSize + 1; + List> buckets = new ArrayList<>(bucketCount); + for (int i = 0; i < bucketCount; i++) { + buckets.add(new ArrayList<>()); + } + + // Distribute elements into buckets + for (int i = 0; i < arr.length; i++) { + int bucketIndex = (arr[i] - minValue) / bucketSize; + buckets.get(bucketIndex).add(arr[i]); + } + + // Sort each bucket and combine + int[] result = new int[arr.length]; + int currentIndex = 0; + + for (int i = 0; i < buckets.size(); i++) { + List bucket = buckets.get(i); + if (bucket.isEmpty()) { + continue; + } + + // Sort bucket + Collections.sort(bucket); + + // Add sorted elements from bucket to result + for (int j = 0; j < bucket.size(); j++) { + result[currentIndex++] = bucket.get(j); + } + } + + return result; + } + + /** + * Shell Sort + * Time complexity: depends on the gap sequence, typically O(n log^2 n) + * + * @param arr Array to be sorted + * @return Sorted array + */ + public static int[] shellSort(int[] arr) { + int[] result = Arrays.copyOf(arr, arr.length); + int n = result.length; + + // Start with large gap, then decrease + for (int gap = n/2; gap > 0; gap /= 2) { + // Perform insertion sort with gap + for (int i = gap; i < n; i++) { + // Add arr[i] to the sorted gap elements + // Save arr[i] in temp and create a hole at position i + int temp = result[i]; + + // Shift sorted gap elements until the correct position for temp is found + int j; + for (j = i; j >= gap && result[j - gap] > temp; j -= gap) { + result[j] = result[j - gap]; + } + + // Place temp in the correct position + result[j] = temp; + } + } + + return result; + } + + /** + * Main method to test sorting algorithms + */ + public static void main(String[] args) { + // Test array + int[] testArray = {64, 34, 25, 12, 22, 11, 90}; + + System.out.println("Original array: " + Arrays.toString(testArray)); + System.out.println("Bubble Sort: " + Arrays.toString(bubbleSort(testArray))); + System.out.println("Selection Sort: " + Arrays.toString(selectionSort(testArray))); + System.out.println("Insertion Sort: " + Arrays.toString(insertionSort(testArray))); + System.out.println("Merge Sort: " + Arrays.toString(mergeSort(testArray))); + System.out.println("Quick Sort: " + Arrays.toString(quickSort(testArray))); + System.out.println("Heap Sort: " + Arrays.toString(heapSort(testArray))); + System.out.println("Counting Sort: " + Arrays.toString(countingSort(testArray))); + System.out.println("Radix Sort: " + Arrays.toString(radixSort(testArray))); + System.out.println("Bucket Sort: " + Arrays.toString(bucketSort(testArray))); + System.out.println("Shell Sort: " + Arrays.toString(shellSort(testArray))); + } +} \ No newline at end of file diff --git a/snippets/algorithms/sorting-algorithms/sorting_algorithms.js b/snippets/algorithms/sorting-algorithms/sorting_algorithms.js new file mode 100644 index 0000000..03443eb --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.js @@ -0,0 +1,445 @@ +/** + * Sorting Algorithms in JavaScript + * + * This module provides implementations of various sorting algorithms. + * + * Example usage: + * node sorting_algorithms.js + */ + +/** + * Bubble Sort + * Time complexity: O(n^2) + * @param {Array} arr - Array to be sorted + * @returns {Array} Sorted array + */ +function bubbleSort(arr) { + const n = arr.length; + const result = [...arr]; // Create a copy to avoid modifying the original array + + for (let i = 0; i < n; i++) { + // Flag to optimize for already sorted arrays + let swapped = false; + + for (let j = 0; j < n - i - 1; j++) { + if (result[j] > result[j + 1]) { + // Swap elements + [result[j], result[j + 1]] = [result[j + 1], result[j]]; + swapped = true; + } + } + + // If no elements were swapped in this iteration, the array is already sorted + if (!swapped) break; + } + + return result; +} + +/** + * Selection Sort + * Time complexity: O(n^2) + * @param {Array} arr - Array to be sorted + * @returns {Array} Sorted array + */ +function selectionSort(arr) { + const n = arr.length; + const result = [...arr]; // Create a copy to avoid modifying the original array + + for (let i = 0; i < n; i++) { + // Find the smallest element in the unsorted part of the array + let minIdx = i; + + for (let j = i + 1; j < n; j++) { + if (result[j] < result[minIdx]) { + minIdx = j; + } + } + + // Swap the smallest element with the first element in the unsorted part + if (minIdx !== i) { + [result[i], result[minIdx]] = [result[minIdx], result[i]]; + } + } + + return result; +} + +/** + * Insertion Sort + * Time complexity: O(n^2) + * @param {Array} arr - Array to be sorted + * @returns {Array} Sorted array + */ +function insertionSort(arr) { + const n = arr.length; + const result = [...arr]; // Create a copy to avoid modifying the original array + + for (let i = 1; i < n; i++) { + const key = result[i]; + let j = i - 1; + + // Move elements greater than key one position ahead + while (j >= 0 && result[j] > key) { + result[j + 1] = result[j]; + j--; + } + + result[j + 1] = key; + } + + return result; +} + +/** + * Merge Sort + * Time complexity: O(n log n) + * @param {Array} arr - Array to be sorted + * @returns {Array} Sorted array + */ +function mergeSort(arr) { + // Function to merge two sorted arrays + function merge(left, right) { + const result = []; + let i = 0; + let j = 0; + + // Merge the two sorted arrays + while (i < left.length && j < right.length) { + if (left[i] <= right[j]) { + result.push(left[i++]); + } else { + result.push(right[j++]); + } + } + + // Add remaining elements + return result.concat(left.slice(i)).concat(right.slice(j)); + } + + // Merge sort function + function sort(arr) { + const n = arr.length; + + // Base case: array with 0 or 1 element + if (n <= 1) { + return arr; + } + + // Divide array into two halves + const mid = Math.floor(n / 2); + const left = arr.slice(0, mid); + const right = arr.slice(mid); + + // Recursively sort and merge + return merge(sort(left), sort(right)); + } + + return sort([...arr]); // Create a copy to avoid modifying the original array +} + +/** + * Quick Sort + * Time complexity: O(n log n) average, O(n^2) worst case + * @param {Array} arr - Array to be sorted + * @returns {Array} Sorted array + */ +function quickSort(arr) { + // Create a copy to avoid modifying the original array + const result = [...arr]; + + // Partition function + function partition(arr, low, high) { + // Choose pivot as the last element + const pivot = arr[high]; + let i = low - 1; + + for (let j = low; j < high; j++) { + // If current element is less than or equal to pivot + if (arr[j] <= pivot) { + i++; + // Swap elements at indices i and j + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + } + + // Swap elements at indices i+1 and high (place pivot in correct position) + [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]]; + return i + 1; + } + + // Quick sort function + function sort(arr, low, high) { + if (low < high) { + // pi is the partition index, arr[pi] is already in correct position + const pi = partition(arr, low, high); + + // Recursively sort elements before and after pivot + sort(arr, low, pi - 1); + sort(arr, pi + 1, high); + } + return arr; + } + + return sort(result, 0, result.length - 1); +} + +/** + * Heap Sort + * Time complexity: O(n log n) + * @param {Array} arr - Array to be sorted + * @returns {Array} Sorted array + */ +function heapSort(arr) { + const result = [...arr]; // Create a copy to avoid modifying the original array + const n = result.length; + + // Build heap (heapify) + function heapify(arr, n, i) { + let largest = i; // Initialize largest as root + const left = 2 * i + 1; // Left child index = 2*i + 1 + const right = 2 * i + 2; // Right child index = 2*i + 2 + + // If left child is greater than root + if (left < n && arr[left] > arr[largest]) { + largest = left; + } + + // If right child is greater than root + if (right < n && arr[right] > arr[largest]) { + largest = right; + } + + // If largest is not root + if (largest !== i) { + [arr[i], arr[largest]] = [arr[largest], arr[i]]; // Swap elements + + // Recursively heapify the affected subtree + heapify(arr, n, largest); + } + } + + // Build max heap + for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { + heapify(result, n, i); + } + + // One by one extract elements from heap + for (let i = n - 1; i > 0; i--) { + // Move current root to end + [result[0], result[i]] = [result[i], result[0]]; + + // Call max heapify on the reduced heap + heapify(result, i, 0); + } + + return result; +} + +/** + * Counting Sort + * Time complexity: O(n + k) with k being the range of input elements + * @param {Array} arr - Array to be sorted + * @returns {Array} Sorted array + */ +function countingSort(arr) { + if (arr.length === 0) return []; + + // Find the maximum and minimum elements + const max = Math.max(...arr); + const min = Math.min(...arr); + const range = max - min + 1; + + // Create count array and initialize with 0 + const count = new Array(range).fill(0); + const output = new Array(arr.length); + + // Count occurrences of each element + for (let i = 0; i < arr.length; i++) { + count[arr[i] - min]++; + } + + // Update count[i] to contain the actual position of this value in output + for (let i = 1; i < count.length; i++) { + count[i] += count[i - 1]; + } + + // Build output array + for (let i = arr.length - 1; i >= 0; i--) { + output[count[arr[i] - min] - 1] = arr[i]; + count[arr[i] - min]--; + } + + return output; +} + +/** + * Radix Sort + * Time complexity: O(d * (n + b)) with d being the number of digits and b being the base + * @param {Array} arr - Array to be sorted (only applies to non-negative integers) + * @returns {Array} Sorted array + */ +function radixSort(arr) { + if (arr.length === 0) return []; + + // Handle negative numbers + const hasNegative = arr.some(x => x < 0); + if (hasNegative) { + // Separate into negative and positive arrays + const negatives = arr.filter(x => x < 0).map(x => -x); + const positives = arr.filter(x => x >= 0); + + // Sort absolute values of negative numbers + const sortedNegatives = radixSort(negatives).map(x => -x).reverse(); + // Sort positive numbers + const sortedPositives = positives.length ? radixSort(positives) : []; + + // Combine: negative (reversed) + positive + return [...sortedNegatives, ...sortedPositives]; + } + + // Find the maximum number of digits + const max = Math.max(...arr); + let maxDigits = 0; + if (max > 0) { + maxDigits = Math.floor(Math.log10(max)) + 1; + } + + // Create a copy to avoid modifying the original array + let result = [...arr]; + + // Radix sort + for (let digit = 0; digit < maxDigits; digit++) { + // Use counting sort for each digit + const count = new Array(10).fill(0); + const output = new Array(result.length); + const divisor = Math.pow(10, digit); + + // Count occurrences of each digit + for (let i = 0; i < result.length; i++) { + const digitValue = Math.floor((result[i] / divisor) % 10); + count[digitValue]++; + } + + // Update count to contain the actual position + for (let i = 1; i < 10; i++) { + count[i] += count[i - 1]; + } + + // Build output array + for (let i = result.length - 1; i >= 0; i--) { + const digitValue = Math.floor((result[i] / divisor) % 10); + output[count[digitValue] - 1] = result[i]; + count[digitValue]--; + } + + result = output; + } + + return result; +} + +/** + * Sắp xếp xô - Bucket Sort + * Độ phức tạp thời gian: O(n + k) với k là số lượng buckets + * @param {Array} arr - Mảng cần sắp xếp + * @param {number} [bucketSize=5] - Kích thước xô + * @returns {Array} Mảng đã sắp xếp + */ +function bucketSort(arr, bucketSize = 5) { + if (arr.length === 0) return []; + + // Tìm giá trị lớn nhất và nhỏ nhất + const min = Math.min(...arr); + const max = Math.max(...arr); + + // Tạo bucket + const bucketCount = Math.floor((max - min) / bucketSize) + 1; + const buckets = new Array(bucketCount); + for (let i = 0; i < bucketCount; i++) { + buckets[i] = []; + } + + // Phân phối các phần tử vào bucket + for (const num of arr) { + const bucketIndex = Math.floor((num - min) / bucketSize); + buckets[bucketIndex].push(num); + } + + // Sắp xếp từng bucket và ghép lại + const result = []; + for (const bucket of buckets) { + if (bucket.length > 0) { + // Sắp xếp từng bucket bằng insertion sort + insertionSort(bucket); + result.push(...bucket); + } + } + + return result; +} + +/** + * Sắp xếp Shell - Shell Sort + * Độ phức tạp thời gian: phụ thuộc vào dãy khoảng cách, thường là O(n log^2 n) + * @param {Array} arr - Mảng cần sắp xếp + * @returns {Array} Mảng đã sắp xếp + */ +function shellSort(arr) { + const n = arr.length; + const result = [...arr]; // Tạo bản sao để không thay đổi mảng gốc + + // Bắt đầu với khoảng cách lớn, sau đó giảm dần + for (let gap = Math.floor(n / 2); gap > 0; gap = Math.floor(gap / 2)) { + // Thực hiện insertion sort với khoảng cách gap + for (let i = gap; i < n; i++) { + // Thêm arr[i] vào các phần tử đã được sắp xếp gap + // Lưu arr[i] trong temp và tạo lỗ hổng tại vị trí i + const temp = result[i]; + + // Dịch chuyển các phần tử đã sắp xếp gap cho đến khi tìm thấy vị trí đúng cho temp + let j; + for (j = i; j >= gap && result[j - gap] > temp; j -= gap) { + result[j] = result[j - gap]; + } + + // Đặt temp vào vị trí đúng + result[j] = temp; + } + } + + return result; +} + +// Kiểm thử với một mảng +function testSortingAlgorithms() { + // Mảng test + const testArray = [64, 34, 25, 12, 22, 11, 90]; + + console.log("Mảng ban đầu:", testArray); + console.log("Bubble Sort:", bubbleSort(testArray)); + console.log("Selection Sort:", selectionSort(testArray)); + console.log("Insertion Sort:", insertionSort(testArray)); + console.log("Merge Sort:", mergeSort(testArray)); + console.log("Quick Sort:", quickSort(testArray)); + console.log("Heap Sort:", heapSort(testArray)); + console.log("Counting Sort:", countingSort(testArray)); + console.log("Radix Sort:", radixSort(testArray)); + console.log("Bucket Sort:", bucketSort(testArray)); + console.log("Shell Sort:", shellSort(testArray)); +} + +// Xuất các thuật toán +module.exports = { + bubbleSort, + selectionSort, + insertionSort, + mergeSort, + quickSort, + heapSort, + countingSort, + radixSort, + bucketSort, + shellSort, + testSortingAlgorithms +}; \ No newline at end of file diff --git a/snippets/algorithms/sorting-algorithms/sorting_algorithms.php b/snippets/algorithms/sorting-algorithms/sorting_algorithms.php new file mode 100644 index 0000000..a2431af --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.php @@ -0,0 +1,468 @@ + $result[$j + 1]) { + // Swap elements + $temp = $result[$j]; + $result[$j] = $result[$j + 1]; + $result[$j + 1] = $temp; + $swapped = true; + } + } + + // If no swapping occurred in this pass, the array is already sorted + if (!$swapped) { + break; + } + } + + return $result; +} + +/** + * Selection Sort + * Time complexity: O(n^2) + */ +function selectionSort($arr) { + $result = $arr; + $n = count($result); + + for ($i = 0; $i < $n - 1; $i++) { + // Find the minimum element in the unsorted part + $minIdx = $i; + for ($j = $i + 1; $j < $n; $j++) { + if ($result[$j] < $result[$minIdx]) { + $minIdx = $j; + } + } + + // Swap the found minimum element with the first element + $temp = $result[$minIdx]; + $result[$minIdx] = $result[$i]; + $result[$i] = $temp; + } + + return $result; +} + +/** + * Insertion Sort + * Time complexity: O(n^2) + */ +function insertionSort($arr) { + $result = $arr; + $n = count($result); + + for ($i = 1; $i < $n; $i++) { + $key = $result[$i]; + $j = $i - 1; + + // Move elements greater than key one position ahead + while ($j >= 0 && $result[$j] > $key) { + $result[$j + 1] = $result[$j]; + $j--; + } + $result[$j + 1] = $key; + } + + return $result; +} + +/** + * Merge Sort + * Time complexity: O(n log n) + */ +function mergeSort($arr) { + $result = $arr; + $n = count($result); + + if ($n <= 1) { + return $result; + } + + $mid = (int)($n / 2); + $left = array_slice($result, 0, $mid); + $right = array_slice($result, $mid); + + $left = mergeSort($left); + $right = mergeSort($right); + + return merge($left, $right); +} + +/** + * Helper function for merge sort + */ +function merge($left, $right) { + $result = []; + $i = $j = 0; + $leftCount = count($left); + $rightCount = count($right); + + // Merge the two arrays + while ($i < $leftCount && $j < $rightCount) { + if ($left[$i] <= $right[$j]) { + $result[] = $left[$i]; + $i++; + } else { + $result[] = $right[$j]; + $j++; + } + } + + // Add remaining elements + while ($i < $leftCount) { + $result[] = $left[$i]; + $i++; + } + + while ($j < $rightCount) { + $result[] = $right[$j]; + $j++; + } + + return $result; +} + +/** + * Quick Sort + * Time complexity: O(n log n) average, O(n^2) worst case + */ +function quickSort($arr) { + if (count($arr) <= 1) { + return $arr; + } + + $result = $arr; + quickSortHelper($result, 0, count($result) - 1); + return $result; +} + +/** + * Helper function for quick sort + */ +function quickSortHelper(&$arr, $low, $high) { + if ($low < $high) { + // pi is the partitioning index + $pi = partition($arr, $low, $high); + + // Recursively sort elements before and after partition + quickSortHelper($arr, $low, $pi - 1); + quickSortHelper($arr, $pi + 1, $high); + } +} + +/** + * Partition function for quick sort + */ +function partition(&$arr, $low, $high) { + $pivot = $arr[$high]; // Choose the last element as pivot + $i = $low - 1; // Index of smaller element + + for ($j = $low; $j < $high; $j++) { + // If current element is smaller than or equal to pivot + if ($arr[$j] <= $pivot) { + $i++; + + // Swap arr[i] and arr[j] + $temp = $arr[$i]; + $arr[$i] = $arr[$j]; + $arr[$j] = $temp; + } + } + + // Swap arr[i+1] and arr[high] (pivot) + $temp = $arr[$i + 1]; + $arr[$i + 1] = $arr[$high]; + $arr[$high] = $temp; + + return $i + 1; +} + +/** + * Heap Sort + * Time complexity: O(n log n) + */ +function heapSort($arr) { + $result = $arr; + $n = count($result); + + // Build max heap + for ($i = (int)($n / 2) - 1; $i >= 0; $i--) { + heapify($result, $n, $i); + } + + // Extract elements from heap one by one + for ($i = $n - 1; $i > 0; $i--) { + // Move current root to end + $temp = $result[0]; + $result[0] = $result[$i]; + $result[$i] = $temp; + + // Call heapify on the reduced heap + heapify($result, $i, 0); + } + + return $result; +} + +/** + * Helper function for heap sort + */ +function heapify(&$arr, $n, $i) { + $largest = $i; // Initialize largest as root + $left = 2 * $i + 1; // left = 2*i + 1 + $right = 2 * $i + 2; // right = 2*i + 2 + + // If left child is larger than root + if ($left < $n && $arr[$left] > $arr[$largest]) { + $largest = $left; + } + + // If right child is larger than largest so far + if ($right < $n && $arr[$right] > $arr[$largest]) { + $largest = $right; + } + + // If largest is not root + if ($largest != $i) { + $swap = $arr[$i]; + $arr[$i] = $arr[$largest]; + $arr[$largest] = $swap; + + // Recursively heapify the affected sub-tree + heapify($arr, $n, $largest); + } +} + +/** + * Counting Sort + * Time complexity: O(n + k) where k is the range of input elements + */ +function countingSort($arr) { + if (empty($arr)) { + return []; + } + + // Find the maximum and minimum element in the array + $max = max($arr); + $min = min($arr); + $range = $max - $min + 1; + + // Create a count array and result array + $count = array_fill(0, $range, 0); + $output = array_fill(0, count($arr), 0); + + // Store count of each element + foreach ($arr as $val) { + $count[$val - $min]++; + } + + // Change count[i] so that count[i] now contains the actual + // position of this element in output array + for ($i = 1; $i < $range; $i++) { + $count[$i] += $count[$i - 1]; + } + + // Build the output array + for ($i = count($arr) - 1; $i >= 0; $i--) { + $output[$count[$arr[$i] - $min] - 1] = $arr[$i]; + $count[$arr[$i] - $min]--; + } + + return $output; +} + +/** + * Radix Sort + * Time complexity: O(d * (n + b)) with d being the number of digits and b being the base + */ +function radixSort($arr) { + if (empty($arr)) { + return []; + } + + // Handle negative numbers + $hasNegative = false; + foreach ($arr as $val) { + if ($val < 0) { + $hasNegative = true; + break; + } + } + + if ($hasNegative) { + // Separate into negative and positive arrays + $neg = []; + $pos = []; + + foreach ($arr as $val) { + if ($val < 0) { + $neg[] = -$val; // Get absolute value + } else { + $pos[] = $val; + } + } + + // Sort absolute values of negative numbers + if (!empty($neg)) { + $neg = radixSort($neg); + // Reverse and negate + $neg = array_reverse($neg); + foreach ($neg as &$val) { + $val = -$val; + } + } + + // Sort positive numbers + if (!empty($pos)) { + $pos = radixSort($pos); + } + + // Merge negative and positive arrays + return array_merge($neg, $pos); + } + + // Find the maximum number to know the number of digits + $max = max($arr); + + // Perform counting sort for each digit + $result = $arr; + $exp = 1; + + while ((int)($max / $exp) > 0) { + $result = countingSortByDigit($result, $exp); + $exp *= 10; + } + + return $result; +} + +/** + * Helper function for radix sort + */ +function countingSortByDigit($arr, $exp) { + $n = count($arr); + $output = array_fill(0, $n, 0); + $count = array_fill(0, 10, 0); // Count array for digits 0-9 + + // Store count of occurrences in count[] + for ($i = 0; $i < $n; $i++) { + $digit = (int)(($arr[$i] / $exp) % 10); + $count[$digit]++; + } + + // Change count[i] so that count[i] now contains the actual + // position of this digit in output[] + for ($i = 1; $i < 10; $i++) { + $count[$i] += $count[$i - 1]; + } + + // Build the output array + for ($i = $n - 1; $i >= 0; $i--) { + $digit = (int)(($arr[$i] / $exp) % 10); + $output[$count[$digit] - 1] = $arr[$i]; + $count[$digit]--; + } + + return $output; +} + +/** + * Bucket Sort + * Time complexity: O(n + k) where k is the number of buckets + */ +function bucketSort($arr, $numBuckets = 10) { + if (empty($arr)) { + return []; + } + + // Find min and max values + $minValue = min($arr); + $maxValue = max($arr); + + // Create buckets + $bucketRange = ($maxValue - $minValue + 1) / $numBuckets; + $buckets = array_fill(0, $numBuckets, []); + + // Distribute elements into buckets + foreach ($arr as $val) { + $bucketIndex = (int)(($val - $minValue) / $bucketRange); + // Handle case for max value + if ($bucketIndex == $numBuckets) { + $bucketIndex = $numBuckets - 1; + } + $buckets[$bucketIndex][] = $val; + } + + // Sort individual buckets and collect them + $result = []; + foreach ($buckets as $bucket) { + if (!empty($bucket)) { + // Sort each bucket using insertion sort + $sortedBucket = insertionSort($bucket); + $result = array_merge($result, $sortedBucket); + } + } + + return $result; +} + +/** + * Shell Sort + * Time complexity: depends on the gap sequence, usually O(n log^2 n) + */ +function shellSort($arr) { + $result = $arr; + $n = count($result); + + // Start with a big gap, then reduce the gap + for ($gap = (int)($n / 2); $gap > 0; $gap = (int)($gap / 2)) { + // Do a gapped insertion sort for this gap size + for ($i = $gap; $i < $n; $i++) { + // Save result[i] in temp and make a hole at position i + $temp = $result[$i]; + + // Shift earlier gap-sorted elements up until the correct + // location for result[i] is found + $j = $i; + while ($j >= $gap && $result[$j - $gap] > $temp) { + $result[$j] = $result[$j - $gap]; + $j -= $gap; + } + + // Put temp (the original result[i]) in its correct location + $result[$j] = $temp; + } + } + + return $result; +} + +// Test with an array +$testArray = [64, 34, 25, 12, 22, 11, 90]; + +echo "Original array: " . implode(", ", $testArray) . "\n"; +echo "Bubble Sort: " . implode(", ", bubbleSort($testArray)) . "\n"; +echo "Selection Sort: " . implode(", ", selectionSort($testArray)) . "\n"; +echo "Insertion Sort: " . implode(", ", insertionSort($testArray)) . "\n"; +echo "Merge Sort: " . implode(", ", mergeSort($testArray)) . "\n"; +echo "Quick Sort: " . implode(", ", quickSort($testArray)) . "\n"; +echo "Heap Sort: " . implode(", ", heapSort($testArray)) . "\n"; +echo "Counting Sort: " . implode(", ", countingSort($testArray)) . "\n"; +echo "Radix Sort: " . implode(", ", radixSort($testArray)) . "\n"; +echo "Bucket Sort: " . implode(", ", bucketSort($testArray, 5)) . "\n"; // Using 5 buckets +echo "Shell Sort: " . implode(", ", shellSort($testArray)) . "\n"; + +?> diff --git a/snippets/algorithms/sorting-algorithms/sorting_algorithms.py b/snippets/algorithms/sorting-algorithms/sorting_algorithms.py new file mode 100644 index 0000000..bf1746f --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.py @@ -0,0 +1,355 @@ +def bubble_sort(arr): + """ + Bubble Sort + Time complexity: O(n^2) + """ + n = len(arr) + for i in range(n): + # Flag to optimize for already sorted arrays + swapped = False + + for j in range(0, n - i - 1): + if arr[j] > arr[j + 1]: + arr[j], arr[j + 1] = arr[j + 1], arr[j] + swapped = True + + # If no elements were swapped in this iteration, the array is already sorted + if not swapped: + break + + return arr + + +def selection_sort(arr): + """ + Selection Sort + Time complexity: O(n^2) + """ + n = len(arr) + + for i in range(n): + min_idx = i + + for j in range(i + 1, n): + if arr[j] < arr[min_idx]: + min_idx = j + + # Swap the smallest element with the first element in the unsorted part + arr[i], arr[min_idx] = arr[min_idx], arr[i] + + return arr + + +def insertion_sort(arr): + """ + Insertion Sort + Time complexity: O(n^2) + """ + for i in range(1, len(arr)): + key = arr[i] + j = i - 1 + + # Move elements greater than key one position ahead + while j >= 0 and arr[j] > key: + arr[j + 1] = arr[j] + j -= 1 + + arr[j + 1] = key + + return arr + + +def merge_sort(arr): + """ + Merge Sort + Time complexity: O(n log n) + """ + if len(arr) > 1: + mid = len(arr) // 2 + L = arr[:mid] + R = arr[mid:] + + # Recursively sort the first half + merge_sort(L) + + # Recursively sort the second half + merge_sort(R) + + i = j = k = 0 + + # Copy data into temporary arrays L[] and R[] + while i < len(L) and j < len(R): + if L[i] <= R[j]: + arr[k] = L[i] + i += 1 + else: + arr[k] = R[j] + j += 1 + k += 1 + + # Check if there are any remaining elements + while i < len(L): + arr[k] = L[i] + i += 1 + k += 1 + + while j < len(R): + arr[k] = R[j] + j += 1 + k += 1 + + return arr + + +def quick_sort(arr): + """ + Quick Sort + Time complexity: O(n log n) average, O(n^2) worst case + """ + if len(arr) <= 1: + return arr + + def partition(arr, low, high): + pivot = arr[high] # Choose pivot as the last element + i = low - 1 # Index of the element less than pivot + + for j in range(low, high): + # If current element is less than or equal to pivot + if arr[j] <= pivot: + i += 1 + arr[i], arr[j] = arr[j], arr[i] + + arr[i + 1], arr[high] = arr[high], arr[i + 1] + return i + 1 + + def quick_sort_helper(arr, low, high): + if low < high: + # pi is the partition index, arr[pi] is already in correct position + pi = partition(arr, low, high) + + # Recursively sort elements before and after partition + quick_sort_helper(arr, low, pi - 1) + quick_sort_helper(arr, pi + 1, high) + + arr_copy = arr.copy() + quick_sort_helper(arr_copy, 0, len(arr_copy) - 1) + return arr_copy + + +def heap_sort(arr): + """ + Heap Sort + Time complexity: O(n log n) + """ + def heapify(arr, n, i): + largest = i # Initialize largest as root + left = 2 * i + 1 + right = 2 * i + 2 + + # Check if left child is greater than root + if left < n and arr[i] < arr[left]: + largest = left + + # Check if right child is greater than root + if right < n and arr[largest] < arr[right]: + largest = right + + # Change root if necessary + if largest != i: + arr[i], arr[largest] = arr[largest], arr[i] + + # Heapify the root + heapify(arr, n, largest) + + arr_copy = arr.copy() + n = len(arr_copy) + + # Build max heap + for i in range(n // 2 - 1, -1, -1): + heapify(arr_copy, n, i) + + # Extract elements from heap one by one + for i in range(n - 1, 0, -1): + arr_copy[i], arr_copy[0] = arr_copy[0], arr_copy[i] # Swap elements + heapify(arr_copy, i, 0) + + return arr_copy + + +def counting_sort(arr): + """ + Counting Sort + Time complexity: O(n + k) with k being the range of input elements + """ + # Find the maximum element in the array + if not arr: + return [] + + max_val = max(arr) + min_val = min(arr) + range_of_elements = max_val - min_val + 1 + + # Create count array and result array + count = [0] * range_of_elements + output = [0] * len(arr) + + # Count occurrences of each element + for i in range(0, len(arr)): + count[arr[i] - min_val] += 1 + + # Update count[i] to contain the actual position of this value in output + for i in range(1, len(count)): + count[i] += count[i - 1] + + # Build output array + for i in range(len(arr) - 1, -1, -1): + output[count[arr[i] - min_val] - 1] = arr[i] + count[arr[i] - min_val] -= 1 + + return output + + +def radix_sort(arr): + """ + Radix Sort + Time complexity: O(d * (n + b)) with d being the number of digits and b being the base + """ + if not arr: + return [] + + # Find the maximum number to know the number of digits + max_num = max(abs(x) for x in arr) + + # Handle negative numbers + has_negative = any(x < 0 for x in arr) + if has_negative: + # Separate into negative and positive arrays + neg = [x for x in arr if x < 0] + pos = [x for x in arr if x >= 0] + + # Sort absolute values of negative numbers + neg = [-x for x in neg] # Get absolute value + neg = radix_sort(neg) # Sort + neg = [-x for x in neg] # Reverse + neg.reverse() # Reverse + + # Sort positive numbers + pos = radix_sort(pos) if pos else [] + + # Combine: negative (reversed) + positive + return neg + pos + + # Find the number of digits + digits = 0 + while max_num > 0: + digits += 1 + max_num //= 10 + + # Counting sort for each digit + result = arr.copy() + exp = 1 + + for _ in range(digits): + # Counting sort for the digit at position 'exp' + output = [0] * len(result) + count = [0] * 10 + + for i in range(len(result)): + index = (result[i] // exp) % 10 + count[index] += 1 + + for i in range(1, 10): + count[i] += count[i - 1] + + for i in range(len(result) - 1, -1, -1): + index = (result[i] // exp) % 10 + output[count[index] - 1] = result[i] + count[index] -= 1 + + result = output + exp *= 10 + + return result + + +def bucket_sort(arr, num_buckets=10): + """ + Bucket Sort + Time complexity: O(n + k) with k being the number of buckets + """ + if not arr: + return [] + + # Find min and max + min_val = min(arr) + max_val = max(arr) + + # Create buckets + bucket_range = (max_val - min_val) / num_buckets + buckets = [[] for _ in range(num_buckets)] + + # Place elements into corresponding buckets + for i in arr: + index = int((i - min_val) / bucket_range) + # Special case for max value + if index == num_buckets: + index -= 1 + buckets[index].append(i) + + # Sort each bucket and merge + result = [] + for bucket in buckets: + insertion_sort(bucket) # Use insertion sort for each bucket + result.extend(bucket) + + return result + + +def shell_sort(arr): + """ + Shell Sort + Time complexity: depends on the gap sequence, usually O(n log^2 n) + """ + n = len(arr) + arr_copy = arr.copy() + + # Start with large gap, then decrease + gap = n // 2 + + while gap > 0: + for i in range(gap, n): + # Add arr[i] to the sorted elements with gap + # Save arr[i] in temp and create a gap at position i + temp = arr_copy[i] + + # Shift the sorted elements with gap until the correct position for temp is found + j = i + while j >= gap and arr_copy[j - gap] > temp: + arr_copy[j] = arr_copy[j - gap] + j -= gap + + # Place temp (original arr[i]) in the correct position + arr_copy[j] = temp + + # Decrease gap for the next iteration + gap //= 2 + + return arr_copy + + +# Test with an array +if __name__ == "__main__": + # Test array + test_array = [64, 34, 25, 12, 22, 11, 90] + + print("Original array:", test_array) + print("Bubble Sort:", bubble_sort(test_array)) + print("Selection Sort:", selection_sort(test_array)) + print("Insertion Sort:", insertion_sort(test_array)) + print("Merge Sort:", merge_sort(test_array.copy())) # Need to copy because merge sort modifies the original array + print("Quick Sort:", quick_sort(test_array)) + print("Heap Sort:", heap_sort(test_array)) + print("Counting Sort:", counting_sort(test_array)) + print("Radix Sort:", radix_sort(test_array)) + print("Bucket Sort:", bucket_sort(test_array)) + print("Shell Sort:", shell_sort(test_array)) \ No newline at end of file diff --git a/snippets/algorithms/sorting-algorithms/sorting_algorithms.rb b/snippets/algorithms/sorting-algorithms/sorting_algorithms.rb new file mode 100644 index 0000000..260983f --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.rb @@ -0,0 +1,358 @@ +# Bubble Sort +# Time complexity: O(n^2) +def bubble_sort(arr) + result = arr.clone + n = result.length + + for i in 0...n + swapped = false + + for j in 0...(n-i-1) + if result[j] > result[j+1] + result[j], result[j+1] = result[j+1], result[j] + swapped = true + end + end + + # If no swapping occurred in this pass, the array is already sorted + break unless swapped + end + + result +end + +# Selection Sort +# Time complexity: O(n^2) +def selection_sort(arr) + result = arr.clone + n = result.length + + for i in 0...n + min_idx = i + + for j in (i+1)...n + min_idx = j if result[j] < result[min_idx] + end + + # Swap the found minimum element with the first element of the unsorted part + result[i], result[min_idx] = result[min_idx], result[i] + end + + result +end + +# Insertion Sort +# Time complexity: O(n^2) +def insertion_sort(arr) + result = arr.clone + n = result.length + + for i in 1...n + key = result[i] + j = i - 1 + + # Move elements greater than key one position ahead + while j >= 0 && result[j] > key + result[j+1] = result[j] + j -= 1 + end + + result[j+1] = key + end + + result +end + +# Merge Sort +# Time complexity: O(n log n) +def merge_sort(arr) + return arr.clone if arr.length <= 1 + + mid = arr.length / 2 + left = merge_sort(arr[0...mid]) + right = merge_sort(arr[mid...arr.length]) + + merge(left, right) +end + +def merge(left, right) + result = [] + i = j = 0 + + while i < left.length && j < right.length + if left[i] <= right[j] + result << left[i] + i += 1 + else + result << right[j] + j += 1 + end + end + + # Add remaining elements + result.concat(left[i...left.length]) + result.concat(right[j...right.length]) + + result +end + +# Quick Sort +# Time complexity: O(n log n) average, O(n^2) worst case +def quick_sort(arr) + return arr.clone if arr.length <= 1 + + # Use closure to avoid modifying the original array + quick_sort_helper = lambda do |array, low, high| + if low < high + # pi is the partition index + pi = partition(array, low, high) + + # Recursively sort elements before and after partition + quick_sort_helper.call(array, low, pi - 1) + quick_sort_helper.call(array, pi + 1, high) + end + end + + result = arr.clone + quick_sort_helper.call(result, 0, result.length - 1) + result +end + +def partition(arr, low, high) + pivot = arr[high] # Choose the last element as pivot + i = low - 1 # Index of smaller element + + for j in low...high + # If current element is smaller than or equal to pivot + if arr[j] <= pivot + i += 1 + arr[i], arr[j] = arr[j], arr[i] + end + end + + # Swap the pivot element with the element at (i+1) + arr[i+1], arr[high] = arr[high], arr[i+1] + i + 1 +end + +# Heap Sort +# Time complexity: O(n log n) +def heap_sort(arr) + result = arr.clone + n = result.length + + # Build max heap + (n/2 - 1).downto(0) do |i| + heapify(result, n, i) + end + + # Extract elements from heap one by one + (n-1).downto(1) do |i| + # Move current root to end + result[0], result[i] = result[i], result[0] + + # Call heapify on the reduced heap + heapify(result, i, 0) + end + + result +end + +def heapify(arr, n, i) + largest = i # Initialize largest as root + left = 2 * i + 1 # left = 2*i + 1 + right = 2 * i + 2 # right = 2*i + 2 + + # If left child is larger than root + largest = left if left < n && arr[left] > arr[largest] + + # If right child is larger than largest so far + largest = right if right < n && arr[right] > arr[largest] + + # If largest is not root + if largest != i + arr[i], arr[largest] = arr[largest], arr[i] + + # Recursively heapify the affected sub-tree + heapify(arr, n, largest) + end +end + +# Counting Sort +# Time complexity: O(n + k) where k is the range of input elements +def counting_sort(arr) + return [] if arr.empty? + + # Find the maximum and minimum element in the array + max_val = arr.max + min_val = arr.min + range_of_elements = max_val - min_val + 1 + + # Create a count array and result array + count = Array.new(range_of_elements, 0) + output = Array.new(arr.length, 0) + + # Count occurrences of each element + arr.each { |val| count[val - min_val] += 1 } + + # Update count[i] to contain the actual position of this value in output + for i in 1...range_of_elements + count[i] += count[i-1] + end + + # Build output array + (arr.length-1).downto(0) do |i| + output[count[arr[i] - min_val] - 1] = arr[i] + count[arr[i] - min_val] -= 1 + end + + output +end + +# Radix Sort +# Time complexity: O(d * (n + b)) with d being the number of digits and b being the base +def radix_sort(arr) + return [] if arr.empty? + + # Handle negative numbers + has_negative = arr.any? { |val| val < 0 } + if has_negative + # Separate into negative and positive arrays + neg = arr.select { |val| val < 0 }.map(&:-@) # Get absolute values + pos = arr.select { |val| val >= 0 } + + # Sort absolute values of negative numbers + neg = radix_sort(neg) + neg = neg.map(&:-@).reverse # Negate and reverse + + # Sort positive numbers + pos = pos.empty? ? [] : radix_sort(pos) + + # Combine: negative (reversed) + positive + return neg + pos + end + + # Find maximum number to know number of digits + max_num = arr.max + + # Find the number of digits + digits = 0 + temp = max_num + while temp > 0 + digits += 1 + temp /= 10 + end + + # Perform counting sort for each digit + result = arr.clone + exp = 1 + + digits.times do + # Counting sort for the digit at position 'exp' + output = Array.new(result.length, 0) + count = Array.new(10, 0) + + # Store count of occurrences in count[] + result.each do |val| + digit = (val / exp) % 10 + count[digit] += 1 + end + + # Change count[i] so that count[i] now contains actual position of this digit in output[] + for i in 1...10 + count[i] += count[i-1] + end + + # Build the output array + (result.length-1).downto(0) do |i| + digit = (result[i] / exp) % 10 + output[count[digit] - 1] = result[i] + count[digit] -= 1 + end + + result = output + exp *= 10 + end + + result +end + +# Bucket Sort +# Time complexity: O(n + k) with k being the number of buckets +def bucket_sort(arr, num_buckets = 10) + return [] if arr.empty? + + # Find min and max values + min_val = arr.min + max_val = arr.max + + # Create buckets + bucket_range = (max_val - min_val).to_f / num_buckets + buckets = Array.new(num_buckets) { [] } + + # Place elements into corresponding buckets + arr.each do |val| + index = ((val - min_val) / bucket_range).to_i + # Handle case for max value + index = num_buckets - 1 if index == num_buckets + buckets[index] << val + end + + # Sort each bucket and merge + result = [] + buckets.each do |bucket| + result.concat(insertion_sort(bucket)) unless bucket.empty? + end + + result +end + +# Shell Sort +# Time complexity: depends on the gap sequence, usually O(n log^2 n) +def shell_sort(arr) + result = arr.clone + n = result.length + + # Start with a big gap, then reduce the gap + gap = n / 2 + + while gap > 0 + for i in gap...n + # Save result[i] in temp and make a hole at position i + temp = result[i] + + # Shift earlier gap-sorted elements up until the correct location for result[i] is found + j = i + while j >= gap && result[j - gap] > temp + result[j] = result[j - gap] + j -= gap + end + + # Put temp (the original result[i]) in its correct location + result[j] = temp + end + + # Reduce the gap + gap /= 2 + end + + result +end + +# Test with an array +if __FILE__ == $0 + # Test array + test_array = [64, 34, 25, 12, 22, 11, 90] + + puts "Original array: #{test_array}" + puts "Bubble Sort: #{bubble_sort(test_array)}" + puts "Selection Sort: #{selection_sort(test_array)}" + puts "Insertion Sort: #{insertion_sort(test_array)}" + puts "Merge Sort: #{merge_sort(test_array)}" + puts "Quick Sort: #{quick_sort(test_array)}" + puts "Heap Sort: #{heap_sort(test_array)}" + puts "Counting Sort: #{counting_sort(test_array)}" + puts "Radix Sort: #{radix_sort(test_array)}" + puts "Bucket Sort: #{bucket_sort(test_array)}" + puts "Shell Sort: #{shell_sort(test_array)}" +end diff --git a/snippets/algorithms/sorting-algorithms/sorting_algorithms.rs b/snippets/algorithms/sorting-algorithms/sorting_algorithms.rs new file mode 100644 index 0000000..9927e1b --- /dev/null +++ b/snippets/algorithms/sorting-algorithms/sorting_algorithms.rs @@ -0,0 +1,393 @@ +/// Bubble Sort +/// Time complexity: O(n^2) +fn bubble_sort(arr: &[i32]) -> Vec { + let mut result = arr.to_vec(); + let n = result.len(); + + for i in 0..n { + let mut swapped = false; + + for j in 0..(n - i - 1) { + if result[j] > result[j + 1] { + result.swap(j, j + 1); + swapped = true; + } + } + + // If no swapping occurred in this pass, the array is already sorted + if !swapped { + break; + } + } + + result +} + +/// Selection Sort +/// Time complexity: O(n^2) +fn selection_sort(arr: &[i32]) -> Vec { + let mut result = arr.to_vec(); + let n = result.len(); + + for i in 0..n { + let mut min_idx = i; + + for j in (i + 1)..n { + if result[j] < result[min_idx] { + min_idx = j; + } + } + + // Swap the found minimum element with the first element of the unsorted part + result.swap(i, min_idx); + } + + result +} + +/// Insertion Sort +/// Time complexity: O(n^2) +fn insertion_sort(arr: &[i32]) -> Vec { + let mut result = arr.to_vec(); + let n = result.len(); + + for i in 1..n { + let key = result[i]; + let mut j = i as i32 - 1; + + // Move elements greater than key one position ahead + while j >= 0 && result[j as usize] > key { + result[(j + 1) as usize] = result[j as usize]; + j -= 1; + } + result[(j + 1) as usize] = key; + } + + result +} + +/// Merge Sort +/// Time complexity: O(n log n) +fn merge_sort(arr: &[i32]) -> Vec { + if arr.len() <= 1 { + return arr.to_vec(); + } + + let mid = arr.len() / 2; + let left = merge_sort(&arr[0..mid]); + let right = merge_sort(&arr[mid..]); + + merge(&left, &right) +} + +fn merge(left: &[i32], right: &[i32]) -> Vec { + let mut result = Vec::with_capacity(left.len() + right.len()); + let (mut i, mut j) = (0, 0); + + while i < left.len() && j < right.len() { + if left[i] <= right[j] { + result.push(left[i]); + i += 1; + } else { + result.push(right[j]); + j += 1; + } + } + + // Add remaining elements + result.extend_from_slice(&left[i..]); + result.extend_from_slice(&right[j..]); + + result +} + +/// Quick Sort +/// Time complexity: O(n log n) average, O(n^2) worst case +fn quick_sort(arr: &[i32]) -> Vec { + if arr.len() <= 1 { + return arr.to_vec(); + } + + let mut result = arr.to_vec(); + quick_sort_helper(&mut result, 0, (result.len() - 1) as i32); + result +} + +fn quick_sort_helper(arr: &mut [i32], low: i32, high: i32) { + if low < high { + let pi = partition(arr, low, high); + + // Recursively sort elements before and after partition + quick_sort_helper(arr, low, pi - 1); + quick_sort_helper(arr, pi + 1, high); + } +} + +fn partition(arr: &mut [i32], low: i32, high: i32) -> i32 { + let pivot = arr[high as usize]; + let mut i = low - 1; + + for j in low..high { + if arr[j as usize] <= pivot { + i += 1; + arr.swap(i as usize, j as usize); + } + } + + arr.swap((i + 1) as usize, high as usize); + i + 1 +} + +/// Heap Sort +/// Time complexity: O(n log n) +fn heap_sort(arr: &[i32]) -> Vec { + let mut result = arr.to_vec(); + let n = result.len(); + + // Build max heap + for i in (0..(n / 2)).rev() { + heapify(&mut result, n, i); + } + + // Extract elements from heap one by one + for i in (1..n).rev() { + // Move current root to end + result.swap(0, i); + + // Call heapify on the reduced heap + heapify(&mut result, i, 0); + } + + result +} + +fn heapify(arr: &mut [i32], n: usize, i: usize) { + let mut largest = i; // Initialize largest as root + let left = 2 * i + 1; // left = 2*i + 1 + let right = 2 * i + 2; // right = 2*i + 2 + + // If left child is larger than root + if left < n && arr[left] > arr[largest] { + largest = left; + } + + // If right child is larger than largest so far + if right < n && arr[right] > arr[largest] { + largest = right; + } + + // If largest is not root + if largest != i { + arr.swap(i, largest); + + // Recursively heapify the affected sub-tree + heapify(arr, n, largest); + } +} + +/// Counting Sort +/// Time complexity: O(n + k) where k is the range of input elements +fn counting_sort(arr: &[i32]) -> Vec { + if arr.is_empty() { + return Vec::new(); + } + + // Find the maximum and minimum element in the array + let max_val = *arr.iter().max().unwrap(); + let min_val = *arr.iter().min().unwrap(); + let range = (max_val - min_val + 1) as usize; + + // Create a count array and result array + let mut count = vec![0; range]; + let mut output = vec![0; arr.len()]; + + // Store count of each element + for &val in arr { + count[(val - min_val) as usize] += 1; + } + + // Change count[i] so that count[i] now contains the actual + // position of this element in output array + for i in 1..range { + count[i] += count[i - 1]; + } + + // Build the output array + for i in (0..arr.len()).rev() { + let val = arr[i]; + output[count[(val - min_val) as usize] - 1] = val; + count[(val - min_val) as usize] -= 1; + } + + output +} + +/// Radix Sort +/// Time complexity: O(d * (n + b)) with d being the number of digits and b being the base +fn radix_sort(arr: &[i32]) -> Vec { + if arr.is_empty() { + return Vec::new(); + } + + // Handle negative numbers + let has_negative = arr.iter().any(|&val| val < 0); + if has_negative { + // Separate into negative and positive arrays + let mut neg: Vec = arr.iter() + .filter(|&&val| val < 0) + .map(|&val| -val) + .collect(); + let mut pos: Vec = arr.iter() + .filter(|&&val| val >= 0) + .copied() + .collect(); + + // Sort absolute values of negative numbers + if !neg.is_empty() { + neg = radix_sort(&neg); + // Reverse and negate + neg.reverse(); + for val in &mut neg { + *val = -*val; + } + } + + // Sort positive numbers + if !pos.is_empty() { + pos = radix_sort(&pos); + } + + // Combine: negative (reversed) + positive + neg.extend(pos); + return neg; + } + + // Find maximum number to know number of digits + let max_num = *arr.iter().max().unwrap(); + let mut result = arr.to_vec(); + let mut exp = 1; + + // Do counting sort for every digit + while max_num / exp > 0 { + counting_sort_by_digit(&mut result, exp); + exp *= 10; + } + + result +} + +fn counting_sort_by_digit(arr: &mut [i32], exp: i32) { + let n = arr.len(); + let mut output = vec![0; n]; + let mut count = vec![0; 10]; + + // Store count of occurrences in count[] + for &val in arr.iter() { + let digit = ((val / exp) % 10) as usize; + count[digit] += 1; + } + + // Change count[i] so that count[i] now contains actual + // position of this digit in output[] + for i in 1..10 { + count[i] += count[i - 1]; + } + + // Build the output array + for i in (0..n).rev() { + let digit = ((arr[i] / exp) % 10) as usize; + output[count[digit] - 1] = arr[i]; + count[digit] -= 1; + } + + // Copy the output array to arr[] + for i in 0..n { + arr[i] = output[i]; + } +} + +/// Bucket Sort +/// Time complexity: O(n + k) where k is the number of buckets +fn bucket_sort(arr: &[i32], num_buckets: usize) -> Vec { + if arr.is_empty() { + return Vec::new(); + } + + // Find min and max values + let min_val = *arr.iter().min().unwrap(); + let max_val = *arr.iter().max().unwrap(); + + // Create buckets + let bucket_range = (max_val - min_val + 1) as f64 / num_buckets as f64; + let mut buckets: Vec> = vec![Vec::new(); num_buckets]; + + // Place elements into corresponding buckets + for &val in arr { + let bucket_idx = ((val - min_val) as f64 / bucket_range) as usize; + // Handle case for max value + let idx = if bucket_idx == num_buckets { num_buckets - 1 } else { bucket_idx }; + buckets[idx].push(val); + } + + // Sort individual buckets and collect them + let mut result = Vec::new(); + for mut bucket in buckets { + if !bucket.is_empty() { + // Sort each bucket using insertion sort + bucket = insertion_sort(&bucket); + result.extend(bucket); + } + } + + result +} + +/// Shell Sort +/// Time complexity: depends on the gap sequence, usually O(n log^2 n) +fn shell_sort(arr: &[i32]) -> Vec { + let mut result = arr.to_vec(); + let n = result.len(); + + // Start with a big gap, then reduce the gap + let mut gap = n / 2; + + while gap > 0 { + for i in gap..n { + // Save result[i] in temp and make a hole at position i + let temp = result[i]; + + // Shift earlier gap-sorted elements up until the correct + // location for result[i] is found + let mut j = i; + while j >= gap && result[j - gap] > temp { + result[j] = result[j - gap]; + j -= gap; + } + + // Put temp (the original result[i]) in its correct location + result[j] = temp; + } + + // Reduce the gap + gap /= 2; + } + + result +} + +fn main() { + // Test array + let test_array = vec![64, 34, 25, 12, 22, 11, 90]; + + println!("Original array: {:?}", test_array); + println!("Bubble Sort: {:?}", bubble_sort(&test_array)); + println!("Selection Sort: {:?}", selection_sort(&test_array)); + println!("Insertion Sort: {:?}", insertion_sort(&test_array)); + println!("Merge Sort: {:?}", merge_sort(&test_array)); + println!("Quick Sort: {:?}", quick_sort(&test_array)); + println!("Heap Sort: {:?}", heap_sort(&test_array)); + println!("Counting Sort: {:?}", counting_sort(&test_array)); + println!("Radix Sort: {:?}", radix_sort(&test_array)); + println!("Bucket Sort: {:?}", bucket_sort(&test_array, 5)); // Using 5 buckets + println!("Shell Sort: {:?}", shell_sort(&test_array)); +} 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/.env.example b/snippets/databases/relational/.env.example new file mode 100644 index 0000000..ef9053e --- /dev/null +++ b/snippets/databases/relational/.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: diff --git a/snippets/databases/relational/relational.cs b/snippets/databases/relational/relational.cs new file mode 100644 index 0000000..062a3e6 --- /dev/null +++ b/snippets/databases/relational/relational.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/relational/relational.py b/snippets/databases/relational/relational.py new file mode 100644 index 0000000..a6f3ac0 --- /dev/null +++ b/snippets/databases/relational/relational.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/ci-cd.sh b/snippets/devops/ci-cd/ci-cd.sh new file mode 100644 index 0000000..af80d42 --- /dev/null +++ b/snippets/devops/ci-cd/ci-cd.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/devops/init.txt b/snippets/devops/init.txt new file mode 100644 index 0000000..e69de29 diff --git a/snippets/linux/bash-scripting/bash-scripting.sh b/snippets/linux/bash-scripting/bash-scripting.sh new file mode 100644 index 0000000..ca584e9 --- /dev/null +++ b/snippets/linux/bash-scripting/bash-scripting.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/linux/init.txt b/snippets/linux/init.txt new file mode 100644 index 0000000..e69de29 diff --git a/snippets/system-design/init.txt b/snippets/system-design/init.txt new file mode 100644 index 0000000..e69de29 diff --git a/snippets/system-design/microservices/microservices.js b/snippets/system-design/microservices/microservices.js new file mode 100644 index 0000000..92e0d24 --- /dev/null +++ b/snippets/system-design/microservices/microservices.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/init.txt b/snippets/testing/init.txt new file mode 100644 index 0000000..e69de29 diff --git a/snippets/testing/unit-testing/unit-testing.js b/snippets/testing/unit-testing/unit-testing.js new file mode 100644 index 0000000..fe33bcc --- /dev/null +++ b/snippets/testing/unit-testing/unit-testing.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/README.md b/tools/README.md new file mode 100644 index 0000000..a093bbe --- /dev/null +++ b/tools/README.md @@ -0,0 +1,107 @@ +# Tools for Tech Notes Hub + +This directory contains utility scripts for maintaining the Tech Notes Hub project. + +## Scripts + +### `update-frontmatter.js` + +A Node.js script that automatically updates the frontmatter (metadata) in all Markdown files in the `/docs` and `/i18n/vi` directories. + +#### Features + +- Ensures all Markdown files have consistent frontmatter +- Updates the `update` field with the current date **only if the field doesn't exists** +- Sets default values for missing fields: + - `author`: "Tech Notes Hub" + - `date`: Current date (if missing) + - `tags`: Default tags (if missing) + - `title`: Generated from filename (if missing) + - `description`: Generated from title (if missing) +- Logs include timestamps for better tracking + +#### Requirements + +- Node.js +- Dependencies: + - `gray-matter` + - `glob` + +#### Usage + +**Update all files:** + +```bash +# Install dependencies if not already installed +npm install + +# Make the script executable +chmod +x tools/update-frontmatter.js + +# Run the script to update all files +node tools/update-frontmatter.js +# Or explicitly specify all files +node tools/update-frontmatter.js --all +``` + +**Update a single file manually:** + +```bash +# Update a specific file by providing the relative path from either docs/ or i18n/vi/ +node tools/update-frontmatter.js algorithms/sorting-algorithms.md + +# The script will check for the file in both directories: +# - /docs/algorithms/sorting-algorithms.md +# - /i18n/vi/algorithms/sorting-algorithms.md +``` + +### `generate_summary.py` + +A Python script that automatically generates the `SUMMARY.md` file by scanning the `docs/` directory structure. + +#### Features + +- Creates a well-structured table of contents +- Extracts titles from Markdown files when available +- Organizes content by categories based on directory structure +- Generates clean, formatted links to all documentation files + +#### Requirements + +- Python 3.6+ +- No external dependencies (uses only standard library) + +#### Usage + +```bash +# Make the script executable (first time only) +chmod +x tools/generate_summary.py + +# Run the script to generate SUMMARY.md +python tools/generate_summary.py +``` + +The script will: +1. Scan all directories in `/docs` +2. Extract titles from Markdown files or generate them from filenames +3. Create a hierarchical table of contents +4. Write the result to `SUMMARY.md` in the project root + +#### Automation + +This script is configured to run automatically via GitHub Actions when: + +1. Any Markdown file is changed in the `/docs` or `/i18n` directories +2. Weekly on Monday at 00:00 UTC +3. Manually via the GitHub Actions interface + +The automation will commit any changes back to the repository with a commit message "chore: update markdown frontmatter [skip ci]". + +## Adding New Tools + +When adding new tools to this directory, please follow these guidelines: + +1. Create a well-documented script with clear comments +2. Update this README with information about the tool +3. Ensure the tool follows project coding standards +4. Add any necessary dependencies to the project's package.json 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 diff --git a/tools/update-frontmatter.js b/tools/update-frontmatter.js new file mode 100755 index 0000000..ef691a1 --- /dev/null +++ b/tools/update-frontmatter.js @@ -0,0 +1,164 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const matter = require('gray-matter'); +const glob = require('glob'); + +// Default configuration for frontmatter +const DEFAULT_AUTHOR = 'Tech Notes Hub'; +const DEFAULT_TAGS = 'learning, technology, programming'; +const CURRENT_DATE = new Date().toISOString().split('T')[0]; // Format: YYYY-MM-DD + +// Path to the directories to update +const DOCS_DIR = path.join(__dirname, '..', 'docs'); +const I18N_VI_DIR = path.join(__dirname, '..', 'i18n', 'vi'); + +// Logger function with timestamp +const log = (message, type = 'info') => { + const timestamp = new Date().toISOString().replace('T', ' ').substr(0, 19); + const prefix = { + info: '📝', + success: '✅', + error: '❌', + warning: '⚠️' + }; + console.log(`${timestamp} ${prefix[type] || ''} ${message}`); +}; + +// Get all markdown files +const getMarkdownFiles = (directory) => { + return glob.sync('**/*.md', { + cwd: directory, + ignore: ['**/README.md', '**/SUMMARY.md', '**/LICENSE.md'] + }).map(file => path.join(directory, file)); +}; + +// Update frontmatter for a file +const updateFrontmatter = (filePath) => { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const { data, content: markdownContent } = matter(content); + + // Update or add frontmatter fields + const updatedData = { + ...data, + author: data.author || DEFAULT_AUTHOR, + tags: data.tags || DEFAULT_TAGS, + }; + + // Only update the 'update' field if it doesn't exist + if (!data.update) { + updatedData.update = CURRENT_DATE; + } + + // If 'date' field is missing, add it + if (!data.date) { + updatedData.date = CURRENT_DATE; + } + + // If 'title' field is missing, create title from file name + if (!data.title) { + const fileName = path.basename(filePath, '.md'); + updatedData.title = fileName + .split('-') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + } + + // If 'description' field is missing, add default description + if (!data.description) { + updatedData.description = `Guide about ${updatedData.title}`; + } + + // Create new content with updated frontmatter + const updatedContent = matter.stringify(markdownContent, updatedData); + + // Write back to file + fs.writeFileSync(filePath, updatedContent); + + log(`Updated: ${filePath}`, 'success'); + return true; + } catch (error) { + log(`Error updating ${filePath}: ${error.message}`, 'error'); + return false; + } +}; + +// Check if file exists +const fileExists = (filePath) => { + try { + return fs.existsSync(filePath); + } catch (error) { + return false; + } +}; + +// Main function to update all markdown files +const updateAllFiles = () => { + // Get all markdown files + const docsFiles = getMarkdownFiles(DOCS_DIR); + const i18nViFiles = getMarkdownFiles(I18N_VI_DIR); + const allFiles = [...docsFiles, ...i18nViFiles]; + + log(`Found ${allFiles.length} markdown files to process...`); + + // Update each file + let successCount = 0; + for (const file of allFiles) { + if (updateFrontmatter(file)) { + successCount++; + } + } + + log(`\nCompleted: ${successCount}/${allFiles.length} files updated successfully.`, 'info'); +}; + +// Function to update a single file manually +const updateSingleFile = (relativePath) => { + // Try to find the file in both directories + const docPath = path.join(DOCS_DIR, relativePath); + const i18nPath = path.join(I18N_VI_DIR, relativePath); + + if (fileExists(docPath)) { + log(`Updating file in docs directory: ${docPath}`); + updateFrontmatter(docPath); + return true; + } else if (fileExists(i18nPath)) { + log(`Updating file in i18n/vi directory: ${i18nPath}`); + updateFrontmatter(i18nPath); + return true; + } else { + log(`File not found: ${relativePath}`, 'error'); + log(`Looked in: \n ${docPath}\n ${i18nPath}`, 'warning'); + return false; + } +}; + +// Process command line arguments +const processArgs = () => { + const args = process.argv.slice(2); + + if (args.length === 0) { + // No arguments, update all files + log('No specific file provided, updating all files...'); + updateAllFiles(); + return; + } + + // First argument is the file path + const filePath = args[0]; + log(`Manual update requested for: ${filePath}`); + + if (filePath === '--all') { + // Update all files + updateAllFiles(); + } else { + // Update single file + updateSingleFile(filePath); + } +}; + +// Run script +log('Starting frontmatter update process...'); +processArgs(); 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..80fd24f --- /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://tech-notes-hub.vercel.app', + openGraph: { + type: 'website', + locale: 'vi_VN', + url: 'https://tech-notes-hub.vercel.app', + siteName: 'Tech Notes Hub', + title: 'Tech Notes Hub', + description: 'A collection of tech notes, code snippets, and technical guides for developers', + images: [ + { + url: 'https://tech-notes-hub.vercel.app/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..cb3d3aa --- /dev/null +++ b/website/next.config.js @@ -0,0 +1,13 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + eslint: { + // Disable ESLint checks during build + ignoreDuringBuilds: true, + }, + typescript: { + // Ignore TypeScript errors during build + ignoreBuildErrors: true, + }, +} + +module.exports = nextConfig 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..db4d3b4 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,9579 @@ +{ + "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", + "prismjs": "^1.30.0", + "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..ce4e672 --- /dev/null +++ b/website/package.json @@ -0,0 +1,59 @@ +{ + "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", + "prismjs": "^1.30.0", + "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..43f1c79 --- /dev/null +++ b/website/pages/sitemap.xml.js @@ -0,0 +1,149 @@ +import { getContentAsBlogPosts } from '@/lib/content-mapper'; + +const WEBSITE_URL = 'https://tech-notes-hub.vercel.app'; +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 0000000..1d053cd Binary files /dev/null and b/website/public/og-image.jpg differ diff --git a/website/public/robots.txt b/website/public/robots.txt new file mode 100644 index 0000000..848e394 --- /dev/null +++ b/website/public/robots.txt @@ -0,0 +1,25 @@ +# Allow all web crawlers +User-agent: * +Allow: / + +# Sitemap location +Sitemap: https://tech-notes-hub.vercel.app/sitemap.xml + +# Disallow admin paths +User-agent: * +Disallow: /admin/ +Disallow: /api/ + +# Special instructions for specific bots +User-agent: Googlebot +Allow: / + +User-agent: Bingbot +Allow: / + +User-agent: Baiduspider +Disallow: / + +# Crawl-delay suggestion +User-agent: * +Crawl-delay: 10 diff --git a/website/public/sitemap.txt b/website/public/sitemap.txt new file mode 100644 index 0000000..eb4724a --- /dev/null +++ b/website/public/sitemap.txt @@ -0,0 +1,10 @@ +https://tech-notes-hub.vercel.app/en +https://tech-notes-hub.vercel.app/vi +https://tech-notes-hub.vercel.app/en/about +https://tech-notes-hub.vercel.app/vi/about +https://tech-notes-hub.vercel.app/en/blog +https://tech-notes-hub.vercel.app/vi/blog +https://tech-notes-hub.vercel.app/en/privacy +https://tech-notes-hub.vercel.app/vi/privacy +https://tech-notes-hub.vercel.app/en/terms +https://tech-notes-hub.vercel.app/vi/terms diff --git a/website/public/vercel.svg b/website/public/vercel.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/website/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/window.svg b/website/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/website/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/app/[locale]/about/page.tsx b/website/src/app/[locale]/about/page.tsx new file mode 100644 index 0000000..4ce9171 --- /dev/null +++ b/website/src/app/[locale]/about/page.tsx @@ -0,0 +1,64 @@ +import { Metadata } from "next"; +import { Locale, getTranslationsFromNamespaces } from "@/lib/i18n/settings"; + +type Params = Promise<{ locale: Locale }>; + +export async function generateMetadata({ params }: { params: Params }): Promise { + const { locale } = await params; + const translations = await getTranslationsFromNamespaces(locale, ['common', 'about']); + const about = translations.about; + + return { + title: `${about.title} - ${translations.common.site.title}`, + description: translations.common.site.description, + }; +} + +export default async function AboutPage({ params }: { params: Params }) { + const { locale } = await params; + const translations = await getTranslationsFromNamespaces(locale, ['common', 'about']); + const t = translations.common; + const about = translations.about; + + return ( +
+
+

+ {about.title} +

+ +
+

+ {about.intro} +

+ +

{about.mission.title}

+

{about.mission.content}

+ +

{about.content.title}

+

{about.content.intro}

+
    + {Object.entries(t.categories).map(([key, value]) => ( +
  • {value}
  • + ))} +
+ +

{about.community.title}

+

{about.community.content}

+ +

{about.contact.title}

+

+ {about.contact.content}{' '} + + {about.contact.email} + + . +

+
+
+
+ ); +} diff --git a/website/src/app/[locale]/blog/[slug]/page.tsx b/website/src/app/[locale]/blog/[slug]/page.tsx new file mode 100644 index 0000000..24b9fd7 --- /dev/null +++ b/website/src/app/[locale]/blog/[slug]/page.tsx @@ -0,0 +1,241 @@ +import { Locale, getTranslationsFromNamespaces } from "@/lib/i18n/settings"; +import { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; +import MarkdownContent from "@/components/blog/markdown-content"; +import { getContentAsBlogPosts, findAvailableTranslations } from "@/lib/content-mapper"; +import { Code, CalendarClock } from "lucide-react"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; +import 'highlight.js/styles/github-dark.css'; +import CodeSnippet from "@/components/blog/code-snippet"; +import { generateDefaultMetadata } from '@/lib/metadata'; +import LanguageSwitcher from "@/components/blog/language-switcher"; + +type Params = Promise<{ + locale: Locale; + slug: string; +}>; + +export async function generateMetadata({ params }: { params: Params }): Promise { + const { locale, slug } = await params; + const allPosts = await getContentAsBlogPosts(locale); + const post = allPosts.find(post => post.slug === slug); + + if (!post) { + return generateDefaultMetadata({ + title: 'Post Not Found', + description: 'The blog post you are looking for does not exist.', + locale, + url: `/blog/${slug}`, + noIndex: true, + }); + } + + return generateDefaultMetadata({ + title: post.title, + description: post.description || `${post.title} - Tech Notes Hub`, + locale, + url: `/blog/${slug}`, + ogImage: post.coverImage || '/og-image.jpg', + }); +} + +export async function generateStaticParams() { + // Generate params for all locales and posts + const locales: Locale[] = ['vi', 'en']; + const params = []; + + for (const locale of locales) { + const posts = await getContentAsBlogPosts(locale); + const localeParams = posts.map(post => ({ + locale, + slug: post.slug, + })); + params.push(...localeParams); + } + + return params; +} + +export default async function BlogPostPage({ params }: { params: Params }) { + const { locale, slug } = await params; + const allPosts = await getContentAsBlogPosts(locale); + const post = allPosts.find(post => post.slug === slug); + + // Get translations + const translations = await getTranslationsFromNamespaces(locale, ['common']); + const t = translations.common; + + if (!post) { + notFound(); + } + + // Find available translations for this post + const availableTranslations = await findAvailableTranslations(slug); + + // Get translated category name if available + const categoryKey = post.category.toLowerCase().replace(/\s+/g, '-') as keyof typeof t.categories; + const translatedCategory = t.categories[categoryKey] || post.category; + + // Get translated source type if available + const sourceTypeKey = post.sourceType as keyof typeof t.blog.sourceTypes | undefined; + const translatedSourceType = sourceTypeKey && t.blog.sourceTypes[sourceTypeKey] + ? t.blog.sourceTypes[sourceTypeKey] + : post.sourceType; + + // Format dates according to locale + const dateFormatter = new Intl.DateTimeFormat(locale === 'vi' ? 'vi-VN' : 'en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + + const publishDate = post.date ? dateFormatter.format(new Date(post.date)) : ''; + const updateDate = post.update ? dateFormatter.format(new Date(post.update)) : ''; + + // Add debug information for development + const debugInfo = process.env.NODE_ENV === 'development' && ( +
+

Debug Info:

+
Current Slug: {slug}
+
Current Locale: {locale}
+
Source Type: {post.sourceType}
+
Source Path: {post.sourcePath}
+
Relative Path: {post.relativePath}
+
Date: {post.date}
+
Update: {post.update}
+
+ Available Translations: +
+          {JSON.stringify(availableTranslations, null, 2)}
+        
+
+ {post.relatedSnippets && ( +
+ Related Snippets: +
+            {JSON.stringify(post.relatedSnippets.map((s) => s.filename), null, 2)}
+          
+
+ )} +
+ ); + + return ( +
+
+ + + + + {availableTranslations.length > 1 && ( + + )} +
+ +
+

+ {post.title} +

+
+ {`${t.blog.publishedOn} ${publishDate}`} + + {`${t.blog.by} ${post.author}`} + + {translatedCategory} + {post.sourceType && ( + <> + + {translatedSourceType} + + )} +
+ + {post.update && ( +
+ + {`${t.blog.lastUpdated || 'Last updated'}: ${updateDate}`} +
+ )} +
+ +
+ {post.coverImage && ( +
+ {post.title} +
+ )} + + + + {post.relatedSnippets && post.relatedSnippets.length > 0 && ( +
+
+ +

{t.blog.codeSnippets}

+
+ + + {post.relatedSnippets.map((snippet, index) => ( + + +
+ {snippet.filename} + + {snippet.language} + +
+
+ +
+ +
+
+
+ ))} +
+
+ )} + +
+

{t.blog.tags}:

+
+ {post.tags.map(tag => ( + + {tag} + + ))} +
+
+ + {post.sourcePath && ( +
+

{t.blog.source}:

+

+ {t.blog.sourcedFrom}: {post.sourcePath.split('/').slice(-3).join('/')} +

+
+ )} + + {process.env.NODE_ENV === 'development' && debugInfo} +
+
+ ); +} diff --git a/website/src/app/[locale]/blog/page.tsx b/website/src/app/[locale]/blog/page.tsx new file mode 100644 index 0000000..544111f --- /dev/null +++ b/website/src/app/[locale]/blog/page.tsx @@ -0,0 +1,271 @@ +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; +import { Locale, getTranslationsFromNamespaces } from "@/lib/i18n/settings"; +import { Metadata } from "next"; +import { getContentAsBlogPosts, getCategoriesWithCounts, CATEGORY_SLUGS } from "@/lib/content-mapper"; +import BlogCategoryFilter from "@/components/blog/category-filter"; +import { notFound } from "next/navigation"; +import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react"; +import Search from "@/components/search"; + +type Params = Promise<{ locale: Locale }>; + +export async function generateMetadata({ params }: { params: Params }): Promise { + const { locale } = await params; + const translations = await getTranslationsFromNamespaces(locale, ['common']); + + return { + title: `${translations.common.nav.blog} - ${translations.common.site.title}`, + description: translations.common.site.description, + }; +} + +const POSTS_PER_PAGE = 20; + +export default async function BlogPage({ + params, + searchParams +}: { + params: Params, + searchParams?: { category?: string; page?: string } +}) { + const { locale } = await params; + const selectedCategory = searchParams?.category || ''; + const currentPage = searchParams?.page ? parseInt(searchParams.page) : 1; + + // Get translations + const translations = await getTranslationsFromNamespaces(locale, ['common']); + const t = translations.common; + + // Get blog posts with the appropriate locale + const blogPosts = await getContentAsBlogPosts(locale); + + // Debug logging + if (process.env.NODE_ENV === 'development') { + console.log(`Total blog posts for locale ${locale}: ${blogPosts.length}`); + console.log(`Selected category: "${selectedCategory}"`); + + // Log all categories found in posts + const allCategories = new Set(blogPosts.map(post => post.category)); + console.log('Available categories in posts:', Array.from(allCategories)); + + // Log posts with design-patterns category + const designPatternPosts = blogPosts.filter(post => + post.category.toLowerCase() === 'design-patterns' || + post.category.toLowerCase() === 'design patterns' + ); + console.log(`Design pattern posts: ${designPatternPosts.length}`); + designPatternPosts.forEach(post => { + console.log(`- ${post.title} (category: "${post.category}", slug: ${post.slug})`); + }); + } + + // Filter by category if specified + const filteredPosts = selectedCategory + ? blogPosts.filter(post => { + const postCategory = post.category; + const postCategorySlug = CATEGORY_SLUGS[postCategory as keyof typeof CATEGORY_SLUGS] || + post.category.toLowerCase().replace(/\s+/g, '-'); + const searchCategory = selectedCategory.toLowerCase(); + + // Match by category slug, category name, or tags + const matchCategorySlug = postCategorySlug.toLowerCase() === searchCategory; + const matchCategoryName = post.category.toLowerCase() === searchCategory; + const matchCategoryNameWithDash = post.category.toLowerCase().replace(/\s+/g, '-') === searchCategory; + const matchTags = post.tags.some(tag => tag.toLowerCase() === searchCategory); + + if (process.env.NODE_ENV === 'development' && selectedCategory.toLowerCase() === 'design-patterns') { + console.log(`Post "${post.title}" - category: "${post.category}", slug: "${postCategorySlug}"`); + console.log(`Matches: slug=${matchCategorySlug}, name=${matchCategoryName}, name-dash=${matchCategoryNameWithDash}, tags=${matchTags}`); + } + + return matchCategorySlug || matchCategoryName || matchCategoryNameWithDash || matchTags; + }) + : blogPosts; + + // Pagination + const totalPosts = filteredPosts.length; + const totalPages = Math.ceil(totalPosts / POSTS_PER_PAGE); + const paginatedPosts = filteredPosts.slice( + (currentPage - 1) * POSTS_PER_PAGE, + currentPage * POSTS_PER_PAGE + ); + + // Get categories with counts for the sidebar + const categories = await getCategoriesWithCounts(locale); + + if (blogPosts.length === 0) { + return notFound(); + } + + // Map category names to translated versions + const translatedCategories = categories.map(category => ({ + ...category, + name: t.categories[category.id as keyof typeof t.categories] || category.name + })); + + // Generate pagination URL + const getPaginationUrl = (page: number) => { + const baseUrl = `/${locale}/blog`; + const categoryParam = selectedCategory ? `category=${selectedCategory}` : ''; + const pageParam = page > 1 ? `page=${page}` : ''; + + if (categoryParam && pageParam) { + return `${baseUrl}?${categoryParam}&${pageParam}`; + } else if (categoryParam) { + return `${baseUrl}?${categoryParam}`; + } else if (pageParam) { + return `${baseUrl}?${pageParam}`; + } + return baseUrl; + }; + + return ( +
+
+

+ {t.nav.blog} +

+

+ {t.site.description} +

+
+ +
+
+ +
+ + +
+ {paginatedPosts.length === 0 ? ( +
+

{t.blog.noPostsFound}

+

+ {t.blog.tryDifferentCategory} +

+
+ ) : ( + <> +
+ {paginatedPosts.map(post => { + // Get translated category name if available + const categoryKey = post.category.toLowerCase().replace(/\s+/g, '-') as keyof typeof t.categories; + const translatedCategory = t.categories[categoryKey] || post.category; + + // Get translated source type if available + const sourceTypeKey = post.sourceType as keyof typeof t.blog.sourceTypes | undefined; + const translatedSourceType = sourceTypeKey && t.blog.sourceTypes[sourceTypeKey] + ? t.blog.sourceTypes[sourceTypeKey] + : post.sourceType; + + return ( + + + {post.title} + + {`${t.blog.publishedOn} ${new Date(post.date).toLocaleDateString(locale === 'vi' ? 'vi-VN' : 'en-US')}`} | {translatedCategory} + + + +

{post.description}

+
+ {post.tags.map(tag => ( + + {tag} + + ))} +
+ {post.sourceType && ( +
+ + {translatedSourceType} + +
+ )} +
+ + + + + +
+ ); + })} +
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ +
+ )} + + {/* Post count information */} +
+ {t.blog.showing} {(currentPage - 1) * POSTS_PER_PAGE + 1}- + {Math.min(currentPage * POSTS_PER_PAGE, totalPosts)} {t.blog.of} {totalPosts} {t.blog.posts} +
+ + )} +
+
+
+ ); +} diff --git a/website/src/app/[locale]/layout.tsx b/website/src/app/[locale]/layout.tsx new file mode 100644 index 0000000..9cbf69a --- /dev/null +++ b/website/src/app/[locale]/layout.tsx @@ -0,0 +1,99 @@ +import { Metadata } from "next"; +import { defaultLocale, getTranslationsFromNamespaces, Locale, locales } from "@/lib/i18n/settings"; +import Header from "@/components/layout/header"; +import { Footer } from "@/components/layout/footer"; + +export async function generateMetadata({ params }: { params: { locale: string } }): Promise { + const locale = params.locale as Locale || defaultLocale; + const translations = await getTranslationsFromNamespaces(locale, ['common']); + + // Access the description directly from translations object as unknown type + const commonTranslations = translations.common as any; + const description = commonTranslations?.meta?.description || + commonTranslations?.site?.description || + 'A collection of tech notes and snippets'; + + return { + title: { + template: '%s - Tech Notes Hub', + default: 'Tech Notes Hub', + }, + description: description, + icons: { + icon: '/favicon.ico', + apple: '/apple-touch-icon.png', + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, + }, + openGraph: { + title: 'Tech Notes Hub', + description: description, + url: `https://tech-notes-hub.vercel.app/${locale}`, + siteName: 'Tech Notes Hub', + locale: locale, + type: 'website', + images: [ + { + url: 'https://tech-notes-hub.vercel.app/og-image.jpg', + width: 1200, + height: 630, + alt: 'Tech Notes Hub', + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: 'Tech Notes Hub', + description: description, + creator: '@technotes', + }, + alternates: { + canonical: `https://tech-notes-hub.vercel.app/${locale}`, + languages: { + en: 'https://tech-notes-hub.vercel.app/en', + vi: 'https://tech-notes-hub.vercel.app/vi', + }, + }, + }; +} + +export function generateStaticParams() { + return locales.map((locale) => ({ locale })); +} + +interface RootLayoutProps { + children: React.ReactNode; + params: { + locale: string; + }; +} + +export default async function RootLayout({ + children, + params, +}: RootLayoutProps) { + // Make sure params.locale is set to default if it doesn't exist + const locale = (params?.locale as Locale) || defaultLocale; + + // Ensure getTranslationsFromNamespaces is awaited + const translations = await getTranslationsFromNamespaces(locale, ['common']); + + return ( +
+
+
+ {children} +
+
+
+ ); +} diff --git a/website/src/app/[locale]/page.tsx b/website/src/app/[locale]/page.tsx new file mode 100644 index 0000000..b15ed64 --- /dev/null +++ b/website/src/app/[locale]/page.tsx @@ -0,0 +1,128 @@ +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { Locale, getTranslationsFromNamespaces } from "@/lib/i18n/settings"; +import { Metadata } from "next"; +import { getContentAsBlogPosts } from "@/lib/content-mapper"; +import { Github } from "lucide-react"; + +type Params = Promise<{ locale: Locale }>; + +export async function generateMetadata({ params }: { params: Params }): Promise { + const { locale } = await params; + const translations = await getTranslationsFromNamespaces(locale, ['common', 'home']); + + return { + title: translations.common.site.title, + description: translations.common.site.description, + }; +} + +export default async function HomePage({ params }: { params: Params }) { + const { locale } = await params; + + // Get translations for home page + const translations = await getTranslationsFromNamespaces(locale, ['common', 'home']); + const t = translations.home; + const commonT = translations.common; + + // Get featured blog posts + const allPosts = await getContentAsBlogPosts(locale); + const featuredPosts = allPosts.slice(0, 6); // Get first 6 posts + + return ( +
+ {/* Hero Section */} +
+
+
+

+ {t.hero.title} +

+

+ {t.hero.description} +

+
+ + + + + + +
+
+
+
+ + {/* Featured Topics Section */} +
+
+
+

{t.featuredTopics.title}

+

+ {t.featuredTopics.description} +

+
+ +
+ {featuredPosts.map((post) => ( + + + {post.title} + + +

{post.description}

+
+ + + + + +
+ ))} +
+ +
+ + + +
+
+
+
+ ); +} + +const featuredTopics = [ + { + id: "web-development", + title: "Web Development", + description: "Frontend and backend technologies", + content: "Explore modern web development techniques, frameworks, and best practices for building responsive and scalable applications." + }, + { + id: "devops", + title: "DevOps", + description: "Continuous integration and deployment", + content: "Learn about DevOps methodologies, tools, and practices to improve collaboration between development and operations teams." + }, + { + id: "data-science", + title: "Data Science", + description: "Data analysis and machine learning", + content: "Dive into data science concepts, machine learning algorithms, and practical applications for extracting insights from data." + }, +]; diff --git a/website/src/app/[locale]/privacy/page.tsx b/website/src/app/[locale]/privacy/page.tsx new file mode 100644 index 0000000..55bb811 --- /dev/null +++ b/website/src/app/[locale]/privacy/page.tsx @@ -0,0 +1,75 @@ +import { Metadata } from "next"; +import { Locale, getTranslationsFromNamespaces } from "@/lib/i18n/settings"; + +type Params = Promise<{ locale: Locale }>; + +export async function generateMetadata({ + params +}: { + params: Params +}): Promise { + const { locale } = await params; + const translations = await getTranslationsFromNamespaces(locale, ['common']); + const t = translations.common; + + return { + title: t.pages?.privacy?.title || 'Privacy Policy', + description: t.pages?.privacy?.introduction || 'Privacy policy for Tech Notes Hub', + }; +} + +export default async function PrivacyPolicyPage({ + params +}: { + params: Params +}) { + const { locale } = await params; + const translations = await getTranslationsFromNamespaces(locale, ['common']); + const t = translations.common; + + return ( +
+

+ {t.pages?.privacy?.title || 'Privacy Policy'} +

+ +
+ {t.pages?.privacy?.lastUpdated || 'Last Updated'}: {new Date('2025-06-06').toLocaleDateString(locale)} +
+ +
+

+ {t.pages?.privacy?.introduction || 'This Privacy Policy describes how we collect, use, and share your personal information.'} +

+ +

+ {t.pages?.privacy?.informationWeCollect || 'Information We Collect'} +

+

+ {t.pages?.privacy?.informationWeCollectText || 'When you visit our website, we may collect certain information about your device.'} +

+ +

+ {t.pages?.privacy?.howWeUseInformation || 'How We Use Your Information'} +

+

+ {t.pages?.privacy?.howWeUseInformationText || 'We use the information we collect to improve and optimize our website.'} +

+ +

+ {t.pages?.privacy?.sharingYourInformation || 'Sharing Your Information'} +

+

+ {t.pages?.privacy?.sharingYourInformationText || 'We do not share your personal information with third parties.'} +

+ +

+ {t.pages?.privacy?.yourRights || 'Your Rights'} +

+

+ {t.pages?.privacy?.yourRightsText || 'You have the right to access personal information we hold about you.'} +

+
+
+ ); +} diff --git a/website/src/app/[locale]/terms/page.tsx b/website/src/app/[locale]/terms/page.tsx new file mode 100644 index 0000000..3a5c4cc --- /dev/null +++ b/website/src/app/[locale]/terms/page.tsx @@ -0,0 +1,82 @@ +import { Metadata } from "next"; +import { Locale, getTranslationsFromNamespaces } from "@/lib/i18n/settings"; + +type Params = Promise<{ locale: Locale }>; + +export async function generateMetadata({ + params +}: { + params: Params +}): Promise { + const { locale } = await params; + const translations = await getTranslationsFromNamespaces(locale, ['common']); + const t = translations.common; + + return { + title: t.pages?.terms?.title || 'Terms of Service', + description: t.pages?.terms?.introduction || 'Terms of service for Tech Notes Hub', + }; +} + +export default async function TermsOfServicePage({ + params +}: { + params: Params +}) { + const { locale } = await params; + const translations = await getTranslationsFromNamespaces(locale, ['common']); + const t = translations.common; + + return ( +
+

+ {t.pages?.terms?.title || 'Terms of Service'} +

+ +
+ {t.pages?.terms?.lastUpdated || 'Last Updated'}: {new Date('2025-06-06').toLocaleDateString(locale)} +
+ +
+

+ {t.pages?.terms?.introduction || 'These Terms of Service govern your access to and use of our website.'} +

+ +

+ {t.pages?.terms?.acceptance || 'Acceptance of Terms'} +

+

+ {t.pages?.terms?.acceptanceText || 'By accessing or using our website, you agree to be bound by these Terms.'} +

+ +

+ {t.pages?.terms?.intellectualProperty || 'Intellectual Property'} +

+

+ {t.pages?.terms?.intellectualPropertyText || 'The content on our website is owned by us and protected by intellectual property laws.'} +

+ +

+ {t.pages?.terms?.userConduct || 'User Conduct'} +

+

+ {t.pages?.terms?.userConductText || 'You agree not to use our website for any illegal or unauthorized purpose.'} +

+ +

+ {t.pages?.terms?.disclaimer || 'Disclaimer'} +

+

+ {t.pages?.terms?.disclaimerText || 'Our website is provided "as is" without any warranties.'} +

+ +

+ {t.pages?.terms?.limitationOfLiability || 'Limitation of Liability'} +

+

+ {t.pages?.terms?.limitationOfLiabilityText || 'We will not be liable for any damages arising from the use of our website.'} +

+
+
+ ); +} diff --git a/website/src/app/api/robots.txt/route.ts b/website/src/app/api/robots.txt/route.ts new file mode 100644 index 0000000..e419883 --- /dev/null +++ b/website/src/app/api/robots.txt/route.ts @@ -0,0 +1,37 @@ +const WEBSITE_URL = 'https://tech-notes-hub.vercel.app'; + +export async function GET() { + const robotsTxt = ` +# Allow all web crawlers +User-agent: * +Allow: / + +# Sitemap location +Sitemap: ${WEBSITE_URL}/api/sitemap.xml + +# Disallow admin paths +User-agent: * +Disallow: /admin/ +Disallow: /api/ + +# Special instructions for specific bots +User-agent: Googlebot +Allow: / + +User-agent: Bingbot +Allow: / + +User-agent: Baiduspider +Disallow: / + +# Crawl-delay suggestion +User-agent: * +Crawl-delay: 10 +`.trim(); + + return new Response(robotsTxt, { + headers: { + 'Content-Type': 'text/plain', + }, + }); +} diff --git a/website/src/app/api/sitemap.xml/route.ts b/website/src/app/api/sitemap.xml/route.ts new file mode 100644 index 0000000..258c43b --- /dev/null +++ b/website/src/app/api/sitemap.xml/route.ts @@ -0,0 +1,143 @@ +import { getContentAsBlogPosts } from '@/lib/content-mapper'; +import { locales } from '@/lib/i18n/settings'; + +const WEBSITE_URL = 'https://tech-notes-hub.vercel.app'; + +export async function GET() { + // 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); + + return new Response(sitemap, { + headers: { + 'Content-Type': 'text/xml', + }, + }); +} + +function generateSiteMap(posts: any[]) { + 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('')} + + `; +} diff --git a/website/src/app/favicon.ico b/website/src/app/favicon.ico new file mode 100644 index 0000000..1d053cd Binary files /dev/null and b/website/src/app/favicon.ico differ diff --git a/website/src/app/globals.css b/website/src/app/globals.css new file mode 100644 index 0000000..28a8bb5 --- /dev/null +++ b/website/src/app/globals.css @@ -0,0 +1,241 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 5.9% 10%; + --radius: 0.5rem; + } + + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + +/* Code block styling */ +.prose pre { + @apply bg-slate-950 text-slate-50 border border-slate-800; +} + +.prose code { + @apply text-slate-50; +} + +.prose .hljs { + @apply bg-transparent; +} + +/* Improve contrast for inline code */ +.prose :not(pre) > code { + @apply bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200 px-1.5 py-0.5 rounded text-sm font-medium; +} + +/* Special styling for code blocks with specific languages */ +.prose pre code.language-bash::before, +.prose pre code.language-sh::before { + content: "$ "; + @apply text-slate-500; +} + +.prose pre code.language-env::before { + content: ""; +} + +/* Environment variable styling */ +.prose pre code.language-env .hljs-attr { + @apply text-green-400; +} + +.prose pre code.language-env .hljs-string { + @apply text-yellow-400; +} + +/* Add special styling for .env files */ +.prose pre.language-env, +.prose code.language-env { + @apply bg-slate-950 text-slate-100; +} + +/* Enhance visibility of environment variables */ +.language-env .hljs-name, +.language-env .hljs-attr { + @apply text-emerald-400 font-semibold; +} + +.language-env .hljs-string, +.language-env .hljs-value { + @apply text-amber-400; +} + +/* Special styling for comments in code */ +.hljs-comment { + @apply text-slate-500 italic; +} + +/* Heading link hover effect */ +.prose h1:hover .absolute, +.prose h2:hover .absolute, +.prose h3:hover .absolute, +.prose h4:hover .absolute { + @apply opacity-100; +} diff --git a/website/src/app/layout.tsx b/website/src/app/layout.tsx new file mode 100644 index 0000000..cca6ff7 --- /dev/null +++ b/website/src/app/layout.tsx @@ -0,0 +1,34 @@ +import "./globals.css"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import { ThemeProvider } from "@/components/layout/theme-provider"; +import { Toaster } from "sonner"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Tech Notes Hub", + description: "Center for sharing tech knowledge and technical guides", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + {children} + + + + + ); +} diff --git a/website/src/app/page.tsx b/website/src/app/page.tsx new file mode 100644 index 0000000..e599e2d --- /dev/null +++ b/website/src/app/page.tsx @@ -0,0 +1,45 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { defaultLocale } from '@/lib/i18n/settings'; +import { setCookie } from '@/lib/cookies'; + +export default function Home() { + const router = useRouter(); + + useEffect(() => { + // Check if the locale is already saved in localStorage + const savedLocale = localStorage.getItem('preferredLocale'); + + if (savedLocale) { + // If there is, use the saved locale + // Also ensure the cookie is set + setCookie('preferredLocale', savedLocale, 365); + router.push(`/${savedLocale}`); + } else { + // If not, try to determine the locale from the browser + const browserLang = navigator.language.split('-')[0]; + const locale = browserLang === 'vi' ? 'vi' : defaultLocale; + + // Save to localStorage for future use + localStorage.setItem('preferredLocale', locale); + + // Also set a cookie for the middleware + setCookie('preferredLocale', locale, 365); + + // Redirect to the page with the determined locale + router.push(`/${locale}`); + } + }, [router]); + + // Show loading while waiting for redirect + return ( +
+
+
+

Loading...

+
+
+ ); +} diff --git a/website/src/components/blog/category-filter.tsx b/website/src/components/blog/category-filter.tsx new file mode 100644 index 0000000..aec72ef --- /dev/null +++ b/website/src/components/blog/category-filter.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { Category } from "@/lib/types"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; +import { Locale } from "@/lib/i18n/settings"; + +interface BlogCategoryFilterProps { + categories: (Category & { count: number })[]; + selectedCategory: string; + locale: Locale; + allCategoriesLabel?: string; +} + +export default function BlogCategoryFilter({ + categories, + selectedCategory, + locale, + allCategoriesLabel = "All Categories" +}: BlogCategoryFilterProps) { + return ( +
+ + {allCategoriesLabel} + + + {categories.map((category) => ( + + {category.name} + {category.count} + + ))} +
+ ); +} diff --git a/website/src/components/blog/code-snippet.tsx b/website/src/components/blog/code-snippet.tsx new file mode 100644 index 0000000..546e4ff --- /dev/null +++ b/website/src/components/blog/code-snippet.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useState } from 'react'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; +import { Copy, Check } from 'lucide-react'; + +interface CodeSnippetProps { + language: string; + content: string; +} + +export default function CodeSnippet({ language, content }: CodeSnippetProps) { + const [copied, setCopied] = useState(false); + + // Map common file extensions to highlight.js language names + const getLanguage = (extension: string): string => { + const langMap: Record = { + 'js': 'javascript', + 'ts': 'typescript', + 'py': 'python', + 'rb': 'ruby', + 'java': 'java', + 'cs': 'csharp', + 'cpp': 'cpp', + 'c': 'c', + 'go': 'go', + 'rs': 'rust', + 'php': 'php', + 'sh': 'bash', + 'yml': 'yaml', + 'yaml': 'yaml', + 'json': 'json', + 'md': 'markdown', + 'html': 'html', + 'css': 'css', + 'scss': 'scss', + 'sql': 'sql', + 'tsx': 'tsx', + 'jsx': 'jsx', + }; + + return langMap[extension.toLowerCase()] || extension; + }; + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(content); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy text: ', err); + } + }; + + return ( +
+ + + {content} + +
+ ); +} diff --git a/website/src/components/blog/language-switcher.tsx b/website/src/components/blog/language-switcher.tsx new file mode 100644 index 0000000..a3ab195 --- /dev/null +++ b/website/src/components/blog/language-switcher.tsx @@ -0,0 +1,69 @@ +'use client'; + +import Link from 'next/link'; +import { Locale } from '@/lib/i18n/settings'; +import { useEffect } from 'react'; +import { setCookie } from '@/lib/cookies'; +import { Globe } from 'lucide-react'; + +interface LanguageSwitcherProps { + currentLocale: Locale; + translations: any; + availableTranslations: Array<{ + locale: string; + slug: string; + }>; +} + +export default function LanguageSwitcher({ + currentLocale, + translations, + availableTranslations, +}: LanguageSwitcherProps) { + const t = translations.common; + + // Update preferredLocale in localStorage when the component mounts + useEffect(() => { + // Update localStorage and cookie with the current locale + if (typeof window !== 'undefined') { + localStorage.setItem('preferredLocale', currentLocale); + setCookie('preferredLocale', currentLocale, 365); // Valid for 1 year + } + }, [currentLocale]); + + // Handle language change (for analytics or other side effects) + const handleLanguageClick = (newLocale: string) => { + // Update localStorage and cookie + if (typeof window !== 'undefined') { + localStorage.setItem('preferredLocale', newLocale); + setCookie('preferredLocale', newLocale, 365); // Valid for 1 year + } + }; + + if (availableTranslations.length <= 1) { + return null; + } + + return ( +
+ + {t.blog.translatedContent}: +
+ {availableTranslations.map(translation => ( + handleLanguageClick(translation.locale)} + className={`text-sm px-2 py-1 rounded cursor-pointer ${ + translation.locale === currentLocale + ? 'bg-primary text-primary-foreground' + : 'bg-secondary text-secondary-foreground hover:bg-secondary/80' + }`} + > + {t.languages[translation.locale as keyof typeof t.languages] || translation.locale.toUpperCase()} + + ))} +
+
+ ); +} diff --git a/website/src/components/blog/markdown-content.tsx b/website/src/components/blog/markdown-content.tsx new file mode 100644 index 0000000..543006d --- /dev/null +++ b/website/src/components/blog/markdown-content.tsx @@ -0,0 +1,179 @@ +"use client"; + +import React, { useEffect, useState } from 'react'; +import ReactMarkdown from 'react-markdown'; +import rehypeRaw from 'rehype-raw'; +import rehypeSanitize from 'rehype-sanitize'; +import rehypeHighlight from 'rehype-highlight'; +import remarkGfm from 'remark-gfm'; +import 'highlight.js/styles/github-dark.css'; +import { slug } from 'github-slugger'; +import { Copy, Check } from 'lucide-react'; + +interface MarkdownContentProps { + content: string; +} + +export default function MarkdownContent({ content }: MarkdownContentProps) { + const [copiedCode, setCopiedCode] = useState(null); + + // Handle anchor link scrolling + useEffect(() => { + // Check if there's a hash in the URL + if (window.location.hash) { + const id = window.location.hash.substring(1); + const element = document.getElementById(id); + + if (element) { + // Wait a bit for the page to fully render + setTimeout(() => { + element.scrollIntoView({ behavior: 'smooth' }); + }, 100); + } + } + }, []); + + const handleCopyCode = async (code: string) => { + try { + await navigator.clipboard.writeText(code); + setCopiedCode(code); + setTimeout(() => setCopiedCode(null), 2000); + } catch (err) { + console.error('Failed to copy code: ', err); + } + }; + + return ( +
+ { + const headingId = slug(children as string); + return ( +

+ + + # + + {children} + +

+ ); + }, + h2: ({node, children, ...props}) => { + const headingId = slug(children as string); + return ( +

+ + + # + + {children} + +

+ ); + }, + h3: ({node, children, ...props}) => { + const headingId = slug(children as string); + return ( +

+ + + # + + {children} + +

+ ); + }, + h4: ({node, children, ...props}) => { + const headingId = slug(children as string); + return ( +

+ + + # + + {children} + +

+ ); + }, + p: ({node, ...props}) =>

, + a: ({node, href, ...props}) => { + // Check if it's an anchor link + const isAnchor = href?.startsWith('#'); + return ( + + ); + }, + ul: ({node, ...props}) =>