commit 785a158451e0df62adbc150ffa5ca6933b39eed6 Author: Ismo Vuorinen Date: Tue Jul 29 12:47:50 2025 +0300 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e77edde --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# EditorConfig is awesome: https://editorconfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +max_line_length = 120 + diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..a5ff83b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @ivuorinen diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2d86e93 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,145 @@ +# Citizen Code of Conduct + +## 1. Purpose + +A primary goal of @ivuorinen's repositories is to be inclusive to the largest +number of contributors, with the most varied and diverse backgrounds possible. +As such, we are committed to providing a friendly, safe and welcoming +environment for all, regardless of gender, sexual orientation, ability, +ethnicity, socioeconomic status, and religion (or lack thereof). + +This code of conduct outlines our expectations for all those who participate in +our community, as well as the consequences for unacceptable behavior. + +We invite all those who participate in @ivuorinen's repositories to help us +create safe and positive experiences for everyone. + +## 2. Open [Source/Culture/Tech] Citizenship + +A supplemental goal of this Code of Conduct is to increase +open [source/culture/tech] citizenship by encouraging participants to recognize +and strengthen the relationships between our actions and their effects on our +community. + +Communities mirror the societies in which they exist and positive action is +essential to counteract the many forms of inequality and abuses of power that +exist in society. + +If you see someone who is making an extra effort to ensure our community is +welcoming, friendly, and encourages all participants to contribute to the +fullest extent, we want to know. + +## 3. Expected Behavior + +The following behaviors are expected and requested of all community members: + +* Participate in an authentic and active way. In doing so, you contribute to the + health and longevity of this community. +* Exercise consideration and respect in your speech and actions. +* Attempt collaboration before conflict. +* Refrain from demeaning, discriminatory, or harassing behavior and speech. +* Be mindful of your surroundings and of your fellow participants. Alert + community leaders if you notice a dangerous situation, someone in distress, or + violations of this Code of Conduct, even if they seem inconsequential. +* Remember that community event venues may be shared with members of the public; + please be respectful to all patrons of these locations. + +## 4. Unacceptable Behavior + +The following behaviors are considered harassment and are unacceptable within +our community: + +* Violence, threats of violence or violent language directed against another + person. +* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory + jokes and language. +* Posting or displaying sexually explicit or violent material. +* Posting or threatening to post other people's personally identifying + information ("doxing"). +* Personal insults, particularly those related to gender, sexual orientation, + race, religion, or disability. +* Inappropriate photography or recording. +* Inappropriate physical contact. You should have someone's consent before + touching them. +* Unwelcome sexual attention. This includes, sexualized comments or jokes; + inappropriate touching, groping, and unwelcomed sexual advances. +* Deliberate intimidation, stalking or following (online or in person). +* Advocating for, or encouraging, any of the above behavior. +* Sustained disruption of community events, including talks and presentations. + +## 5. Weapons Policy + +No weapons will be allowed at @ivuorinen's repositories events, community +spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons +include but are not limited to guns, explosives (including fireworks), and large +knives such as those used for hunting or display, as well as any other item used +for the purpose of causing injury or harm to others. Anyone seen in possession +of one of these items will be asked to leave immediately, and will only be +allowed to return without the weapon. Community members are further expected to +comply with all state and local laws on this matter. + +## 6. Consequences of Unacceptable Behavior + +Unacceptable behavior from any community member, including sponsors and those +with decision-making authority, will not be tolerated. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. + +If a community member engages in unacceptable behavior, the community organizers +may take any action they deem appropriate, up to and including a temporary ban +or permanent expulsion from the community without warning (and without refund in +the case of a paid event). + +## 7. Reporting Guidelines + +If you are subject to or witness unacceptable behavior, or have any other +concerns, please notify a community organizer as soon as possible: + + +Additionally, community organizers are available to help community members +engage with local law enforcement or to otherwise help those experiencing +unacceptable behavior feel safe. In the context of in-person events, organizers +will also provide escorts as desired by the person experiencing distress. + +## 8. Addressing Grievances + +If you feel you have been falsely or unfairly accused of violating this Code of +Conduct, you should notify @ivuorinen with a concise description of your +grievance. Your grievance will be handled in accordance with our existing +governing policies. + +## 9. Scope + +We expect all community participants (contributors, paid or otherwise; sponsors; +and other guests) to abide by this Code of Conduct in all community +venues--online and in-person--as well as in all one-on-one communications +pertaining to community business. + +This code of conduct and its related procedures also applies to unacceptable +behavior occurring outside the scope of community activities when such behavior +has the potential to adversely affect the safety and well-being of community +members. + +## 10. Contact info + +@ivuorinen + + +## 11. License and attribution + +The Citizen Code of Conduct is distributed by [Stumptown Syndicate][stumptown] +under a [Creative Commons Attribution-ShareAlike license][cc-by-sa]. + +Portions of text derived from the [Django Code of Conduct][django] and +the [Geek Feminism Anti-Harassment Policy][geek-feminism]. + +* _Revision 2.3. Posted 6 March 2017._ +* _Revision 2.2. Posted 4 February 2016._ +* _Revision 2.1. Posted 23 June 2014._ +* _Revision 2.0, adopted by the [Stumptown Syndicate][stumptown] board on 10 + January 2013. Posted 17 March 2013._ + +[stumptown]: https://github.com/stumpsyn +[cc-by-sa]: https://creativecommons.org/licenses/by-sa/3.0/ +[django]: https://www.djangoproject.com/conduct/ +[geek-feminism]: http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f57b5f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: ivuorinen + +--- + +**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. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..abdc2e8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: ivuorinen + +--- + +**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. diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..e46316f --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>ivuorinen/renovate-config" + ] +} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..c75d036 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,46 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: 'CodeQL' + +on: + push: + branches: ['main'] + pull_request: + branches: ['main'] + schedule: + - cron: '30 1 * * 0' # Run at 1:30 AM UTC every Sunday + merge_group: + +permissions: + actions: read + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['javascript'] # Add languages used in your actions + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + with: + languages: ${{ matrix.language }} + queries: security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + with: + category: '/language:${{matrix.language}}' diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 0000000..75e08c0 --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,30 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Lint Code Base + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: read-all + +jobs: + Linter: + name: PR Lint + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + statuses: write + contents: read + packages: read + + steps: + - name: Run PR Lint + # https://github.com/ivuorinen/actions + uses: ivuorinen/actions/pr-lint@8476cd4675ea8210eadf4a267bbeb13bddea4e75 # 25.7.21 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..667f284 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,26 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Stale + +on: + schedule: + - cron: '0 8 * * *' # Every day at 08:00 + workflow_call: + workflow_dispatch: + +permissions: + contents: read + packages: read + statuses: read + +jobs: + stale: + name: 🧹 Clean up stale issues and PRs + runs-on: ubuntu-latest + + permissions: + contents: write # only for delete-branch option + issues: write + pull-requests: write + steps: + - uses: ivuorinen/actions/stale@8476cd4675ea8210eadf4a267bbeb13bddea4e75 # 25.7.21 diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..5f88668 --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,41 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: Sync Labels + +on: + push: + branches: + - main + - master + paths: + - '.github/labels.yml' + - '.github/workflows/sync-labels.yml' + schedule: + - cron: '34 5 * * *' # Run every day at 05:34 AM UTC + workflow_call: + workflow_dispatch: + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: read-all + +jobs: + labels: + name: ♻️ Sync Labels + runs-on: ubuntu-latest + timeout-minutes: 10 + + permissions: + contents: read + issues: write + + steps: + - name: ⤵️ Checkout Repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: ⤵️ Sync Latest Labels Definitions + uses: ivuorinen/actions/sync-labels@8476cd4675ea8210eadf4a267bbeb13bddea4e75 # 25.7.21 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2a1faa --- /dev/null +++ b/.gitignore @@ -0,0 +1,134 @@ +.php-cs-fixer.cache +.php-cs-fixer.php +composer.phar +/vendor/ +.phpunit.result.cache +.phpunit.cache +/app/phpunit.xml +/phpunit.xml +/build/ +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json +pids +*.pid +*.seed +*.pid.lock +lib-cov +coverage +*.lcov +.nyc_output +.grunt +bower_components +.lock-wscript +build/Release +node_modules/ +jspm_packages/ +web_modules/ +*.tsbuildinfo +.npm +.eslintcache +.stylelintcache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ +.node_repl_history +*.tgz +.yarn-integrity +.env +.env.development.local +.env.test.local +.env.production.local +.env.local +.cache +.parcel-cache +.next +out +.nuxt +dist +.cache/ +.vuepress/dist +.temp +.docusaurus +.serverless/ +.fusebox/ +.dynamodb/ +.tern-port +.vscode-test +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] +Session.vim +Sessionx.vim +.netrwhist +*~ +tags +[._]*.un~ +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf +.idea/**/aws.xml +.idea/**/contentModel.xml +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml +.idea/**/gradle.xml +.idea/**/libraries +cmake-build-*/ +.idea/**/mongoSettings.xml +*.iws +out/ +.idea_modules/ +atlassian-ide-plugin.xml +.idea/replstate.xml +.idea/sonarlint/ +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +.idea/httpRequests +.idea/caches/build_file_checksums.ser +npm-debug.log +yarn-error.log +bootstrap/compiled.php +app/storage/ +public/storage +public/hot +public_html/storage +public_html/hot +storage/*.key +Homestead.yaml +Homestead.json +/.vagrant +/node_modules +/.pnp +.pnp.js +/coverage +/.next/ +/out/ +/build +.DS_Store +*.pem +.env*.local +.vercel +next-env.d.ts diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..3de10f3 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,13 @@ +{ + "default": true, + "MD013": { + "line_length": 200, + "code_blocks": false, + "tables": false + }, + "MD024": { + "siblings_only": true + }, + "MD033": false, + "MD041": false +} diff --git a/.mega-linter.yml b/.mega-linter.yml new file mode 100644 index 0000000..82e546d --- /dev/null +++ b/.mega-linter.yml @@ -0,0 +1,35 @@ +--- +# Configuration file for MegaLinter +# See all available variables at +# https://megalinter.io/configuration/ and in linters documentation + +APPLY_FIXES: all +SHOW_ELAPSED_TIME: false # Show elapsed time at the end of MegaLinter run +PARALLEL: true +VALIDATE_ALL_CODEBASE: true +FILEIO_REPORTER: false # Generate file.io report +GITHUB_STATUS_REPORTER: true # Generate GitHub status report +IGNORE_GENERATED_FILES: true # Ignore generated files +JAVASCRIPT_DEFAULT_STYLE: prettier # Default style for JavaScript +PRINT_ALPACA: false # Print Alpaca logo in console +SARIF_REPORTER: true # Generate SARIF report +SHOW_SKIPPED_LINTERS: false # Show skipped linters in MegaLinter log + +DISABLE_LINTERS: + - REPOSITORY_DEVSKIM + +ENABLE_LINTERS: + - YAML_YAMLLINT + - MARKDOWN_MARKDOWNLINT + - YAML_PRETTIER + - JSON_PRETTIER + - JAVASCRIPT_ES + - TYPESCRIPT_ES + +YAML_YAMLLINT_CONFIG_FILE: .yamllint.yml +MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .markdownlint.json +JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.json +TYPESCRIPT_ES_CONFIG_FILE: .eslintrc.json + +FILTER_REGEX_EXCLUDE: > + (node_modules|\.automation/test|docs/json-schemas|\.github/workflows) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ccfa22d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,63 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: requirements-txt-fixer + - id: detect-private-key + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: check-case-conflict + - id: check-merge-conflict + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: check-symlinks + - id: check-toml + - id: check-xml + - id: check-yaml + args: [--allow-multiple-documents] + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=auto] + - id: pretty-format-json + args: [--autofix, --no-sort-keys] + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.44.0 + hooks: + - id: markdownlint + args: [-c, .markdownlint.json, --fix] + + - repo: https://github.com/adrienverge/yamllint + rev: v1.37.0 + hooks: + - id: yamllint + + - repo: https://github.com/scop/pre-commit-shfmt + rev: v3.11.0-1 + hooks: + - id: shfmt + + - repo: https://github.com/koalaman/shellcheck-precommit + rev: v0.10.0 + hooks: + - id: shellcheck + args: ['--severity=warning'] + + - repo: https://github.com/rhysd/actionlint + rev: v1.7.7 + hooks: + - id: actionlint + args: ['-shellcheck='] + + - repo: https://github.com/renovatebot/pre-commit-hooks + rev: 39.227.2 + hooks: + - id: renovate-config-validator + + - repo: https://github.com/bridgecrewio/checkov.git + rev: '3.2.400' + hooks: + - id: checkov + args: + - '--quiet' diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 0000000..b430800 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1 @@ +disable=SC2129 diff --git a/.yamlignore b/.yamlignore new file mode 100644 index 0000000..e69de29 diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..065bc60 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,13 @@ +--- +extends: default + +rules: + line-length: + max: 200 + level: warning + truthy: + check-keys: false + comments: + min-spaces-from-content: 1 + trailing-spaces: + level: warning diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2753b34 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,152 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +phpenv.fish is a PHP version manager for Fish Shell, similar to nvm for Node.js. +It provides fast PHP version switching, extension management, and automatic version detection from project files. + +## Development Commands + +### Testing Changes + +Since this is a Fish shell plugin, test changes by: + +```bash +# Reload the function after changes +source functions/phpenv.fish + +# Test commands +phpenv help +phpenv versions +phpenv current +``` + +### Installation for Development + +```bash +# Link the development version to Fish config +ln -sf $PWD/functions/phpenv.fish ~/.config/fish/functions/phpenv.fish +ln -sf $PWD/completions/phpenv.fish ~/.config/fish/completions/phpenv.fish +ln -sf $PWD/conf.d/phpenv.fish ~/.config/fish/conf.d/phpenv.fish +``` + +## Code Architecture + +### Core Components + +1. **Main Dispatcher (`functions/phpenv.fish`)** + - Entry point function that routes commands to internal functions + - All subcommands are implemented as `__phpenv_*` functions + - Version detection logic in `__phpenv_detect_version` + +2. **Completions (`completions/phpenv.fish`)** + - Provides tab completions for all commands + - Fetches available versions dynamically from shivammathur/setup-php + +3. **Configuration (`conf.d/phpenv.fish`)** + - Sets up Fish universal variables on load + - Handles PATH initialization + +### Key Design Patterns + +- **Performance Focus**: Direct directory checks instead of `brew list` (100-1000x faster) +- **Fish Universal Variables**: Used for configuration persistence +- **Homebrew Integration**: Uses shivammathur taps for PHP/extension installation +- **Version File Priority**: `.php-version` > `.tool-version` > `composer.json` > global > system + +### Version Detection Flow + +1. Check for `.php-version` file (exact version) +2. Check for `.tool-version` file (parse PHP line) +3. Check `composer.json` for PHP constraints (semver resolution) +4. Use global version from Fish universal variable +5. Fall back to system PHP + +### Important Implementation Details + +- All internal functions are prefixed with `__phpenv_` +- Version resolution supports semver constraints (^8.1, ~8.2.0, etc.) +- Extension management uses separate Homebrew tap +- Auto-switching uses Fish's `pwd` event handler +- Configuration stored in Fish universal variables with `PHPENV_` prefix + +## Code Style Requirements + +- Maximum line length: 120 characters (enforced by .editorconfig) +- Use LF line endings +- UTF-8 encoding +- Trim trailing whitespace +- Insert final newline + +## PATH and Variable Management + +### PATH State Tracking + +- `PHPENV_ORIGINAL_PATH`: Stores initial PATH before any modifications +- `PHPENV_CURRENT_VERSION`: Tracks currently active PHP version +- `PHPENV_CURRENT_PATH`: Stores path to current PHP binary +- Use `phpenv use system` to restore original PATH + +### Variable Scope Strategy + +- **Universal variables** (`set -U`): Only `PHPENV_GLOBAL_VERSION` (persists across shells) +- **Session variables** (`set -g`): Configuration settings (per-shell session) +- **Local variables** (`set -l`): Function-scoped, automatically cleaned up + +### Auto-Switch Debouncing + +- `PHPENV_LAST_SWITCH_TIME`: Prevents excessive PATH changes on rapid directory changes +- 1-second minimum interval between auto-switches +- Early exit if already using correct version + +## Common Tasks + +### Adding a New Command + +1. Add case in main `phpenv` function switch statement +2. Implement `__phpenv_` function +3. Add completions in `completions/phpenv.fish` +4. Update help text in `__phpenv_help` + +### Modifying Version Detection + +- Edit `__phpenv_detect_version` function +- Maintain priority order of version sources +- Test with various project configurations + +### Working with Homebrew Integration + +- PHP versions: `shivammathur/php/php@` +- Extensions: `shivammathur/extensions/@` +- Check formula existence before operations + +### Performance Optimizations + +#### Caching System + +- `__phpenv_version_cache`: 5-minute cache for API version data +- `__phpenv_cellar_cache`: Permanent cache for Homebrew Cellar path +- Reduces network calls and filesystem operations + +#### Unified Helper Functions + +- `__phpenv_parse_version_field`: Single function for all jq parsing (eliminates 9+ duplicated calls) +- `__phpenv_ensure_taps`: Unified Homebrew tap management +- `__phpenv_get_available_extensions`: Shared extension listing logic + +### PATH Management Best Practices + +- Always check `PHPENV_ORIGINAL_PATH` exists before modification +- Use debouncing for automatic operations +- Validate PHP paths before setting +- Provide restoration mechanism (`phpenv use system`) +- Clean up temporary variables in error cases + +### Code Organization Principles + +- Cache expensive operations (API calls, filesystem checks) +- Unify repeated patterns into helper functions +- Use session variables instead of universal where possible +- Minimize network requests and subprocess calls diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6b210e7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +MIT License Copyright (c) 2025 Ismo Vuorinen + +Permission is hereby granted, free of charge, to any person obtaining a copy +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..8d1507b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Ismo Vuorinen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..485fb61 --- /dev/null +++ b/README.md @@ -0,0 +1,286 @@ +# phpenv.fish + +A fast, feature-rich PHP version manager for Fish Shell that acts like goenv or nvm. + +## Features + +- **Fast version detection** (100-1000x faster than `brew list`) +- **Dynamic version resolution** from [shivammathur/setup-php](https://github.com/shivammathur/setup-php) +- **Multiple version sources**: `.php-version`, `.tool-version`, `composer.json` +- **Auto-installation** of missing PHP versions +- **Extension management** with availability checking +- **Composer.json integration** with full semver support +- **Auto-switching** between versions (configurable) +- **Fisher package manager** support +- **Rich completions** with descriptions + +## Installation + +### Using Fisher (recommended) + +```bash +fisher install ivuorinen/phpenv.fish +``` + +### Manual Installation + +1. Copy files to your fish configuration: + ```bash + # Functions + curl -L https://raw.githubusercontent.com/ivuorinen/phpenv.fish/main/functions/phpenv.fish > ~/.config/fish/functions/phpenv.fish + + # Completions + curl -L https://raw.githubusercontent.com/ivuorinen/phpenv.fish/main/completions/phpenv.fish > ~/.config/fish/completions/phpenv.fish + + # Configuration + curl -L https://raw.githubusercontent.com/ivuorinen/phpenv.fish/main/conf.d/phpenv.fish > ~/.config/fish/conf.d/phpenv.fish + ``` + +2. Install dependencies: + ```bash + brew install jq + ``` + +3. Add Homebrew taps: + ```bash + brew tap shivammathur/php + brew tap shivammathur/extensions + ``` + +## Quick Start + +```bash +# Show available versions +phpenv versions + +# Install PHP versions +phpenv install 8.3 +phpenv install 8.1 + +# Set global default +phpenv global 8.3 + +# Set project-specific version +phpenv local 8.1 + +# Install extensions +phpenv extensions install xdebug +phpenv extensions install redis + +# Configure behavior +phpenv config set auto-switch false # Disable auto-switching +phpenv config set auto-install true # Enable auto-installation + +# Check installation +phpenv doctor +``` + +## Commands + +### Version Management + +- `phpenv install ` - Install PHP version +- `phpenv uninstall ` - Uninstall PHP version +- `phpenv use ` - Use version for current shell +- `phpenv local ` - Set version for current project +- `phpenv global ` - Set global default version +- `phpenv list` - List installed versions +- `phpenv current` - Show current version +- `phpenv versions` - Show all available versions + +### Extension Management + +- `phpenv extensions install ` - Install extension for current PHP +- `phpenv extensions uninstall ` - Uninstall extension +- `phpenv extensions list` - List installed extensions +- `phpenv extensions available` - Show available extensions + +### Configuration + +- `phpenv config get ` - Get configuration value +- `phpenv config set ` - Set configuration value +- `phpenv config list` - List all configuration + +### Utilities + +- `phpenv which [binary]` - Show path to PHP binary +- `phpenv doctor` - Check installation health +- `phpenv help` - Show help + +## Version Detection + +phpenv automatically detects PHP versions from multiple sources (in priority order): + +1. **`.php-version`** - Project-specific version file +2. **`.tool-version`** - Tool version file (parses `v8.4` as `8.4`) +3. **`composer.json`** - Both `config.platform.php` and `require.php` with semver support +4. **Global version** - Fish universal variable +5. **System PHP** - Fallback to system installation + +### Composer.json Support + +Supports all semver constraints: +- `^8.1` → Uses PHP 8.3 (latest 8.x) +- `~8.2.0` → Uses PHP 8.2 +- `>=8.0` → Uses PHP 8.3 +- `8.1.*` → Uses PHP 8.1 + +Checks both locations: +```json +{ + "require": { + "php": "^8.1" + }, + "config": { + "platform": { + "php": "8.2.0" + } + } +} +``` + +## Configuration + +### Configuration Keys + +- `auto-install` - Auto-install missing versions (default: false) +- `auto-install-extensions` - Install extensions with new PHP versions (default: false) +- `auto-switch` - Auto-switch versions when changing directories (default: true) +- `default-extensions` - Space-separated list of default extensions (default: "opcache") +- `global-version` - Global PHP version + +### Configuration Files + +phpenv checks these locations (in order): + +1. `~/.config/fish/conf.d/phpenv.fish` (preferred) +2. `~/.config/phpenv/config` +3. `~/.phpenv.fish` + +### Examples + +```bash +# Enable auto-installation +phpenv config set auto-install true + +# Disable auto-switching +phpenv config set auto-switch false + +# Set default extensions +phpenv config set default-extensions "opcache xdebug redis" + +# Enable auto-extension installation +phpenv config set auto-install-extensions true +``` + +## Supported PHP Versions + +Uses [shivammathur/homebrew-php](https://github.com/shivammathur/homebrew-php) with dynamic version detection: + +**Version Aliases:** +- `latest` - Latest stable PHP version +- `nightly` - Development version +- `8.x` - Latest PHP 8.x version +- `7.x` - Latest PHP 7.x version +- `5.x` - Latest PHP 5.x version + +**Available Versions:** 5.6, 7.0-7.4, 8.0-8.5 + +## Supported Extensions + +Uses [shivammathur/homebrew-extensions](https://github.com/shivammathur/homebrew-extensions): + +- xdebug, redis, imagick, mongodb, memcached +- pcov, ast, grpc, protobuf, yaml +- And many more... + +## Performance + +- **Directory checks**: ~1-5ms (vs ~1000ms for `brew list`) +- **Bulk version detection**: ~10ms for all versions +- **No Ruby overhead** or git operations +- **Efficient caching** and lazy loading + +## Auto-switching + +phpenv automatically switches PHP versions when you change directories if a version file is detected in the project. + +## Fisher Integration + +Works seamlessly with Fisher package manager: + +```bash +# Install +fisher install ivuorinen/phpenv.fish + +# Update +fisher update ivuorinen/phpenv.fish + +# Uninstall +fisher remove ivuorinen/phpenv.fish +``` + +## Troubleshooting + +### Check Installation + +```bash +phpenv doctor +``` + +### Common Issues + +#### jq not found + +```bash +brew install jq +``` + +#### PHP version not found + +```bash +# Add taps manually +brew tap shivammathur/php +brew tap shivammathur/extensions + +# Install specific version +phpenv install 8.3 +``` + +#### Slow performance + +- phpenv is designed to be fast by avoiding `brew list` +- If performance issues persist, check your filesystem or try `phpenv doctor` + +### Debug Information + +```bash +# Check current detection +phpenv current + +# Check which binary is used +phpenv which php + +# List all configuration +phpenv config list +``` + +## Contributing + +Contributions welcome! Please: + +1. Follow fish shell best practices +2. Add tests for new functionality +3. Update documentation +4. Maintain performance optimizations + +## License + +MIT License - see LICENSE file for details. + +## Related Projects + +- [shivammathur/homebrew-php](https://github.com/shivammathur/homebrew-php) +- [shivammathur/homebrew-extensions](https://github.com/shivammathur/homebrew-extensions) +- [jorgebucaran/fisher](https://github.com/jorgebucaran/fisher) + diff --git a/completions/phpenv.fish b/completions/phpenv.fish new file mode 100644 index 0000000..8b627e8 --- /dev/null +++ b/completions/phpenv.fish @@ -0,0 +1,123 @@ +# Completions for phpenv command +# Place in ~/.config/fish/completions/phpenv.fish + +# Complete main commands +complete -c phpenv -f -n "__fish_use_subcommand" -a "install" -d "Install a PHP version" +complete -c phpenv -f -n "__fish_use_subcommand" -a "uninstall" -d "Uninstall a PHP version" +complete -c phpenv -f -n "__fish_use_subcommand" -a "use" -d "Use PHP version for current shell" +complete -c phpenv -f -n "__fish_use_subcommand" -a "local" -d "Set PHP version for current project" +complete -c phpenv -f -n "__fish_use_subcommand" -a "global" -d "Set global PHP version" +complete -c phpenv -f -n "__fish_use_subcommand" -a "list" -d "List installed PHP versions" +complete -c phpenv -f -n "__fish_use_subcommand" -a "ls" -d "List installed PHP versions" +complete -c phpenv -f -n "__fish_use_subcommand" -a "current" -d "Show current PHP version" +complete -c phpenv -f -n "__fish_use_subcommand" -a "which" -d "Show path to PHP binary" +complete -c phpenv -f -n "__fish_use_subcommand" -a "versions" -d "Show all available versions" +complete -c phpenv -f -n "__fish_use_subcommand" -a "doctor" -d "Check phpenv installation" +complete -c phpenv -f -n "__fish_use_subcommand" -a "config" -d "Manage configuration" +complete -c phpenv -f -n "__fish_use_subcommand" -a "extensions" -d "Manage PHP extensions" +complete -c phpenv -f -n "__fish_use_subcommand" -a "ext" -d "Manage PHP extensions" +complete -c phpenv -f -n "__fish_use_subcommand" -a "help" -d "Show help" + +# Helper functions for completions +function __phpenv_complete_installed_versions + phpenv list 2>/dev/null | sed 's/^[* ]*//' +end + +function __phpenv_complete_available_versions + # Try to get dynamic versions first + if command -q curl -a command -q jq; and functions -q __phpenv_parse_version_field + echo "latest" + echo "nightly" + echo "5.x" + echo "7.x" + echo "8.x" + __phpenv_parse_version_field "latest" "8.4" + __phpenv_parse_version_field "nightly" "8.5" + __phpenv_parse_version_field "5.x" "5.6" + __phpenv_parse_version_field "7.x" "7.4" + __phpenv_parse_version_field "8.x" "8.4" + end + + # Fallback to common versions + printf "5.6\n7.0\n7.1\n7.2\n7.3\n7.4\n8.0\n8.1\n8.2\n8.3\n8.4\n8.5\n" +end + +function __phpenv_complete_config_keys + printf "global-version\nauto-install\nauto-install-extensions\nauto-switch\ndefault-extensions\n" +end + +function __phpenv_complete_extensions + printf "xdebug\nredis\nimagick\nmongodb\nmemcached\npcov\nast\ngrpc\n" + printf "protobuf\nyaml\nzip\ncurl\ngd\nintl\nmbstring\nmysql\nopcache\npdo\nsockets\nxml\n" +end + +function __phpenv_complete_binaries + printf "php\nphp-config\nphpize\ncomposer\npecl\npear\n" +end + +# Complete versions for install command +complete -c phpenv -f -n "__fish_seen_subcommand_from install" \ + -a "(__phpenv_complete_available_versions)" -d "PHP version" + +# Complete installed versions for uninstall, use commands +complete -c phpenv -f -n "__fish_seen_subcommand_from uninstall use local global" \ + -a "(__phpenv_complete_installed_versions)" -d "Installed PHP version" + +# Add system option for use command +complete -c phpenv -f -n "__fish_seen_subcommand_from use" -a "system" -d "Use system PHP" + +# Complete binaries for which command +complete -c phpenv -f -n "__fish_seen_subcommand_from which" -a "(__phpenv_complete_binaries)" -d "PHP binary" + +# Complete config subcommands +complete -c phpenv -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set list" \ + -a "get" -d "Get configuration value" +complete -c phpenv -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set list" \ + -a "set" -d "Set configuration value" +complete -c phpenv -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set list" \ + -a "list" -d "List all configuration" + +# Complete config keys +complete -c phpenv -f -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from get set" \ + -a "(__phpenv_complete_config_keys)" -d "Configuration key" + +# Complete config values for boolean settings +complete -c phpenv -f \ + -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set" \ + -n "contains -- (commandline -opc)[-1] auto-install auto-install-extensions auto-switch" \ + -a "true false" -d "Boolean value" + +# Complete extensions subcommands +complete -c phpenv -f \ + -n "__fish_seen_subcommand_from extensions ext" \ + -n "not __fish_seen_subcommand_from install uninstall remove list ls available" \ + -a "install" -d "Install PHP extension" +complete -c phpenv -f \ + -n "__fish_seen_subcommand_from extensions ext" \ + -n "not __fish_seen_subcommand_from install uninstall remove list ls available" \ + -a "uninstall" -d "Uninstall PHP extension" +complete -c phpenv -f \ + -n "__fish_seen_subcommand_from extensions ext" \ + -n "not __fish_seen_subcommand_from install uninstall remove list ls available" \ + -a "remove" -d "Remove PHP extension" +complete -c phpenv -f \ + -n "__fish_seen_subcommand_from extensions ext" \ + -n "not __fish_seen_subcommand_from install uninstall remove list ls available" \ + -a "list" -d "List installed extensions" +complete -c phpenv -f \ + -n "__fish_seen_subcommand_from extensions ext" \ + -n "not __fish_seen_subcommand_from install uninstall remove list ls available" \ + -a "ls" -d "List installed extensions" +complete -c phpenv -f \ + -n "__fish_seen_subcommand_from extensions ext" \ + -n "not __fish_seen_subcommand_from install uninstall remove list ls available" \ + -a "available" -d "Show available extensions" + +# Complete extension names +complete -c phpenv -f \ + -n "__fish_seen_subcommand_from extensions ext; and __fish_seen_subcommand_from install" \ + -a "(__phpenv_complete_extensions)" -d "PHP extension" + +# Complete help options +complete -c phpenv -f -s h -l help -d "Show help" + diff --git a/conf.d/phpenv.fish b/conf.d/phpenv.fish new file mode 100644 index 0000000..1b93ca9 --- /dev/null +++ b/conf.d/phpenv.fish @@ -0,0 +1,38 @@ +# phpenv configuration file +# Place in ~/.config/fish/conf.d/phpenv.fish + +# Set default configuration using session variables for most settings +# Only PHPENV_GLOBAL_VERSION needs to persist across shells +if not set -q PHPENV_AUTO_INSTALL + set -g PHPENV_AUTO_INSTALL false +end + +if not set -q PHPENV_AUTO_INSTALL_EXTENSIONS + set -g PHPENV_AUTO_INSTALL_EXTENSIONS false +end + +if not set -q PHPENV_AUTO_SWITCH + set -g PHPENV_AUTO_SWITCH true +end + +if not set -q PHPENV_DEFAULT_EXTENSIONS + set -g PHPENV_DEFAULT_EXTENSIONS "opcache" +end + +# Initialize PATH on shell startup if global version is set (less aggressive) +if test -n "$PHPENV_GLOBAL_VERSION"; and not set -q PHPENV_INITIALIZED + if functions -q __phpenv_is_version_installed __phpenv_set_php_path + if __phpenv_is_version_installed "$PHPENV_GLOBAL_VERSION" 2>/dev/null + # Only set PATH if no project-specific version is detected + if not __phpenv_find_version_file .php-version >/dev/null 2>&1 + if not __phpenv_find_version_file .tool-version >/dev/null 2>&1 + if not test -f composer.json + __phpenv_set_php_path "$PHPENV_GLOBAL_VERSION" 2>/dev/null + end + end + end + end + end + set -g PHPENV_INITIALIZED true +end + diff --git a/fish_plugins b/fish_plugins new file mode 100644 index 0000000..8d03752 --- /dev/null +++ b/fish_plugins @@ -0,0 +1,85 @@ +# Fisher package definition for phpenv.fish +# This file enables installation via: fisher install ivuorinen/phpenv.fish + +function __phpenv_install --on-event phpenv_install + echo "Installing phpenv.fish..." + + # Check for required dependencies + if not command -q jq + echo "Installing jq dependency..." + if command -q brew + brew install jq + else + echo "Error: Homebrew is required to install jq dependency" >&2 + echo "Install Homebrew first: https://brew.sh" >&2 + return 1 + end + end + + # Add required Homebrew taps + if command -q brew; and functions -q __phpenv_ensure_taps + echo "Adding Homebrew taps..." + __phpenv_ensure_taps + end + + # Set default configuration if not already set (use session variables) + if not set -q PHPENV_AUTO_INSTALL + set -g PHPENV_AUTO_INSTALL false + echo "Set PHPENV_AUTO_INSTALL=false (change with: phpenv config set auto-install true)" + end + + if not set -q PHPENV_AUTO_INSTALL_EXTENSIONS + set -g PHPENV_AUTO_INSTALL_EXTENSIONS false + echo "Set PHPENV_AUTO_INSTALL_EXTENSIONS=false" + end + + if not set -q PHPENV_AUTO_SWITCH + set -g PHPENV_AUTO_SWITCH true + echo "Set PHPENV_AUTO_SWITCH=true (automatic version switching enabled)" + end + + if not set -q PHPENV_DEFAULT_EXTENSIONS + set -g PHPENV_DEFAULT_EXTENSIONS "opcache" + echo "Set default extensions: opcache" + end + + echo "phpenv.fish installed successfully!" + echo "" + echo "Quick start:" + echo " phpenv versions # Show available versions" + echo " phpenv install 8.3 # Install PHP 8.3" + echo " phpenv global 8.3 # Set as global default" + echo " phpenv help # Show all commands" + echo "" + echo "Run 'phpenv doctor' to check your installation." +end + +function __phpenv_update --on-event phpenv_update + echo "Updating phpenv.fish..." + __phpenv_install +end + +function __phpenv_uninstall --on-event phpenv_uninstall + echo "Uninstalling phpenv.fish..." + + # Remove all phpenv variables (both session and universal) + set -e PHPENV_AUTO_INSTALL + set -e PHPENV_AUTO_INSTALL_EXTENSIONS + set -e PHPENV_AUTO_SWITCH + set -e PHPENV_DEFAULT_EXTENSIONS + set -e PHPENV_GLOBAL_VERSION + set -e PHPENV_ORIGINAL_PATH + set -e PHPENV_CURRENT_VERSION + set -e PHPENV_CURRENT_PATH + set -e PHPENV_LAST_SWITCH_TIME + set -e PHPENV_VERSION_OVERRIDE + set -e PHPENV_INITIALIZED + # Remove cache variables + set -e __phpenv_version_cache + set -e __phpenv_version_cache_time + set -e __phpenv_cellar_cache + + echo "phpenv.fish configuration removed." + echo "Note: Installed PHP versions are preserved." +end + diff --git a/functions/phpenv.fish b/functions/phpenv.fish new file mode 100644 index 0000000..40a5d2f --- /dev/null +++ b/functions/phpenv.fish @@ -0,0 +1,1078 @@ +# phpenv - PHP Version Manager for Fish Shell +# Repository: https://github.com/ivuorinen/phpenv.fish + +function phpenv -d "PHP version manager for Fish Shell" + if not command -q jq + echo "Error: jq is required but not installed. Install with: brew install jq" >&2 + return 1 + end + + set -l phpenv_cmd $argv[1] + set -l phpenv_args $argv[2..] + + switch $phpenv_cmd + case install + __phpenv_install $phpenv_args + case uninstall + __phpenv_uninstall $phpenv_args + case use + __phpenv_use $phpenv_args + case local + __phpenv_local $phpenv_args + case global + __phpenv_global $phpenv_args + case list ls + __phpenv_list $phpenv_args + case current + __phpenv_current + case which + __phpenv_which $phpenv_args + case versions + __phpenv_versions + case doctor + __phpenv_doctor + case config + __phpenv_config $phpenv_args + case extensions ext + __phpenv_extensions $phpenv_args + case help -h --help "" + __phpenv_help + case '*' + echo "phpenv: unknown command '$phpenv_cmd'" + echo "Run 'phpenv help' for available commands." + return 1 + end +end + +function __phpenv_current + set -l phpenv_version (__phpenv_detect_version) + if test -n "$phpenv_version" + echo "$phpenv_version" + else + echo "No PHP version set" + return 1 + end +end + +function __phpenv_detect_version + set -l phpenv_version_file (__phpenv_find_version_file .php-version) + if test -n "$phpenv_version_file" + set -l phpenv_version (string trim < $phpenv_version_file) + if test -n "$phpenv_version" + echo $phpenv_version + return + end + end + + set -l phpenv_tool_version_file (__phpenv_find_version_file .tool-version) + if test -n "$phpenv_tool_version_file" + set -l phpenv_version (__phpenv_parse_tool_version $phpenv_tool_version_file) + if test -n "$phpenv_version" + echo $phpenv_version + return + end + end + + if test -f composer.json + set -l phpenv_version (__phpenv_parse_composer_version) + if test -n "$phpenv_version" + echo $phpenv_version + return + end + end + + if test -n "$PHPENV_GLOBAL_VERSION" + echo $PHPENV_GLOBAL_VERSION + return + end + + set -l phpenv_global_version (__phpenv_config_get global-version) + if test -n "$phpenv_global_version" + echo $phpenv_global_version + return + end + + if command -q php + php -r "echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;" 2>/dev/null + end +end + +function __phpenv_find_version_file -a phpenv_filename + set -l phpenv_dir (pwd) + while test "$phpenv_dir" != "/" + if test -f "$phpenv_dir/$phpenv_filename" + echo "$phpenv_dir/$phpenv_filename" + return + end + set phpenv_dir (dirname $phpenv_dir) + end +end + +function __phpenv_parse_tool_version -a phpenv_file + if test -f $phpenv_file + set -l phpenv_line (grep "^php " $phpenv_file) + if test -n "$phpenv_line" + set -l phpenv_parts (string split ' ' $phpenv_line) + if test (count $phpenv_parts) -ge 2 + echo $phpenv_parts[2] | sed 's/^v//' + end + end + end +end + +function __phpenv_parse_composer_version + if not test -f composer.json + return + end + + set -l phpenv_platform_php (jq -r '.config.platform.php // empty' composer.json 2>/dev/null) + if test $status -eq 0 -a -n "$phpenv_platform_php" -a "$phpenv_platform_php" != "null" + echo $phpenv_platform_php + return + end + + set -l phpenv_require_php (jq -r '.require.php // empty' composer.json 2>/dev/null) + if test $status -eq 0 -a -n "$phpenv_require_php" -a "$phpenv_require_php" != "null" + __phpenv_parse_semver_constraint $phpenv_require_php + return + end +end + +function __phpenv_parse_semver_constraint -a phpenv_constraint + set phpenv_constraint (echo $phpenv_constraint | tr -d ' "') + + set -l phpenv_latest_8x (__phpenv_parse_version_field "8.x" "8.4") + set -l phpenv_latest_7x (__phpenv_parse_version_field "7.x" "7.4") + set -l phpenv_latest (__phpenv_parse_version_field "latest" "8.4") + + switch $phpenv_constraint + case '^8.*' + echo $phpenv_latest_8x + case '^7.*' + echo $phpenv_latest_7x + case '~8.4*' + echo "8.4" + case '~8.3*' + echo "8.3" + case '~8.2*' + echo "8.2" + case '~8.1*' + echo "8.1" + case '~8.0*' + echo "8.0" + case '~7.4*' + echo "7.4" + case '>=8.1' + echo $phpenv_latest_8x + case '>=8.0' + echo $phpenv_latest_8x + case '>=7.4' + echo $phpenv_latest_8x + case '8.*' '8.x.*' + echo $phpenv_latest_8x + case '7.*' '7.x.*' + echo $phpenv_latest_7x + case '5.*' '5.x.*' + echo "5.6" + case '*' + if echo $phpenv_constraint | grep -q '[0-9]\+\.[0-9]\+' + echo $phpenv_constraint | sed 's/[^0-9\.]//g' | cut -d. -f1,2 + else + echo $phpenv_latest + end + end +end + +# Cache version info to avoid repeated API calls +set -g __phpenv_version_cache +set -g __phpenv_version_cache_time 0 + +function __phpenv_get_version_info + set -l current_time (date +%s) + set -l cache_duration 300 # 5 minutes + + # Return cached version if still valid + if test -n "$__phpenv_version_cache" + set -l cache_age (math $current_time - $__phpenv_version_cache_time) + if test $cache_age -lt $cache_duration + echo $__phpenv_version_cache + return + end + end + + if command -q curl + set -l url https://raw.githubusercontent.com/shivammathur/setup-php/refs/heads/main + set -l version_data (curl -s "$url/src/configs/php-versions.json" 2>/dev/null) + if test -n "$version_data" + set -g __phpenv_version_cache $version_data + set -g __phpenv_version_cache_time $current_time + echo $version_data + end + end +end + +# Cache cellar path as it doesn't change +set -g __phpenv_cellar_cache + +function __phpenv_get_cellar_path + if test -n "$__phpenv_cellar_cache" + echo $__phpenv_cellar_cache + return + end + + if test -d /opt/homebrew/Cellar + set -g __phpenv_cellar_cache /opt/homebrew/Cellar + else if test -d /usr/local/Cellar + set -g __phpenv_cellar_cache /usr/local/Cellar + else + set -g __phpenv_cellar_cache "" + end + + echo $__phpenv_cellar_cache +end + +function __phpenv_ensure_taps + if not command -q brew + return 1 + end + + # Add taps if not already added + if not brew tap | grep -q shivammathur/php 2>/dev/null + brew tap shivammathur/php 2>/dev/null + end + if not brew tap | grep -q shivammathur/extensions 2>/dev/null + brew tap shivammathur/extensions 2>/dev/null + end +end + +function __phpenv_parse_version_field -a field fallback + set -l version_info (__phpenv_get_version_info) + if test -n "$version_info" + echo $version_info | jq -r ".$field // \"$fallback\"" 2>/dev/null + else + echo $fallback + end +end + +function __phpenv_list_installed + set -l phpenv_versions + set -l phpenv_cellar_path (__phpenv_get_cellar_path) + + if test -d $phpenv_cellar_path + for phpenv_dir in $phpenv_cellar_path/php@* $phpenv_cellar_path/php + if test -d $phpenv_dir + set -l phpenv_basename (basename $phpenv_dir) + + if echo $phpenv_basename | grep -qE '(debug|zts)' + continue + end + + if test "$phpenv_basename" = "php" + set -l phpenv_latest (__phpenv_parse_version_field "latest" "8.4") + set -a phpenv_versions $phpenv_latest + else if echo $phpenv_basename | grep -qE '^php@[0-9]+\.[0-9]+$' + set -l phpenv_version (echo $phpenv_basename | sed 's/php@//') + set -a phpenv_versions $phpenv_version + end + end + end + end + + printf '%s\n' $phpenv_versions | sort -V | uniq +end + +function __phpenv_resolve_version_alias -a phpenv_version + switch $phpenv_version + case latest + __phpenv_parse_version_field "latest" "8.4" + case nightly + __phpenv_parse_version_field "nightly" "8.5" + case '8.x' + __phpenv_parse_version_field "8.x" "8.4" + case '7.x' + __phpenv_parse_version_field "7.x" "7.4" + case '5.x' + __phpenv_parse_version_field "5.x" "5.6" + case '*' + echo $phpenv_version + end +end + +function __phpenv_get_formula_name -a phpenv_version + set -l phpenv_latest_version (__phpenv_parse_version_field "latest" "8.4") + + if test "$phpenv_version" = "$phpenv_latest_version" + echo "shivammathur/php/php" + else + echo "shivammathur/php/php@$phpenv_version" + end +end + +function __phpenv_is_version_installed -a phpenv_version + set -l phpenv_cellar_path (__phpenv_get_cellar_path) + set -l phpenv_latest_version (__phpenv_parse_version_field "latest" "8.4") + + if test "$phpenv_version" = "$phpenv_latest_version" + test -d "$phpenv_cellar_path/php" -o -d "$phpenv_cellar_path/php@$phpenv_version" + else + test -d "$phpenv_cellar_path/php@$phpenv_version" + end +end + +function __phpenv_get_php_path -a phpenv_version + set -l phpenv_cellar_path (__phpenv_get_cellar_path) + set -l phpenv_latest_version (__phpenv_parse_version_field "latest" "8.4") + + set -l phpenv_target_dir + if test "$phpenv_version" = "$phpenv_latest_version" + if test -d "$phpenv_cellar_path/php" + set phpenv_target_dir "$phpenv_cellar_path/php" + else if test -d "$phpenv_cellar_path/php@$phpenv_version" + set phpenv_target_dir "$phpenv_cellar_path/php@$phpenv_version" + end + else + if test -d "$phpenv_cellar_path/php@$phpenv_version" + set phpenv_target_dir "$phpenv_cellar_path/php@$phpenv_version" + end + end + + if test -n "$phpenv_target_dir" + # Find the latest version directory by sorting + set -l phpenv_versions + for phpenv_dir in $phpenv_target_dir/* + if test -d "$phpenv_dir" + set -a phpenv_versions (basename "$phpenv_dir") + end + end + + if test (count $phpenv_versions) -gt 0 + set -l phpenv_latest_dir (printf '%s\n' $phpenv_versions | sort -V | tail -1) + if test -n "$phpenv_latest_dir" + echo "$phpenv_target_dir/$phpenv_latest_dir" + end + end + end +end + +function __phpenv_set_php_path -a phpenv_version + set -l phpenv_php_path (__phpenv_get_php_path $phpenv_version) + if test -z "$phpenv_php_path" + echo "Failed to locate PHP $phpenv_version installation path" >&2 + return 1 + end + + if not test -x "$phpenv_php_path/bin/php" + echo "PHP binary not found at $phpenv_php_path/bin/php" >&2 + return 1 + end + + # Store original PATH if not already stored + if not set -q PHPENV_ORIGINAL_PATH + set -g PHPENV_ORIGINAL_PATH $PATH + end + + # Check if we're already using this version + if set -q PHPENV_CURRENT_VERSION; and test "$PHPENV_CURRENT_VERSION" = "$phpenv_version" + return 0 + end + + # Build clean PATH without any PHP paths + set -l phpenv_clean_path + for phpenv_path_entry in $PHPENV_ORIGINAL_PATH + if not echo $phpenv_path_entry | grep -qE "/(Cellar|opt/homebrew)/(php|php@)" + set -a phpenv_clean_path $phpenv_path_entry + end + end + + # Set new PATH with PHP version at front + set -gx PATH "$phpenv_php_path/bin" $phpenv_clean_path + set -g PHPENV_CURRENT_VERSION $phpenv_version + set -g PHPENV_CURRENT_PATH "$phpenv_php_path/bin" + + # Verify the change worked + if command -q php + set -l phpenv_active_version (php -r "echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;" 2>/dev/null) + if test "$phpenv_active_version" != "$phpenv_version" + echo "Warning: PHP $phpenv_version set in PATH but php command shows $phpenv_active_version" >&2 + echo "PATH may have conflicting PHP installations" >&2 + end + end +end + +function __phpenv_restore_system_path + if set -q PHPENV_ORIGINAL_PATH + set -gx PATH $PHPENV_ORIGINAL_PATH + set -e PHPENV_CURRENT_VERSION + set -e PHPENV_CURRENT_PATH + set -e PHPENV_ORIGINAL_PATH + return 0 + else + echo "No original PATH stored to restore" + return 1 + end +end + +function __phpenv_install -a phpenv_version + if test -z "$phpenv_version" + echo "Usage: phpenv install " + return 1 + end + + set phpenv_version (__phpenv_resolve_version_alias $phpenv_version) + + if __phpenv_is_version_installed $phpenv_version + echo "PHP $phpenv_version is already installed" + return 0 + end + + echo "Installing PHP $phpenv_version..." + + if not __phpenv_ensure_taps + echo "Error: Homebrew is required but not available" + return 1 + end + + set -l phpenv_formula (__phpenv_get_formula_name $phpenv_version) + if test -z "$phpenv_formula" + echo "Unknown PHP version: $phpenv_version" + echo "Run 'phpenv versions' to see available versions" + return 1 + end + + if brew install $phpenv_formula + echo "PHP $phpenv_version installed successfully" + + if __phpenv_config_get auto-install-extensions | grep -q true + __phpenv_install_default_extensions $phpenv_version + end + else + echo "Failed to install PHP $phpenv_version" + return 1 + end +end + +function __phpenv_uninstall -a phpenv_version + if test -z "$phpenv_version" + echo "Usage: phpenv uninstall " + return 1 + end + + if not __phpenv_is_version_installed $phpenv_version + echo "PHP $phpenv_version is not installed" + return 1 + end + + set -l phpenv_formula (__phpenv_get_formula_name $phpenv_version) + if brew uninstall $phpenv_formula + echo "PHP $phpenv_version uninstalled successfully" + else + echo "Failed to uninstall PHP $phpenv_version" + return 1 + end +end + +function __phpenv_use + set -l phpenv_version $argv[1] + + # Handle special case: restore system PHP + if test "$phpenv_version" = "system" + __phpenv_restore_system_path + echo "Restored system PHP" + return 0 + end + + if test -z "$phpenv_version" + set phpenv_version (__phpenv_detect_version) + if test -z "$phpenv_version" + echo "No PHP version found for this project" + echo "Usage: phpenv use " + return 1 + end + echo "Detected PHP $phpenv_version for this project" + end + + if not __phpenv_is_version_installed $phpenv_version + if test "$(__phpenv_config_get auto-install)" = "true" + __phpenv_install $phpenv_version + else + echo "PHP $phpenv_version is not installed. Install with: phpenv install $phpenv_version" + return 1 + end + end + + if __phpenv_set_php_path $phpenv_version + echo "Using PHP $phpenv_version" + else + echo "Failed to switch to PHP $phpenv_version" + return 1 + end +end + +function __phpenv_local -a phpenv_version + if test -z "$phpenv_version" + echo "Usage: phpenv local " + return 1 + end + + echo $phpenv_version > .php-version + echo "Set local PHP version to $phpenv_version" +end + +function __phpenv_global -a phpenv_version + if test -z "$phpenv_version" + echo "Usage: phpenv global " + return 1 + end + + set -U PHPENV_GLOBAL_VERSION $phpenv_version + echo "Set global PHP version to $phpenv_version" +end + +function __phpenv_list + set -l phpenv_versions (__phpenv_list_installed) + set -l phpenv_current (__phpenv_detect_version) + + for phpenv_version in $phpenv_versions + if test "$phpenv_version" = "$phpenv_current" + echo "* $phpenv_version" + else + echo " $phpenv_version" + end + end +end + +function __phpenv_versions + echo "Available versions from shivammathur/homebrew-php:" + + set -l phpenv_tap_versions (__phpenv_get_tap_versions) + if test -n "$phpenv_tap_versions" + echo $phpenv_tap_versions + return + end + + # Use cached version info + set -l phpenv_latest (__phpenv_parse_version_field "latest" "8.4") + set -l phpenv_nightly (__phpenv_parse_version_field "nightly" "8.5") + set -l phpenv_version_8x (__phpenv_parse_version_field "8.x" "8.4") + set -l phpenv_version_7x (__phpenv_parse_version_field "7.x" "7.4") + set -l phpenv_version_5x (__phpenv_parse_version_field "5.x" "5.6") + + echo "Stable versions:" + echo " $phpenv_version_5x (5.x latest) $phpenv_version_7x (7.x latest) $phpenv_latest (latest stable)" + echo " $phpenv_nightly (nightly)" + echo " 5.6 7.0 7.1 7.2 7.3 7.4" + echo " 8.0 8.1 8.2 8.3 8.4 8.5" +end + +function __phpenv_get_tap_versions + if not command -q brew + return + end + + set -l phpenv_formulas (brew tap-info shivammathur/php --json 2>/dev/null | \ + jq -r '.[]|(.formula_names[]?)' 2>/dev/null) + + if test -z "$phpenv_formulas" + return + end + + set -l phpenv_versions + set -l phpenv_version_info (__phpenv_get_version_info) + set -l phpenv_latest_version (echo $phpenv_version_info | jq -r '.latest // "8.4"' 2>/dev/null) + + for phpenv_formula in $phpenv_formulas + set -l phpenv_clean_name (echo $phpenv_formula | sed 's|shivammathur/php/||') + + if echo $phpenv_clean_name | grep -qE '(debug|zts|autoconf|bison)' + continue + end + + if test "$phpenv_clean_name" = "php" + set -a phpenv_versions "$phpenv_latest_version (latest)" + else if echo $phpenv_clean_name | grep -qE '^php@[0-9]+\.[0-9]+$' + set -l phpenv_version (echo $phpenv_clean_name | sed 's/php@//') + set -a phpenv_versions $phpenv_version + end + end + + if test (count $phpenv_versions) -gt 0 + printf '%s\n' $phpenv_versions | sort -V | tr '\n' ' ' | sed 's/ $//' + echo "" + end +end + +function __phpenv_which -a phpenv_binary + set -l phpenv_binary (test -n "$phpenv_binary"; and echo $phpenv_binary; or echo "php") + set -l phpenv_version (__phpenv_detect_version) + + if test -n "$phpenv_version" + set -l phpenv_php_path (__phpenv_get_php_path $phpenv_version) + if test -x "$phpenv_php_path/bin/$phpenv_binary" + echo "$phpenv_php_path/bin/$phpenv_binary" + else + echo "$phpenv_binary not found for PHP $phpenv_version" + return 1 + end + else + which $phpenv_binary + end +end + +function __phpenv_doctor + echo "phpenv doctor" + echo "=============" + + if command -q jq + echo "✓ jq is installed" + else + echo "✗ jq is not installed (required)" + end + + if command -q brew + echo "✓ Homebrew is installed" + else + echo "✗ Homebrew is not installed" + return 1 + end + + # Check taps using unified function + set -l tap_status (__phpenv_ensure_taps 2>/dev/null; echo $status) + if test $tap_status -eq 0 + echo "✓ Required Homebrew taps are available" + else + echo "! Some Homebrew taps may need to be added automatically" + end + + set -l phpenv_versions (__phpenv_list_installed) + if test (count $phpenv_versions) -gt 0 + echo "✓ PHP versions installed: "(string join ", " $phpenv_versions) + else + echo "! No PHP versions installed" + end + + set -l phpenv_current (__phpenv_detect_version) + if test -n "$phpenv_current" + echo "✓ Current PHP version: $phpenv_current" + else + echo "! No PHP version detected" + end +end + +function __phpenv_config -a phpenv_action phpenv_key phpenv_value + switch $phpenv_action + case get + __phpenv_config_get $phpenv_key + case set + __phpenv_config_set $phpenv_key $phpenv_value + case list + __phpenv_config_list + case '*' + echo "Usage: phpenv config {get|set|list} [key] [value]" + return 1 + end +end + +function __phpenv_config_get -a phpenv_key + set -l phpenv_value + set -l phpenv_source + + switch $phpenv_key + case global-version + if test -n "$PHPENV_GLOBAL_VERSION" + set phpenv_value $PHPENV_GLOBAL_VERSION + set phpenv_source "fish universal variable" + end + case auto-install + if test -n "$PHPENV_AUTO_INSTALL" + set phpenv_value $PHPENV_AUTO_INSTALL + set phpenv_source "fish universal variable" + end + case auto-install-extensions + if test -n "$PHPENV_AUTO_INSTALL_EXTENSIONS" + set phpenv_value $PHPENV_AUTO_INSTALL_EXTENSIONS + set phpenv_source "fish universal variable" + end + case auto-switch + if test -n "$PHPENV_AUTO_SWITCH" + set phpenv_value $PHPENV_AUTO_SWITCH + set phpenv_source "fish universal variable" + end + case default-extensions + if test -n "$PHPENV_DEFAULT_EXTENSIONS" + set phpenv_value $PHPENV_DEFAULT_EXTENSIONS + set phpenv_source "fish universal variable" + end + end + + if test -z "$phpenv_value" + for phpenv_config_file in ~/.config/fish/conf.d/phpenv.fish ~/.config/phpenv/config ~/.phpenv.fish + if test -f $phpenv_config_file + set -l phpenv_file_value (grep "^$phpenv_key=" $phpenv_config_file | cut -d= -f2- | head -1) + if test -n "$phpenv_file_value" + set phpenv_value $phpenv_file_value + set phpenv_source $phpenv_config_file + break + end + end + end + end + + if test "$argv[2]" = "--verbose" + if test -n "$phpenv_value" + echo "$phpenv_key = $phpenv_value (from $phpenv_source)" + else + echo "$phpenv_key = (not set)" + end + else + echo $phpenv_value + end +end + +function __phpenv_config_set -a phpenv_key phpenv_value + if test -z "$phpenv_value" + echo "Usage: phpenv config set " + return 1 + end + + switch $phpenv_key + case global-version + if __phpenv_validate_version $phpenv_value + set -U PHPENV_GLOBAL_VERSION $phpenv_value + else + echo "Invalid PHP version: $phpenv_value" + echo "Run 'phpenv versions' to see available versions" + return 1 + end + case auto-install + if __phpenv_validate_boolean $phpenv_value + set -g PHPENV_AUTO_INSTALL $phpenv_value + else + echo "Invalid value for auto-install. Use 'true' or 'false'" + return 1 + end + case auto-install-extensions + if __phpenv_validate_boolean $phpenv_value + set -g PHPENV_AUTO_INSTALL_EXTENSIONS $phpenv_value + else + echo "Invalid value for auto-install-extensions. Use 'true' or 'false'" + return 1 + end + case auto-switch + if __phpenv_validate_boolean $phpenv_value + set -g PHPENV_AUTO_SWITCH $phpenv_value + else + echo "Invalid value for auto-switch. Use 'true' or 'false'" + return 1 + end + case default-extensions + if __phpenv_validate_extensions $phpenv_value + set -g PHPENV_DEFAULT_EXTENSIONS $phpenv_value + else + echo "Warning: Some extensions may not be available for all PHP versions" + set -g PHPENV_DEFAULT_EXTENSIONS $phpenv_value + end + case '*' + echo "Unknown config key: $phpenv_key" + echo "Available keys: global-version, auto-install, auto-install-extensions, auto-switch," + echo " default-extensions" + return 1 + end + echo "Set $phpenv_key = $phpenv_value" +end + +function __phpenv_config_list + echo "Configuration (showing sources):" + __phpenv_config_get global-version --verbose + __phpenv_config_get auto-install --verbose + __phpenv_config_get auto-install-extensions --verbose + __phpenv_config_get auto-switch --verbose + __phpenv_config_get default-extensions --verbose +end + +function __phpenv_extensions -a phpenv_action phpenv_extension + switch $phpenv_action + case install + __phpenv_extensions_install $phpenv_extension + case uninstall remove + __phpenv_extensions_uninstall $phpenv_extension + case list ls + __phpenv_extensions_list + case available + __phpenv_extensions_available + case '*' + echo "Usage: phpenv extensions {install|uninstall|list|available} [extension]" + return 1 + end +end + +function __phpenv_extensions_install -a phpenv_extension + if test -z "$phpenv_extension" + echo "Usage: phpenv extensions install " + return 1 + end + + # Check for version override first (from environment, not global variable) + set -l phpenv_version + if test -n "$PHPENV_VERSION_OVERRIDE" + set phpenv_version $PHPENV_VERSION_OVERRIDE + else + set phpenv_version (__phpenv_detect_version) + end + if test -z "$phpenv_version" + echo "No PHP version detected" + return 1 + end + + if not __phpenv_extension_available $phpenv_extension $phpenv_version + echo "Extension $phpenv_extension may not be available for PHP $phpenv_version" + echo "Attempting installation anyway..." + end + + echo "Installing $phpenv_extension for PHP $phpenv_version..." + + if not __phpenv_ensure_taps + echo "Error: Homebrew is required but not available" + return 1 + end + + set -l phpenv_formula "shivammathur/extensions/$phpenv_extension@$phpenv_version" + if brew install $phpenv_formula + echo "$phpenv_extension@$phpenv_version installed successfully" + else + echo "Failed to install $phpenv_extension@$phpenv_version" + echo "Extension may not be available for PHP $phpenv_version" + echo "Run 'phpenv extensions available' to see available extensions" + return 1 + end +end + +function __phpenv_extensions_uninstall -a phpenv_extension + if test -z "$phpenv_extension" + echo "Usage: phpenv extensions uninstall " + return 1 + end + + set -l phpenv_version (__phpenv_detect_version) + if test -z "$phpenv_version" + echo "No PHP version detected" + return 1 + end + + set -l phpenv_formula "shivammathur/extensions/$phpenv_extension@$phpenv_version" + if brew uninstall $phpenv_formula + echo "$phpenv_extension@$phpenv_version uninstalled successfully" + else + echo "Failed to uninstall $phpenv_extension@$phpenv_version" + return 1 + end +end + +function __phpenv_get_available_extensions + if not command -q brew + return 1 + end + + brew tap-info shivammathur/extensions --json 2>/dev/null | \ + jq -r '.[]|(.formula_names[]?)' 2>/dev/null +end + +function __phpenv_extension_available -a phpenv_extension phpenv_version + set -l phpenv_available_extensions (__phpenv_get_available_extensions) + + if test -z "$phpenv_available_extensions" + return 0 # Assume available if can't check + end + + for phpenv_ext_formula in $phpenv_available_extensions + if test "$phpenv_ext_formula" = "shivammathur/extensions/$phpenv_extension@$phpenv_version" + return 0 + end + end + + return 1 +end + +function __phpenv_extensions_available + set -l phpenv_version (__phpenv_detect_version) + if test -z "$phpenv_version" + set phpenv_version "8.3" + end + + echo "Available extensions for PHP $phpenv_version:" + + set -l phpenv_available_extensions (__phpenv_get_available_extensions) + + if test -n "$phpenv_available_extensions" + set -l phpenv_version_extensions + for phpenv_ext_formula in $phpenv_available_extensions + if echo $phpenv_ext_formula | grep -q "@$phpenv_version\$" + set -l phpenv_ext_name (echo $phpenv_ext_formula | \ + sed "s|shivammathur/extensions/||" | sed "s|@$phpenv_version||") + set -a phpenv_version_extensions $phpenv_ext_name + end + end + + if test (count $phpenv_version_extensions) -gt 0 + printf ' %s\n' $phpenv_version_extensions | sort + else + echo " No extensions found for PHP $phpenv_version" + end + else + echo " Unable to fetch extension list or Homebrew not available" + end +end + +function __phpenv_extensions_list + set -l phpenv_version (__phpenv_detect_version) + if test -z "$phpenv_version" + echo "No PHP version detected" + return 1 + end + + set -l phpenv_cellar_path (__phpenv_get_cellar_path) + echo "Extensions for PHP $phpenv_version:" + + if test -d $phpenv_cellar_path + for phpenv_ext_dir in $phpenv_cellar_path/*@$phpenv_version + if test -d $phpenv_ext_dir + set -l phpenv_ext_name (basename $phpenv_ext_dir | sed "s/@$phpenv_version//") + if test "$phpenv_ext_name" != "php" + echo " $phpenv_ext_name" + end + end + end + end +end + +function __phpenv_install_default_extensions -a phpenv_version + set -l phpenv_extensions (__phpenv_config_get default-extensions) + if test -n "$phpenv_extensions" + echo "Installing default extensions for PHP $phpenv_version..." + set -l phpenv_failed_extensions + + for phpenv_ext in (echo $phpenv_extensions | tr ' ' '\n') + if test -n "$phpenv_ext" + echo "Installing $phpenv_ext..." + # Temporarily switch context using local variable + set -l phpenv_saved_version (__phpenv_detect_version) + set -l PHPENV_VERSION_OVERRIDE $phpenv_version + if not env PHPENV_VERSION_OVERRIDE=$phpenv_version __phpenv_extensions_install $phpenv_ext + set -a phpenv_failed_extensions $phpenv_ext + echo "Warning: Failed to install $phpenv_ext for PHP $phpenv_version" + end + end + end + + if test (count $phpenv_failed_extensions) -gt 0 + echo "Some extensions failed to install: "(string join ", " $phpenv_failed_extensions) + echo "You can install them manually later with: phpenv extensions install " + else + echo "All default extensions installed successfully" + end + end +end + +function __phpenv_auto_switch --on-variable PWD + # Debouncing: skip if we just switched directories recently + set -l phpenv_current_time (date +%s) + if set -q PHPENV_LAST_SWITCH_TIME + set -l phpenv_time_diff (math $phpenv_current_time - $PHPENV_LAST_SWITCH_TIME) + if test $phpenv_time_diff -lt 1 + return 0 + end + end + + set -l phpenv_auto_switch (__phpenv_config_get auto-switch) + if test "$phpenv_auto_switch" = "false" + return 0 + end + + if not functions -q __phpenv_detect_version __phpenv_set_php_path + return 0 + end + + set -l phpenv_new_version (__phpenv_detect_version 2>/dev/null) + if test -z "$phpenv_new_version" + return 0 + end + + # Check if we're already using the correct version + if set -q PHPENV_CURRENT_VERSION; and test "$PHPENV_CURRENT_VERSION" = "$phpenv_new_version" + return 0 + end + + if __phpenv_is_version_installed "$phpenv_new_version" 2>/dev/null + __phpenv_set_php_path "$phpenv_new_version" 2>/dev/null + set -g PHPENV_LAST_SWITCH_TIME $phpenv_current_time + else + set -l phpenv_auto_install (__phpenv_config_get auto-install) + if test "$phpenv_auto_install" = "true" + echo "Auto-installing PHP $phpenv_new_version..." + if phpenv install "$phpenv_new_version" + set -g PHPENV_LAST_SWITCH_TIME $phpenv_current_time + end + end + end +end + +function __phpenv_help + echo "phpenv - PHP Version Manager for Fish Shell" + echo "" + echo "Usage: phpenv [args]" + echo "" + echo "Commands:" + echo " install Install a PHP version" + echo " uninstall Uninstall a PHP version" + echo " use [version|system] Use PHP version for current shell (auto-detects if no version)" + echo " 'system' restores original PATH" + echo " local Set PHP version for current project" + echo " global Set global PHP version" + echo " list List installed PHP versions" + echo " current Show current PHP version" + echo " which [binary] Show path to PHP binary" + echo " versions Show all available versions" + echo " doctor Check phpenv installation" + echo " config Manage configuration" + echo " extensions Manage PHP extensions" + echo " help Show this help" + echo "" + echo "Version sources (in order of priority):" + echo " 1. .php-version file" + echo " 2. .tool-version file" + echo " 3. composer.json" + echo " 4. Global version" + echo " 5. System PHP" + echo "" + echo "Configuration:" + echo " auto-switch: Enable automatic PHP version switching (default: true)" + echo " auto-install: Auto-install missing versions (default: false)" + echo "" + echo "For more information, visit:" + echo " https://github.com/ivuorinen/phpenv.fish" +end + +function __phpenv_validate_boolean -a phpenv_value + test "$phpenv_value" = "true" -o "$phpenv_value" = "false" +end + +function __phpenv_validate_version -a phpenv_version + if echo $phpenv_version | grep -qE '^[0-9]+\.[0-9]+$' + return 0 + end + + switch $phpenv_version + case latest nightly '5.x' '7.x' '8.x' + return 0 + case '*' + return 1 + end +end + +function __phpenv_validate_extensions -a phpenv_extensions_string + if echo $phpenv_extensions_string | grep -qE '^[a-zA-Z0-9_-]+( +[a-zA-Z0-9_-]+)*$' + return 0 + else + return 1 + end +end +