Repository: koxudaxi/poetry-pycharm-plugin
Branch: master
Commit: 51b33be1c68a
Files: 57
Total size: 142.3 KB
Directory structure:
gitextract_e_ytidwl/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── CI.yml
│ ├── docs.yml
│ └── publish.yml
├── .gitignore
├── LICENSE
├── NOTICE.txt
├── README.md
├── build.gradle
├── docs/
│ ├── development.md
│ ├── index.md
│ └── install.md
├── gradle/
│ └── wrapper/
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── mkdocs.yml
├── requirements.txt
├── resources/
│ ├── META-INF/
│ │ ├── only-toml.xml
│ │ └── plugin.xml
│ └── inspectionDescriptions/
│ └── PoetryPackageVersion.html
├── scripts/
│ └── build_changelog.py
├── settings.gradle
├── src/
│ └── com/
│ └── koxudaxi/
│ └── poetry/
│ ├── PoetryConfigLoader.kt
│ ├── PoetryConfigService.kt
│ ├── PoetryExtrasLineMarkerContributor.kt
│ ├── PoetryInstallExtras.kt
│ ├── PoetryRunScript.kt
│ ├── PoetryScriptsLineMarkerContributor.kt
│ ├── PoetrySdkProvider.kt
│ ├── PoetryVersionInspection.kt
│ ├── PyAddExistingPoetryEnvPanel.kt
│ ├── PyAddNewPoetryFromFilePanel.kt
│ ├── PyAddNewPoetryPanel.kt
│ ├── PyAddPoetrySdkProvider.kt
│ ├── PyPoetryPackageManagementService.kt
│ ├── PyPoetryPackageManager.kt
│ ├── PyPoetryPackageManagerProvider.kt
│ ├── PyPoetrySdkAdditionalData.kt
│ ├── PyPoetrySdkConfiguration.kt
│ ├── PyPoetrySdkFlavor.kt
│ ├── PyPoetrySdkFlavorProvider.kt
│ └── poetry.kt
├── testData/
│ ├── Poetry/
│ │ ├── getPyProjectTomlForPoetry/
│ │ │ └── pyproject.toml
│ │ ├── getPyProjectTomlForPoetryBroken/
│ │ │ └── pyproject.toml
│ │ ├── getPyProjectTomlForPoetryInvalid/
│ │ │ └── pyproject.toml
│ │ └── parsePoetryShoOutdated/
│ │ └── show-outdated.txt
│ └── PyPoetryPackageManager/
│ ├── parsePoetryInstallDryRun1_0/
│ │ └── dry-run-result.txt
│ └── parsePoetryInstallDryRun1_1/
│ └── dry-run-result.txt
└── testSrc/
└── com/
├── jetbrains/
│ └── python/
│ └── fixtures/
│ ├── PythonMockSdk.java
│ └── PythonTestUtil.java
└── koxudaxi/
└── poetry/
├── PoetryTest.kt
├── PoetryTestCase.kt
└── PyPoetryPackageManagerTest.kt
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [koxudaxi]
custom: ['https://stakes.social/0x87E30B7640ac3949e1594C8231304435Edb9B560']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
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.
Also, gif movies are recommended.
**Environments (please complete the following information):**
- IDE: [e.g. PyCharm Community 2020.1 ]
- OS: [e.g. macOS 10.15.5 ]
- Poetry Version [e.g. 1.0.8 ]
- Plugin version [e.g. 0.0.10 ]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
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.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: gradle
directory: "/"
schedule:
interval: daily
time: "10:00"
timezone: Asia/Tokyo
open-pull-requests-limit: 10
ignore:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
versions:
- 1.4.30
- 1.4.31
- 1.4.32
- 1.5.0
- dependency-name: org.jetbrains.intellij
versions:
- 0.7.0
- 0.7.1
- 0.7.2
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
time: "10:00"
timezone: Asia/Tokyo
open-pull-requests-limit: 10
ignore:
- dependency-name: mkdocs-material
versions:
- 6.2.6
- 7.0.2
- 7.0.3
- 7.0.7
- 7.1.2
================================================
FILE: .github/workflows/CI.yml
================================================
name: CI
on:
pull_request: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cache
uses: actions/cache@v1.1.2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.cfg') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache
uses: actions/cache@v1.1.2
with:
path: ~/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('**/build.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: test
run: ./gradlew buildPlugin test jacocoTestReport
- name: IntelliJ Platform Plugin Verifier
uses: ChrisCarini/intellij-platform-plugin-verifier-action@v0.0.2
with:
ide-versions: |
pycharmPC:2020.1
pycharmPC:LATEST-EAP-SNAPSHOT
- name: setup python
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: build document
run: |
python scripts/build_changelog.py
mkdocs build --verbose --clean --strict
# - name: Upload coverage to Codecov
# uses: codecov/codecov-action@v1
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# file: ./build/reports/jacoco/test/jacocoTestReport.xml
# flags: unittests
# fail_ci_if_error: true
================================================
FILE: .github/workflows/docs.yml
================================================
name: Docs
on:
push:
branches:
- master
jobs:
build-deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- name: Setup Python
uses: actions/setup-python@v1
with:
python-version: '3.8'
architecture: 'x64'
- uses: actions/cache@v1
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
- name: Install dependencies
run: |
pip install -r requirements.txt
- run: |
python scripts/build_changelog.py
mkdocs build --verbose --clean --strict
- name: Deploy
uses: peaceiris/actions-gh-pages@v2
env:
PERSONAL_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
PUBLISH_BRANCH: gh-pages
PUBLISH_DIR: ./site
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish
on:
push:
tags:
- '**'
jobs:
build-n-publish:
name: Build and publish plugin 📦 to JetBrains merket
if: "success() && startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v1
with:
python-version: '3.8'
architecture: 'x64'
- uses: actions/cache@v1
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: build changelog
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
run: |
python scripts/build_changelog.py
CHANGELOG_LATEST=$(cat docs/changelog_latest.md)
CHANGELOG_LATEST="${CHANGELOG_LATEST//'%'/'%25'}"
CHANGELOG_LATEST="${CHANGELOG_LATEST//$'\n'/'%0A'}"
CHANGELOG_LATEST="${CHANGELOG_LATEST//$'\r'/'%0D'}"
echo ::set-env name=CHANGELOG_LATEST::$CHANGELOG_LATEST
- name: Cache
uses: actions/cache@v1.1.2
with:
path: ~/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('**/build.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: test
run: ./gradlew buildPlugin test jacocoTestReport
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
${{ env.CHANGELOG_LATEST }}
draft: false
prerelease: false
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./build/distributions/poetry-pycharm-plugin.zip
asset_name: poetry-pycharm-plugin.zip
asset_content_type: application/zip
- name: Publish a plugin
run: |
curl --fail --header "Authorization: Bearer ${{secrets.JETBRAINS_TOKEN}}" \
-F pluginId=14307 -F file=@build/distributions/poetry-pycharm-plugin.zip \
https://plugins.jetbrains.com/plugin/uploadPlugin
================================================
FILE: .gitignore
================================================
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
.idea/workspace.xml
.idea/modules.xml
.idea/compiler.xml
.idea/misc.xml
.idea/gradle.xml
.idea/shelf/
.idea/libraries/
.idea/dictionaries
.idea/.name
*.iml
/build/
/jps-plugin/build/
.gradle
/out/
/poetry-pycahrm-plugin/out/
/venv/
/site
/docs/changelog.md
/docs/changelog_latest.md
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: NOTICE.txt
================================================
This software includes code from IntelliJ IDEA Community Edition
Copyright (C) JetBrains s.r.o.
https://www.jetbrains.com/idea/
================================================
FILE: README.md
================================================
# Poetry PyCharm Plugin

[](https://plugins.jetbrains.com/plugin/14307-poetry)



[A JetBrains PyCharm plugin](https://plugins.jetbrains.com/plugin/14307-poetry) for [`poetry`](https://python-poetry.org/).
# [This plugin has been merged into PyCharm source code🎉](https://www.jetbrains.com/pycharm/whatsnew/)
PyCharm 2021.3 or later provide poetry integration as a built-in feature😎
👉[What’s New in PyCharm 2021.3](https://www.jetbrains.com/pycharm/whatsnew/)
If you have any questions then please crate an issue on [JetBrains youtrack](https://youtrack.jetbrains.com/issues)
JetBrains maintains the poetry feature in PyCharm.
## Sponsors
[](https://github.com/JetBrainsOfficial)
## Help
See [documentation](https://koxudaxi.github.io/poetry-pycharm-plugin/) for more details.
## Demo

## Quick Installation
The plugin is in JetBrains repository ([Poetry Plugin Page](https://plugins.jetbrains.com/plugin/14307-poetry))
You can install the stable version on PyCharm's `Marketplace` (Preference -> Plugins -> Marketplace) [Official Document](https://www.jetbrains.com/help/idea/managing-plugins.html)
**I recommend PyCharm 2020.3 and the plugin version 1.0 or later to get all features and performance.**

## Features
### Implemented
- add a new/existing poetry environment as a interpreter
- install packages with pyproject.toml when add a interpreter
- add a new pyproject.toml when it does not exists
- [install and import a package with poetry (QuickFix)](#screen-shots)
- install packages from poetry.lock
- update and lock with a popup
- show a message and a link to fix settings (QuickFix)
- install extras and run scripts by clicking a line marker ([Toml plugin](https://plugins.jetbrains.com/plugin/8195-toml) is required)
- show a message for outdated version packages ([Toml plugin](https://plugins.jetbrains.com/plugin/8195-toml) is required)
- install/uninstall packages from GUI
- detect poetry project when open new project
## Motivation
Poetry is a popular package manager of python.
However, PyCharm doesn't support poetry.
This plugin support poetry. This source code was forked from the Pipenv integration code in IntelliJ-community.
In this issue [PY-30702](https://youtrack.jetbrains.com/issue/PY-30702), the feature is discussing. But, We need time to get the proper functionality in PyCharm.
The plugin has useful features like installing from poetry.lock.(you can watch demo video)
However, The feature is limited. PyCharm has to provided extension points for perfect features.
I guess if the plugin be used a lot of people, then JetBrains developers will implement extension points or poetry integration in PyCharm.
## This project is currently in an experimental phase
~~This plugin supports minimum [features](#features) to use poetry in PyCharm.~~
~~There is [feature restrictions](#feature-restrictions).~~
~~But, PyCharm team will add a few api to resolve the problem. The detail is in [this issue](https://github.com/koxudaxi/poetry-pycharm-plugin/issues/58).~~
PyCharm provides APIs that are for third-party package manager plugins.
The project needs any feedback and PRs. We're waiting for your feedback and PRs.
## Feature Restrictions
The plugin can't provide some features for technical reasons.
Because PyCharm has not provided APIs to support third-party python package managers yet. (a.k.a [extension points](https://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_extensions.html))
We will be able to implement the following features when JetBrains add extension points to PyCharms.
- ~~Create a new environment when creating a new project.~~
- Use the Custom Icon for poetry. (Coming Soon)
- ~~Install/uninstall a package from GUI(settings)~~
- And more.
## Screen Shots








## Contribute
We are waiting for your contributions to `poetry-pycharm-plugin`.
## Links
- [A JetBrains PyCharm plugin](https://plugins.jetbrains.com/plugin/12861-pydantic) for [`pydantic`](https://github.com/samuelcolvin/pydantic).
- I got interviewed about creating a plugin for [JetBrains' PyCharm Blog](https://blog.jetbrains.com/pycharm/2020/04/interview-koudai-aono-author-of-pydantic-plugin-for-pycharm/).
================================================
FILE: build.gradle
================================================
buildscript {
ext.kotlin_version = "1.5.30"
ext.toml_version = "0.2.154.4085-212"
ext.tuweni_toml_version = "2.0.0"
ext.jvm_version = "11"
ext.language_version = "1.3"
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version", "org.apache.tuweni:tuweni-toml:$tuweni_toml_version"
}
}
plugins {
id "org.jetbrains.intellij" version "1.1.6"
}
intellij {
pluginName = project.name
version = "2021.2.1"
type = "PC"
updateSinceUntilBuild = false
downloadSources = true
plugins = ["python-ce", "org.toml.lang:$toml_version"]
}
patchPluginXml {
sinceBuild = "212.4746.13"
untilBuild = "212.*"
}
allprojects {
apply plugin: "org.jetbrains.intellij"
apply plugin: "kotlin"
apply plugin: "jacoco"
repositories {
mavenCentral()
}
compileKotlin {
kotlinOptions {
jvmTarget = jvm_version
languageVersion = language_version
apiVersion = language_version
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = jvm_version
languageVersion = language_version
apiVersion = language_version
}
}
dependencies {
testImplementation 'junit:junit:4.13.2'
implementation "org.apache.tuweni:tuweni-toml:$tuweni_toml_version"
compile "org.jetbrains:annotations:23.0.0"
}
jacocoTestReport {
reports {
xml.enabled true
html.enabled true
}
}
sourceCompatibility = jvm_version
targetCompatibility = jvm_version
}
sourceSets {
main {
java.srcDir 'src'
resources.srcDir 'resources'
}
test {
java.srcDir 'testSrc'
resources.srcDir 'testData'
}
}
================================================
FILE: docs/development.md
================================================
# Development
## Building the plugin
You can build and run the plugin either via the command line or through IntelliJ IDEA:
### Shell on Linux or MacOS
```bash
$ ./gradlew buildPlugin
```
### Command Prompt on Windows
```
$ gradlew.bat buildPlugin
```
### JetBrains IDE on any platform
[Official documentation](https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/using_dev_kit.html])
## Running the IDE with the built plugin
```bash
$ ./gradlew runIde
```
## Unittest
You should add a unittest for the new code. But, This plugin do complex behavior to modules.
You may feel writing unittest difficult.
We should write a unittest as much as possible.
Unittest is not blocker for PRs.
## License For JetBrains' Code
These files are forked from [IntelliJ IDEA Community Edition](https://github.com/JetBrains/intellij-community)
The files are licensed under the Apache License, Version 2.0.
http://www.apache.org/licenses/LICENSE-2.0
================================================
FILE: docs/index.md
================================================
# Poetry PyCharm Plugin

[](https://plugins.jetbrains.com/plugin/14307-poetry)


[A JetBrains PyCharm plugin](https://plugins.jetbrains.com/plugin/14307-poetry) for [`poetry`](https://python-poetry.org/).
# [This plugin has been merged into PyCharm source code🎉](https://www.jetbrains.com/pycharm/whatsnew/)
PyCharm 2021.3 or later provide poetry integration as a built-in feature😎
👉[What’s New in PyCharm 2021.3](https://www.jetbrains.com/pycharm/whatsnew/)
If you have any questions then please crate an issue on [JetBrains youtrack](https://youtrack.jetbrains.com/issues)
JetBrains maintains the poetry feature in PyCharm.
## Sponsors
[](https://github.com/JetBrainsOfficial)
## Demo

## Quick Installation
The plugin is in JetBrains repository ([Poetry Plugin Page](https://plugins.jetbrains.com/plugin/14307-poetry))
You can install the stable version on PyCharm's `Marketplace` (Preference -> Plugins -> Marketplace) [Official Document](https://www.jetbrains.com/help/idea/managing-plugins.html)
**I recommend PyCharm 2020.3 and the plugin version 1.0 or later to get all features and performance.**

## Features
### Implemented
- add a new/existing poetry environment as a interpreter
- install packages with pyproject.toml when add a interpreter
- add a new pyproject.toml when it does not exists
- [install and import a package with poetry (QuickFix)](#screen-shots)
- install packages from poetry.lock
- update and lock with a popup
- show a message and a link to fix settings (QuickFix)
- install extras and run scripts by clicking a line marker ([Toml plugin](https://plugins.jetbrains.com/plugin/8195-toml) is required)
- show a message for outdated version packages ([Toml plugin](https://plugins.jetbrains.com/plugin/8195-toml) is required)
- install/uninstall packages from GUI
- detect poetry project when open new project
## Motivation
Poetry is a popular package manager of python.
However, PyCharm doesn't support poetry.
This plugin support poetry. This source code was forked from the Pipenv integration code in IntelliJ-community.
In this issue [PY-30702](https://youtrack.jetbrains.com/issue/PY-30702), the feature is discussing. But, We need time to get the proper functionality in PyCharm.
The plugin has useful features like installing from poetry.lock.(you can watch demo video)
However, The feature is limited. PyCharm has to provided extension points for perfect features.
I guess if the plugin be used a lot of people, then JetBrains developers will implement extension points or poetry integration in PyCharm.
## This project is currently in an experimental phase
~~This plugin supports minimum [features](#features) to use poetry in PyCharm.~~
~~There is [feature restrictions](#feature-restrictions).~~
~~But, PyCharm team will add a few api to resolve the problem. The detail is in [this issue](https://github.com/koxudaxi/poetry-pycharm-plugin/issues/58).~~
PyCharm provides APIs that are for third-party package manager plugins.
The project needs any feedback and PRs. We're waiting for your feedback and PRs.
## Feature Restrictions
The plugin can't provide some features for technical reasons.
Because PyCharm has not provided APIs to support third-party python package managers yet. (a.k.a [extension points](https://www.jetbrains.org/intellij/sdk/docs/basics/plugin_structure/plugin_extensions.html))
We will be able to implement the following features when JetBrains add extension points to PyCharms.
- ~~Create a new environment when creating a new project.~~
- Use the Custom Icon for poetry. (Coming Soon)
- ~~Install/uninstall a package from GUI(settings)~~
- And more.
## Screen Shots








## Contribute
We are waiting for your contributions to `poetry-pycharm-plugin`.
## Links
- [A JetBrains PyCharm plugin](https://plugins.jetbrains.com/plugin/12861-pydantic) for [`pydantic`](https://github.com/samuelcolvin/pydantic).
- I got interviewed about creating a plugin for [JetBrains' PyCharm Blog](https://blog.jetbrains.com/pycharm/2020/04/interview-koudai-aono-author-of-pydantic-plugin-for-pycharm/).
================================================
FILE: docs/install.md
================================================
# Installation
**The plugin requires PyCharm 2020.3 or later (include other JetBrains IDEs)**
## MarketPlace
The plugin is in JetBrains repository ([Poetry Plugin Page](https://plugins.jetbrains.com/plugin/14307-poetry))
You can install the stable version on PyCharm's `Marketplace` (Preference -> Plugins -> Marketplace) [Official Document](https://www.jetbrains.com/help/idea/managing-plugins.html)

## Complied binary
The releases section of this repository contains a compiled version of the plugin: [poetry-pycharm-plugin.zip(latest)](https://github.com/koxudaxi/poetry-pycharm-plugin/releases/latest/download/poetry-pycharm-plugin.zip)
After downloading this file, you can install the plugin from disk by following [the JetBrains instructions here](https://www.jetbrains.com/help/pycharm/plugins-settings.html).
## Source
Alternatively, you can clone this repository and follow the instructions under the "Building the plugin" heading below to build from source.
The build process will create the file `build/distributions/poetry-pycharm-plugin.zip`.
This file can be installed as a PyCharm plugin from disk following the same instructions.
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sat May 16 18:47:10 JST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
================================================
FILE: gradle.properties
================================================
#org.gradle.jvmargs= -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m -Dfile.encoding=utf-8
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: mkdocs.yml
================================================
site_name: Poetry PyCharm Plugin
site_description: A JetBrains PyCharm plugin for poetry.
theme:
name: 'material'
palette:
primary: 'light blue'
accent: 'light blue'
analytics:
gtag: 275257853
markdown_extensions:
- codehilite
- admonition
- toc:
permalink: 🔗
repo_name: koxudaxi/poetry-pycharm-plugin
repo_url: https://github.com/koxudaxi/poetry-pycharm-plugin
site_url: https://koxudaxi.github.io/poetry-pycharm-plugin
nav:
- Overview: index.md
- Install: install.md
- Development: development.md
- Changelog: changelog.md
plugins:
- search
================================================
FILE: requirements.txt
================================================
mkdocs==1.2.3
mkdocs-material==8.0.5
================================================
FILE: resources/META-INF/only-toml.xml
================================================
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<runLineMarkerContributor language="TOML"
implementationClass="com.koxudaxi.poetry.PoetryExtrasLineMarkerContributor"/>
<runLineMarkerContributor language="TOML"
implementationClass="com.koxudaxi.poetry.PoetryScriptsLineMarkerContributor"/>
<localInspection language="TOML" enabledByDefault="true" implementationClass="com.koxudaxi.poetry.PoetryVersionInspection" displayName="Poetry Package versions" bundle="messages.PyPsiBundle" groupKey="INSP.GROUP.python" suppressId="PoetryPackageVersion" shortName="PoetryPackageVersion"/>
</extensions>
</idea-plugin>
================================================
FILE: resources/META-INF/plugin.xml
================================================
<idea-plugin url="https://github.com/koxudaxi/poetry-pycharm-plugin" require-restart="true">
<id>com.koxudaxi.poetry</id>
<name>Poetry</name>
<version>1.1.5-212</version>
<vendor email="koaxudai@gmail.com">Koudai Aono @koxudaxi</vendor>
<change-notes><![CDATA[
<h2>version 1.1.5-212</h2>
<p>Bug fixes</p>
<ul>
<li>Fix toml header problem [#271]</li>
</ul>
<p>Features</p>
<ul>
<li>Update versions [#270]</li>
</ul>
<h2>version 1.1.4-212</h2>
<p>Features</p>
<ul>
<li>Support PyCharm EAP 212.* [#240]</li>
</ul>
<h2>version 1.1.4</h2>
<p>Bug fixes</p>
<ul>
<li>Update an error dialog to the same as the dialog of pipenv [#224]</li>
</ul>
<h2>version 1.1.3</h2>
<p>Bug fixes</p>
<ul>
<li>[#215] Suggest existing poetry venvs that are in existing modules [#217] by @roxchkplusony </li>
</ul>
<h2>version 1.1.2</h2>
<p>Bug fixes</p>
<ul>
<li>Improve responsiveness of the Poetry add new venv panel [#214] by @roxchkplusony </li>
</ul>
<h2>version 1.1.1</h2>
<p>Bug fixes</p>
<ul>
<li>Remove checking poetry executable [#203]</li>
</ul>
<h2>version 1.1.0</h2>
<p>Features</p>
<ul>
<li>Support PyCharm EAP 211.* [#181]</li>
</ul>
<h2>version 1.0.7</h2>
<p>Bug fixes</p>
<ul>
<li>Fix replaced Python restricted dependencies [#184]</li>
</ul>
<h2>version 1.0.6</h2>
<p>Bug fixes</p>
<ul>
<li>Change command timeout to 30 sec [#177]</li>
<li>Fix broken dependency on Toml plugin [#176]</li>
</ul>
<h2>version 1.0.5</h2>
<p>Bug fixes</p>
<ul>
<li>Support Toml plugin version 0.2.139.3615-203 or earlier version [#174]</li>
</ul>
<h2>version 1.0.4</h2>
<p>Bug fixes</p>
<ul>
<li>Support Toml plugin version 0.2.140.3644-202 [#169]</li>
</ul>
<h2>version 1.0.3</h2>
<p>Features</p>
<ul>
<li>Add validation for poetry executable [#165]</li>
</ul>
<h2>version 1.0.2</h2>
<p>Features</p>
<ul>
<li>Support poetry lock with "--no-update" [#161]</li>
</ul>
<h2>version 1.0.1</h2>
<p>Bug fixes</p>
<ul>
<li>Remove configuration collector [#150] by @sproshev </li>
<li>Apply changes in python bundles in 2020.3 [#149] by @sproshev </li>
</ul>
<h2>version 1.0.0</h2>
<p>Features</p>
<ul>
<li>Remove deprecated APIs [#148]</li>
<li>Support PyCharm 2020.3 [#147]</li>
</ul>
<h2>version 0.5.5</h2>
<p>Bug fixes</p>
<ul>
<li>Fix compatibility problems for IntelliJ IDEA Ultimate IU-203.4818.26 [#130]</li>
</ul>
<h2>version 0.5.4</h2>
<p>Bug fixes</p>
<ul>
<li>Fix wrong warning for installed packages [#128]</li>
<li>Fix wrong icon for system interpreter [#127]</li>
</ul>
<h2>version 0.5.3</h2>
<p>Bug fixes</p>
<ul>
<li>Fix invalid build target version for IntelliJ [#119]</li>
</ul>
<h2>version 0.5.2</h2>
<p>Bug fixes</p>
<ul>
<li>Fix broken inspection config [#116]</li>
</ul>
<h2>version 0.5.1</h2>
<p>Features</p>
<ul>
<li>Change max target version to 2020.3 [#112]</li>
</ul>
<h2>version 0.5.0</h2>
<p>Features</p>
<ul>
<li>Support 2020.2.2 [#104]</li>
</ul>
<h2>version 0.1.3</h2>
<p>Features</p>
<ul>
<li>Support running scripts with RunConfiguration [#100]</li>
<li>Improve build config [#96]</li>
</ul>
<h2>version 0.1.2</h2>
<p>Features</p>
<ul>
<li>Add a version inspection [#85]</li>
</ul>
<h2>version 0.1.1</h2>
<p>Features</p>
<ul>
<li>Support script [#81]</li>
</ul>
<h2>version 0.1.0</h2>
<p>Features</p>
<ul>
<li>Add python version for environment name [#77]</li>
</ul>
<h2>version 0.0.13</h2>
<p>Bug fixes</p>
<ul>
<li>Fix invalid notification to install packages [#62] </li>
</ul>
<h2>version 0.0.12</h2>
<p>Bug fixes</p>
<ul>
<li>Create new windows interpreter [#53] by @akinnear</li>
Thanks to @akinnear
</ul>
<h2>version 0.0.11</h2>
<p>Features</p>
<ul>
<li>Support adding existing env [#48] </li>
</ul>
<p>Improvements</p>
<ul>
<li>Improve supporting windows [#51]</li>
</ul>
<p>Bug fixes</p>
<ul>
<li>fix detecting poetry envs [#50] </li>
</ul>
<h2>version 0.0.10</h2>
<p>Features</p>
<ul>
<li>Support adding packages with a quick fix [#44] </li>
</ul>
<h2>version 0.0.9</h2>
<p>Features</p>
<ul>
<li>Add unittest [#41] </li>
</ul>
<p>Bug fixes</p>
<ul>
<li>Fix an error not found toml plugin [#42] </li>
</ul>
<h2>version 0.0.8</h2>
<p>Features</p>
<ul>
<li>improve detecting extras [#39] </li>
<li>improve showing popup [#38] </li>
<li>improve handling invalid interpreter [#37] </li>
</ul>
<p>Bug fixes</p>
<ul>
<li>Remove a invalid content in Open Project menu [#35] </li>
</ul>
<h2>version 0.0.7</h2>
<p>Features</p>
<ul>
<li>Improve handling sdk [#32] </li>
<li>Support installing extras [#31] </li>
<li>Update jvm version [#26] </li>
</ul>
<h2>version 0.0.6</h2>
<p>Bug fixes</p>
<ul>
<li>fix project already disposed error [#21] </li>
</ul>
<h2>version 0.0.5</h2>
<p>Features</p>
</ul>
<li>update documents [#20] </li>
</ul>
<h2>version 0.0.4</h2>
<p>Features</p>
</ul>
<li>support creating new pyproject.toml [#17] </li>
<li>check pyproject.toml [#16] </li>
</ul>
<h2>version 0.0.3</h2>
<p>Features</p>
<ul>
<li>add installing from poetry.lock [#10] </li>
</ul>
<p>Bug fixes</p>
<ul>
<li>disable custom icon [#11] </li>
</ul>
<h2>version 0.0.2</h2>
<p>Features</p>
<ul>
<li>add quick fix [#9] </li>
</ul>
<h2>version 0.0.1</h2>
<p>Features</p>
<ul>
<li>Support update and lock [#3] </li>
<li>Support poetry [#2] </li>
</ul>
]]></change-notes>
<description><![CDATA[
<p>This plugin supports <a href="https://python-poetry.org">poetry</a> which is a package manager for Python</p>
<h2>Help</h2>
<p>See <a href="https://koxudaxi.github.io/poetry-pycharm-plugin/">documentation</a> for more details.</p>
<h3>This project is currently in an experimental phase</h3>
<img width="700" src="https://raw.githubusercontent.com/koxudaxi/poetry-pycharm-plugin/master/docs/poetry_demo1.gif"/>
<h2>Features</h2>
<h3>Implemented</h3>
<li>add a new/existing poetry environment as a interpreter </li>
<li>install packages with pyproject.toml when add a interpreter</li>
<li>add a new pyproject.toml when it does not exists</li>
<li>install and import a package with poetry (QuickFix)</li>
<li>install packages from poetry.lock</li>
<li>update and lock with a popup</li>
<li>show a message and a link to fix settings (QuickFix)</li>
<li>install extras and run scripts by clicking a line marker (<a href="https://plugins.jetbrains.com/plugin/8195-toml">Toml plugin</a> is required)</li>
<li>show a message for outdated version packages (<a href="https://plugins.jetbrains.com/plugin/8195-toml">Toml plugin</a> is required)</li>
<li>install/uninstall packages from GUI</li>
<li>- detect poetry project when open new project</li>
]]></description>
<depends>com.intellij.modules.lang</depends>
<depends>com.intellij.modules.python</depends>
<depends>com.intellij.modules.platform</depends>
<depends optional="true" config-file="only-toml.xml">org.toml.lang</depends>
<extensions defaultExtensionNs="com.intellij">
<editorFactoryListener implementation="com.koxudaxi.poetry.PyProjectTomlWatcher"/>
<projectService serviceImplementation="com.koxudaxi.poetry.PoetryConfigService"/>
<postStartupActivity implementation="com.koxudaxi.poetry.PoetryConfigLoader" order="last"/>
</extensions>
<extensions defaultExtensionNs="Pythonid">
<pyAddSdkProvider implementation="com.koxudaxi.poetry.PyAddPoetrySdkProvider"/>
<pythonFlavorProvider implementation="com.koxudaxi.poetry.PyPoetrySdkFlavorProvider"/>
<pySdkProvider implementation="com.koxudaxi.poetry.PoetrySdkProvider"/>
<packageManagerProvider implementation="com.koxudaxi.poetry.PyPoetryPackageManagerProvider"/>
<projectSdkConfigurationExtension implementation="com.koxudaxi.poetry.PyPoetrySdkConfiguration"/>
</extensions>
<actions>
<action id="poetryInstallExtras" class="com.koxudaxi.poetry.PoetryInstallExtras" text="Install Packages"
description="Poetry install extras packages">
</action>
<action id="poetryRunScript" class="com.koxudaxi.poetry.PoetryRunScript" text="Run Scripts"
description="Poetry run scripts">
</action>
</actions>
</idea-plugin>
================================================
FILE: resources/inspectionDescriptions/PoetryPackageVersion.html
================================================
<html>
<body>
This inspection checks versions of installed packages and pyproject.toml.
</body>
</html>
================================================
FILE: scripts/build_changelog.py
================================================
import re
import xml.etree.ElementTree
from enum import Enum
from html.parser import HTMLParser
from pathlib import Path
from typing import List, Optional
PROJECT_ROOT = Path(__file__).parent.parent
GITHUB_PR_URL: str = 'https://github.com/koxudaxi/poetry-pycharm-plugin/pull/'
class Tag(Enum):
Version = 'h2'
ChangeType = 'p'
ChangeBody = 'ul'
ChangeContent = 'li'
def get_markdown_hyperlinks(text: str) -> List[str]:
return [
f'[#{pr_number}]({GITHUB_PR_URL}/{pr_number})'
for pr_number in re.findall(r'#(\d+)', text)
]
class HistoryHTMLParser(HTMLParser):
def error(self, message):
pass
def __init__(self):
super().__init__()
self.current_tag: Tag = Tag.Version
self.markdown: str = ''
self.markdown_latest_log: str = ''
self.latest: Optional[bool] = None
def handle_starttag(self, tag: str, attrs):
self.current_tag = Tag(tag)
def handle_endtag(self, tag):
pass
def handle_data(self, data: str):
if not data.strip():
return
if self.current_tag == Tag.Version:
if self.latest is None:
self.latest = True
elif self.latest is True:
self.latest = False
self.markdown_latest_log = '\n'.join(self.markdown.splitlines()[1:])
self.markdown += f'## {data.replace("version ", "")}\n'
elif self.current_tag == Tag.ChangeType:
self.markdown += f'### {data}\n'
elif self.current_tag == Tag.ChangeContent:
links = get_markdown_hyperlinks(data)
converted_data = re.sub(r'\[[^]].+\]', f'[{", ".join(links)}]', data)
self.markdown += f'- {converted_data}\n'
def main():
plugin_xml = PROJECT_ROOT / 'resources/META-INF/plugin.xml'
tree = xml.etree.ElementTree.parse(str(plugin_xml))
root = tree.getroot()
history: str = root.find('change-notes').text
html_parser = HistoryHTMLParser()
html_parser.feed(history)
with open(PROJECT_ROOT / 'docs/changelog.md', 'w') as f:
f.write(html_parser.markdown)
with open(PROJECT_ROOT / 'docs/changelog_latest.md', 'w') as f:
f.write(html_parser.markdown_latest_log)
if __name__ == '__main__':
main()
================================================
FILE: settings.gradle
================================================
rootProject.name = 'poetry-pycharm-plugin'
================================================
FILE: src/com/koxudaxi/poetry/PoetryConfigLoader.kt
================================================
package com.koxudaxi.poetry
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import com.intellij.serviceContainer.AlreadyDisposedException
import com.jetbrains.python.statistics.sdks
/**
* This source code is created by @koxudaxi (Koudai Aono)
*/
class PoetryConfigLoader : StartupActivity {
override fun runActivity(project: Project) {
if (ApplicationManager.getApplication().isUnitTestMode) return
if (project.isDisposed) return
DumbService.getInstance(project).smartInvokeLater {
try {
project.sdks
.filterNot { it.isPoetry }
.filter { isPoetryFromConfig(project, it) }
.forEach { it.isPoetry = true }
} catch (e: AlreadyDisposedException) {
}
}
}
}
================================================
FILE: src/com/koxudaxi/poetry/PoetryConfigService.kt
================================================
package com.koxudaxi.poetry
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.project.Project
import com.intellij.util.xmlb.XmlSerializerUtil
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
@State(name = "PoetryConfigService", storages = [Storage("poetry.xml")])
class PoetryConfigService : PersistentStateComponent<PoetryConfigService> {
var poetryVirtualenvPaths = mutableSetOf<String>()
override fun getState(): PoetryConfigService {
return this
}
override fun loadState(config: PoetryConfigService) {
XmlSerializerUtil.copyBean(config, this)
}
companion object {
fun getInstance(project: Project): PoetryConfigService {
return ServiceManager.getService(project, PoetryConfigService::class.java)
}
}
}
================================================
FILE: src/com/koxudaxi/poetry/PoetryExtrasLineMarkerContributor.kt
================================================
package com.koxudaxi.poetry
import com.intellij.execution.lineMarker.RunLineMarkerContributor
import com.intellij.icons.AllIcons.Actions.Install
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.psi.PsiElement
import com.jetbrains.python.sdk.pythonSdk
import org.toml.lang.psi.*
object PoetryExtrasLineMarkerContributor : RunLineMarkerContributor() {
override fun getInfo(element: PsiElement): Info? {
val sdk = element.project.pythonSdk ?: return null
if (!sdk.isPoetry) return null
try {
if (element !is TomlKey) return null
} catch (e: NoClassDefFoundError) {
return null //Toml plugin is installed. But, PyCharm has not restarted yet.
}
val keyValue = element.parent as? TomlKeyValue ?: return null
val header = (keyValue.parent as? TomlTable)?.header ?: return null
if (header.key?.text != "tool.poetry.extras") return null
if (keyValue.key.text == null) return null
val value = keyValue.value as? TomlArray ?: return null
if (value.elements.isEmpty() || value.elements.any { it !is TomlLiteral || it.textLength < 3}) return null
val action =ActionManager.getInstance().getAction(PoetryInstallExtras.actionID)
return Info(Install, { parameter -> "Install " + ((parameter as? TomlKey)?.text ?: "extra")}, arrayOf(action))
}
}
================================================
FILE: src/com/koxudaxi/poetry/PoetryInstallExtras.kt
================================================
package com.koxudaxi.poetry
import com.intellij.execution.Location
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.jetbrains.python.sdk.isAssociatedWithModule
import com.jetbrains.python.sdk.pythonSdk
import com.jetbrains.python.statistics.modules
import org.toml.lang.psi.TomlKey
class PoetryInstallExtras : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val tomlKey = e.dataContext.getData(Location.DATA_KEY)?.psiElement as? TomlKey ?: return
val project = e.project ?: return
val pythonSdk = project.pythonSdk ?: return
project.modules.firstOrNull { pythonSdk.isAssociatedWithModule(it) }?.let { runPoetryInBackground(it, listOf("install", "--extras", tomlKey.text), "installing ${tomlKey.text}") }
}
companion object {
const val actionID = "poetryInstallExtras"
}
}
================================================
FILE: src/com/koxudaxi/poetry/PoetryRunScript.kt
================================================
package com.koxudaxi.poetry
import com.intellij.execution.ExecutionManager
import com.intellij.execution.Location
import com.intellij.execution.RunManager
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.RunConfigurationProducer
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.runners.ExecutionEnvironmentBuilder
import com.intellij.execution.util.ExecutionErrorDialog
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.jetbrains.extensions.python.toPsi
import com.jetbrains.python.packaging.PyExecutionException
import com.jetbrains.python.run.PythonRunConfigurationProducer
import com.jetbrains.python.sdk.pythonSdk
import org.toml.lang.psi.*
class PoetryRunScript : AnAction() {
private fun runScriptFromRunConfiguration(project: Project, file: PsiFile) {
val configurationFromContext = RunConfigurationProducer
.getInstance(PythonRunConfigurationProducer::class.java)
.createConfigurationFromContext(ConfigurationContext(file)) ?: return
val settings = configurationFromContext.configurationSettings
val runManager = RunManager.getInstance(project)
runManager.addConfiguration(settings)
runManager.selectedConfiguration = settings
val builder = ExecutionEnvironmentBuilder.createOrNull(DefaultRunExecutor.getRunExecutorInstance(), settings)
?: return
ExecutionManager.getInstance(project).restartRunProfile(builder.build())
}
override fun actionPerformed(e: AnActionEvent) {
val tomlKey = e.dataContext.getData(Location.DATA_KEY)?.psiElement as? TomlKey ?: return
val project = e.project ?: return
val scriptPath = project.pythonSdk?.homeDirectory?.parent?.findChild(tomlKey.text) ?: return
val scriptFile = scriptPath.toPsi(project) ?: return ExecutionErrorDialog.show(PyExecutionException("Cannot find a script file\nPlease run 'poetry install' before executing scripts", "poetry", emptyList()), "Poetry Plugin", project)
runScriptFromRunConfiguration(project, scriptFile.containingFile)
}
init {
templatePresentation.icon = AllIcons.Actions.Execute
}
companion object {
const val actionID = "poetryRunScript"
}
}
================================================
FILE: src/com/koxudaxi/poetry/PoetryScriptsLineMarkerContributor.kt
================================================
package com.koxudaxi.poetry
import com.intellij.execution.lineMarker.RunLineMarkerContributor
import com.intellij.icons.AllIcons.Actions.Execute
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.psi.PsiElement
import com.jetbrains.python.sdk.pythonSdk
import org.toml.lang.psi.TomlKey
import org.toml.lang.psi.TomlKeyValue
import org.toml.lang.psi.TomlLiteral
import org.toml.lang.psi.TomlTable
object PoetryScriptsLineMarkerContributor : RunLineMarkerContributor() {
override fun getInfo(element: PsiElement): Info? {
val sdk = element.project.pythonSdk ?: return null
if (!sdk.isPoetry) return null
try {
if (element !is TomlKey) return null
} catch (e: NoClassDefFoundError) {
return null //Toml plugin is installed. But, PyCharm has not restarted yet.
}
val keyValue = element.parent as? TomlKeyValue ?: return null
val header = (keyValue.parent as? TomlTable)?.header ?: return null
if (header.key?.text != "tool.poetry.scripts") return null
if (keyValue.key.text == null) return null
val value = keyValue.value as? TomlLiteral ?: return null
if (value.textLength < 3) return null
val action = ActionManager.getInstance().getAction(PoetryRunScript.actionID)
action.templatePresentation.text = "Run '${keyValue.key.text}'"
return Info(Execute, { parameter -> "Run '${((parameter as? TomlKey)?.text ?: "script")}'" }, arrayOf(action))
}
}
================================================
FILE: src/com/koxudaxi/poetry/PoetrySdkProvider.kt
================================================
package com.koxudaxi.poetry
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.SdkAdditionalData
import com.intellij.openapi.util.UserDataHolder
import com.jetbrains.python.packaging.ui.PyPackageManagementService
import com.jetbrains.python.sdk.PyInterpreterInspectionQuickFixData
import com.jetbrains.python.sdk.PySdkProvider
import com.jetbrains.python.sdk.PythonSdkUtil
import com.jetbrains.python.sdk.add.PyAddNewEnvPanel
import org.jdom.Element
import javax.swing.Icon
class PoetrySdkProvider : PySdkProvider {
override fun createEnvironmentAssociationFix(module: Module, sdk: Sdk, isPyCharm: Boolean, associatedModulePath: String?): PyInterpreterInspectionQuickFixData? {
if (sdk.isPoetry) {
val message = when {
associatedModulePath != null -> when {
isPyCharm -> "Poetry interpreter is associated with another project: $associatedModulePath"
else -> "Poetry interpreter is associated with another module: $associatedModulePath"
}
else -> when {
isPyCharm -> "Poetry interpreter is not associated with any project"
else -> "Poetry interpreter is not associated with any module"
}
}
return PyInterpreterInspectionQuickFixData(UsePoetryQuickFix(sdk, module), message)
}
return null
}
override fun createInstallPackagesQuickFix(module: Module): LocalQuickFix? {
val sdk = PythonSdkUtil.findPythonSdk(module) ?: return null
return if (sdk.isPoetry) PoetryInstallQuickFix() else null
}
override fun createNewEnvironmentPanel(project: Project?, module: Module?, existingSdks: List<Sdk>, newProjectPath: String?, context: UserDataHolder): PyAddNewEnvPanel {
return PyAddNewPoetryPanel(null, null, existingSdks, newProjectPath, context)
}
override fun getSdkAdditionalText(sdk: Sdk): String? = if (sdk.isPoetry) sdk.versionString else null
override fun getSdkIcon(sdk: Sdk): Icon? {
return if (sdk.isPoetry) POETRY_ICON else null
}
override fun loadAdditionalDataForSdk(element: Element): SdkAdditionalData? {
return PyPoetrySdkAdditionalData.load(element)
}
override fun tryCreatePackageManagementServiceForSdk(project: Project, sdk: Sdk): PyPackageManagementService? {
return if (sdk.isPoetry) PyPoetryPackageManagementService(project, sdk) else null
}
}
================================================
FILE: src/com/koxudaxi/poetry/PoetryVersionInspection.kt
================================================
package com.koxudaxi.poetry
import com.intellij.codeInspection.*
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiFile
import com.jetbrains.python.packaging.PyPackageManager
import com.jetbrains.python.sdk.*
import org.toml.lang.psi.*
class PoetryVersionInspection : LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder,
isOnTheFly: Boolean,
session: LocalInspectionToolSession): PsiElementVisitor {
return PoetryFileVisitor(holder, session)
}
class PoetryFileVisitor(val holder: ProblemsHolder,
session: LocalInspectionToolSession) : PsiElementVisitor() {
private fun guessModule(element: PsiElement): Module? {
return ModuleUtilCore.findModuleForPsiElement(element)
?: ModuleManager.getInstance(element.project).modules.let { if (it.size != 1) null else it[0] }
}
override fun visitFile(file: PsiFile) {
val module = guessModule(file) ?: return
val sdk = PythonSdkUtil.findPythonSdk(module) ?: return
if (!sdk.isPoetry) return
if (file.virtualFile != module.pyProjectToml) return
file.children
.filter { element ->
(element as? TomlTable)?.header?.key?.text in listOf("tool.poetry.dependencies", "tool.poetry.dev-dependencies")
}.flatMap {
it.children.mapNotNull { line -> line as? TomlKeyValue }
}.forEach { keyValue ->
val packageName = keyValue.key.text
val outdatedVersion = (PyPackageManager.getInstance(sdk) as? PyPoetryPackageManager)?.let { it.getOutdatedPackages()[packageName] }
if (outdatedVersion is PoetryOutdatedVersion) {
val message = "'${packageName}' version ${outdatedVersion.currentVersion} is outdated (latest: ${outdatedVersion.latestVersion})"
holder.registerProblem(keyValue, message, ProblemHighlightType.WARNING)
}
}
}
}
}
================================================
FILE: src/com/koxudaxi/poetry/PyAddExistingPoetryEnvPanel.kt
================================================
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.koxudaxi.poetry
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.UserDataHolder
import com.intellij.util.ui.FormBuilder
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PySdkBundle
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.add.PyAddSdkPanel
import com.jetbrains.python.sdk.add.PyAddSdkView
import com.jetbrains.python.sdk.add.PySdkPathChoosingComboBox
import com.jetbrains.python.sdk.add.addInterpretersAsync
import java.awt.BorderLayout
import java.util.concurrent.ConcurrentHashMap
import javax.swing.Icon
import kotlin.streams.toList
/**
* @author vlan
*/
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
class PyAddExistingPoetryEnvPanel(private val project: Project?,
private val module: Module?,
private val existingSdks: List<Sdk>,
override var newProjectPath: String?,
context: UserDataHolder) : PyAddSdkPanel() {
private var sdkToModule = ConcurrentHashMap<String, Module>()
override val panelName: String get() = PyBundle.message("python.add.sdk.panel.name.existing.environment")
override val icon: Icon = POETRY_ICON
private val sdkComboBox = PySdkPathChoosingComboBox()
init {
layout = BorderLayout()
val formPanel = FormBuilder.createFormBuilder()
.addLabeledComponent(PySdkBundle.message("python.interpreter.label"), sdkComboBox)
.panel
add(formPanel, BorderLayout.NORTH)
addInterpretersAsync(sdkComboBox) {
val existingSdkPaths = sdkHomes(existingSdks)
val moduleSdks = allModules(project).parallelStream().flatMap { module ->
val sdks = detectPoetryEnvs(module, existingSdkPaths, module.basePath)
.filterNot { it.isAssociatedWithAnotherModule(module) }
sdks.forEach { sdkToModule.putIfAbsent(it.name, module) }
sdks.stream()
}.toList()
val rootSdks = detectPoetryEnvs(module, existingSdkPaths, project?.basePath ?: newProjectPath)
.filterNot { it.isAssociatedWithAnotherModule(module) }
val moduleSdkPaths = moduleSdks.map { it.name }.toSet()
val sdks = rootSdks.filterNot { moduleSdkPaths.contains(it.name) } + moduleSdks
sdks.sortedBy { it.name }
}
}
override fun validateAll(): List<ValidationInfo> = listOfNotNull(validateSdkComboBox(sdkComboBox, this))
override fun getOrCreateSdk(): Sdk? {
return when (val sdk = sdkComboBox.selectedSdk) {
is PyDetectedSdk -> {
val mappedModule = sdkToModule[sdk.name] ?: module
setupPoetrySdkUnderProgress(project, mappedModule, existingSdks, newProjectPath,
getPythonExecutable(sdk.name), false, sdk.name)?.apply {
PySdkSettings.instance.preferredVirtualEnvBaseSdk = getPythonExecutable(sdk.name)
}
}
else -> sdk
}
}
companion object {
fun validateSdkComboBox(field: PySdkPathChoosingComboBox, view: PyAddSdkView): ValidationInfo? {
return when (val sdk = field.selectedSdk) {
null -> ValidationInfo(PySdkBundle.message("python.sdk.field.is.empty"), field)
// This plugin does not support installing python sdk.
// is PySdkToInstall -> {
// val message = sdk.getInstallationWarning(getDefaultButtonName(view))
// ValidationInfo(message).asWarning().withOKEnabled()
// }
else -> null
}
}
}
}
================================================
FILE: src/com/koxudaxi/poetry/PyAddNewPoetryFromFilePanel.kt
================================================
package com.koxudaxi.poetry
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.module.Module
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.NlsSafe
import com.intellij.util.ui.FormBuilder
import org.jetbrains.annotations.SystemDependent
import java.awt.BorderLayout
import javax.swing.JPanel
class PyAddNewPoetryFromFilePanel(private val module: Module) : JPanel() {
val envData: Data
get() = Data(poetryPathField.text)
private val poetryPathField = TextFieldWithBrowseButton()
init {
poetryPathField.apply {
getPoetryExecutable()?.absolutePath?.also { text = it }
addBrowseFolderListener(
"Select Path to Poetry Executable",
null,
module.project,
FileChooserDescriptorFactory.createSingleFileOrExecutableAppDescriptor()
)
}
layout = BorderLayout()
val formPanel = FormBuilder.createFormBuilder()
.addLabeledComponent("Poetry executable:", poetryPathField)
.panel
add(formPanel, BorderLayout.NORTH)
}
fun validateAll(): List<ValidationInfo> = listOfNotNull( validatePoetryExecutable(poetryPathField.text))
data class Data(val poetryPath: @NlsSafe @SystemDependent String)
}
================================================
FILE: src/com/koxudaxi/poetry/PyAddNewPoetryPanel.kt
================================================
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.koxudaxi.poetry
import com.intellij.application.options.ModuleListCellRenderer
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBTextField
import com.intellij.util.PlatformUtils
import com.intellij.util.text.nullize
import com.intellij.util.ui.FormBuilder
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PySdkBundle
import com.jetbrains.python.PythonModuleTypeBase
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.add.PyAddNewEnvPanel
import com.jetbrains.python.sdk.add.PySdkPathChoosingComboBox
import com.jetbrains.python.sdk.add.addInterpretersAsync
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.event.ItemEvent
import java.io.File
import java.nio.file.Files
import java.util.concurrent.ConcurrentHashMap
import javax.swing.Icon
import javax.swing.JComboBox
import javax.swing.event.DocumentEvent
/**
* The UI panel for adding the poetry interpreter for the project.
*
* @author vlan
*/
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
class PyAddNewPoetryPanel(private val project: Project?,
private val module: Module?,
private val existingSdks: List<Sdk>,
override var newProjectPath: String?,
context: UserDataHolder) : PyAddNewEnvPanel() {
override val envName = "Poetry"
override val panelName: String get() = "Poetry Environment"
// TODO: Need a extension point
override val icon: Icon = POETRY_ICON
private val moduleField: JComboBox<Module>
private val baseSdkField = PySdkPathChoosingComboBox()
init {
addInterpretersAsync(baseSdkField) {
val sdks = findBaseSdks(existingSdks, module, context).takeIf { it.isNotEmpty() }
?: detectSystemWideSdks(module, existingSdks, context)
sdks.filterNot { PythonSdkUtil.isInvalid(it) || it.isPoetry }
}
}
private val installPackagesCheckBox = JBCheckBox("Install packages from pyproject.toml").apply {
isVisible = projectPath?.let { StandardFileSystems.local().findFileByPath(it)?.findChild(PY_PROJECT_TOML)?.let { file -> getPyProjectTomlForPoetry(file) } } != null
isSelected = isVisible
}
private val poetryPathField = TextFieldWithBrowseButton().apply {
addBrowseFolderListener(null, null, null, FileChooserDescriptorFactory.createSingleFileDescriptor())
val field = textField as? JBTextField ?: return@apply
detectPoetryExecutable()?.let {
field.emptyText.text = "Auto-detected: ${it.absolutePath}"
}
PropertiesComponent.getInstance().poetryPath?.let {
field.text = it
}
}
init {
layout = BorderLayout()
val modules = allModules(project)
moduleField = JComboBox(modules.toTypedArray()).apply {
renderer = ModuleListCellRenderer()
preferredSize = Dimension(Int.MAX_VALUE, preferredSize.height)
addItemListener {
if (it.stateChange == ItemEvent.SELECTED) {
update()
}
}
}
poetryPathField.textField.document.addDocumentListener(object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
update()
}
})
val builder = FormBuilder.createFormBuilder().apply {
if (module == null && modules.size > 1) {
val associatedObject = if (PlatformUtils.isPyCharm()) "project" else "module"
addLabeledComponent("Associated $associatedObject", moduleField)
}
addLabeledComponent(PySdkBundle.message("python.venv.base.label"), baseSdkField)
addComponent(installPackagesCheckBox)
addLabeledComponent("Poetry executable:", poetryPathField)
}
add(builder.panel, BorderLayout.NORTH)
update()
}
override fun getOrCreateSdk(): Sdk? {
PropertiesComponent.getInstance().poetryPath = poetryPathField.text.nullize()
return setupPoetrySdkUnderProgress(project, selectedModule, existingSdks, newProjectPath,
baseSdkField.selectedSdk?.homePath, installPackagesCheckBox.isSelected)?.apply {
PySdkSettings.instance.preferredVirtualEnvBaseSdk = baseSdkField.selectedSdk?.homePath
}
}
override fun validateAll(): List<ValidationInfo> =
listOfNotNull(validatePoetryExecutable(), validatePoetryIsNotAdded())
override fun addChangeListener(listener: Runnable) {
poetryPathField.textField.document.addDocumentListener(object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
listener.run()
}
})
super.addChangeListener(listener)
}
/**
* Updates the view according to the current state of UI controls.
*/
private fun update() {
selectedModule?.let {
installPackagesCheckBox.isEnabled = it.pyProjectToml != null
}
}
/**
* The effective module for which we add a new environment.
*/
private val selectedModule: Module?
get() = module ?: try {
moduleField.selectedItem
} catch (e: NullPointerException) {
null
} as? Module
/**
* Checks if `poetry` is available on `$PATH`.
*/
private fun validatePoetryExecutable(): ValidationInfo? {
val executable = poetryPathField.text.nullize()?.let { File(it) }
?: detectPoetryExecutable()
?: return ValidationInfo("Poetry executable is not found")
return when {
!executable.exists() -> ValidationInfo(PyBundle.message("python.sdk.file.not.found", executable.absolutePath))
!Files.isExecutable(executable.toPath()) || !executable.isFile -> ValidationInfo(PyBundle.message("python.sdk.cannot.execute", executable.absolutePath))
else -> null
}
}
private val isPoetry by lazy { existingSdks.filter { it.isPoetry }.associateBy { it.associatedModulePath } }
private val homePath by lazy { existingSdks.associateBy { it.homePath } }
private val pythonExecutable = ConcurrentHashMap<String, String>()
private val venvInProject = ConcurrentHashMap<String, Boolean?>()
private fun computePythonExecutable(homePath: String): String? {
return pythonExecutable.getOrPut(homePath) { getPythonExecutable(homePath) }
}
private fun isVenvInProject(path: String): Boolean? {
return venvInProject.getOrPut(path) { isVirtualEnvsInProject(path) }
}
/**
* Checks if the poetry for the project hasn't been already added.
*/
private fun validatePoetryIsNotAdded(): ValidationInfo? {
val path = projectPath ?: return null
val project = project ?: return null
val addedPoetry = isPoetry[path] ?: return null
if (addedPoetry.homeDirectory == null) return null
// TODO: check existing envs
if (isVenvInProject(path) == false) return null
val inProjectEnvExecutable = inProjectEnvPath?.let {computePythonExecutable(it)} ?: return null
val inProjectEnv = homePath[inProjectEnvExecutable] ?: return null
return ValidationInfo("Poetry interpreter has been already added, select '${inProjectEnv.name}'")
}
/**
* The effective project path for the new project or for the existing project.
*/
private val projectPath: String?
get() = newProjectPath ?: selectedModule?.basePath ?: project?.basePath
private val inProjectEnvDir = ".venv"
private val inProjectEnvPath: String?
get() = projectPath?.let { it + File.separator + inProjectEnvDir }
}
================================================
FILE: src/com/koxudaxi/poetry/PyAddPoetrySdkProvider.kt
================================================
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.koxudaxi.poetry
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.UserDataHolder
import com.jetbrains.python.sdk.add.PyAddSdkProvider
/**
* @author vlan
*/
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
class PyAddPoetrySdkProvider : PyAddSdkProvider {
override fun createView(project: Project?,
module: Module?,
newProjectPath: String?,
existingSdks: List<Sdk>,
context: UserDataHolder) =
createPoetryPanel(project, module, existingSdks, newProjectPath, context)
}
================================================
FILE: src/com/koxudaxi/poetry/PyPoetryPackageManagementService.kt
================================================
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.koxudaxi.poetry
import com.intellij.execution.ExecutionException
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.webcore.packaging.RepoPackage
import com.jetbrains.python.packaging.PyPIPackageUtil
import com.jetbrains.python.packaging.PyPackageManagerUI
import com.jetbrains.python.packaging.PyRequirementParser
import com.jetbrains.python.packaging.ui.PyPackageManagementService
/**
* @author vlan
*/
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
class PyPoetryPackageManagementService(project: Project, sdk: Sdk) : PyPackageManagementService(project, sdk) {
override fun getAllRepositories(): List<String>? = null
override fun canInstallToUser() = false
override fun getAllPackages(): List<RepoPackage> {
PyPIPackageUtil.INSTANCE.loadAdditionalPackages(sdk.poetrySources, false)
return allPackagesCached
}
override fun reloadAllPackages(): List<RepoPackage> {
PyPIPackageUtil.INSTANCE.loadAdditionalPackages(sdk.poetrySources, true)
return allPackagesCached
}
override fun getAllPackagesCached(): List<RepoPackage> =
PyPIPackageUtil.INSTANCE.getAdditionalPackages(sdk.poetrySources)
override fun installPackage(repoPackage: RepoPackage,
version: String?,
forceUpgrade: Boolean,
extraOptions: String?,
listener: Listener,
installToUser: Boolean) {
val ui = PyPackageManagerUI(project, sdk, object : PyPackageManagerUI.Listener {
override fun started() {
listener.operationStarted(repoPackage.name)
}
override fun finished(exceptions: List<ExecutionException?>?) {
listener.operationFinished(repoPackage.name, toErrorDescription(exceptions, sdk))
}
})
val requirement = when {
version != null -> PyRequirementParser.fromLine("${repoPackage.name}==$version")
else -> PyRequirementParser.fromLine(repoPackage.name)
} ?: return
val extraArgs = extraOptions?.split(" +".toRegex()) ?: emptyList()
ui.install(listOf(requirement), extraArgs)
}
}
================================================
FILE: src/com/koxudaxi/poetry/PyPoetryPackageManager.kt
================================================
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.koxudaxi.poetry
import com.intellij.execution.ExecutionException
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.jetbrains.python.packaging.*
import com.jetbrains.python.sdk.PythonSdkType
import com.jetbrains.python.sdk.associatedModuleDir
/**
* @author vlan
*/
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
class PyPoetryPackageManager(val sdk: Sdk) : PyPackageManager() {
private val installedLines = listOf("Already installed", "Skipping", "Updating")
@Volatile
private var packages: List<PyPackage>? = null
private var requirements: List<PyRequirement>? = null
private var outdatedPackages: Map<String, PoetryOutdatedVersion> = emptyMap()
init {
PyPackageUtil.runOnChangeUnderInterpreterPaths(sdk, this, Runnable{
PythonSdkType.getInstance().setupSdkPaths(sdk)
})
}
override fun installManagement() {}
override fun hasManagement() = true
override fun install(requirementString: String) {
install(parseRequirements(requirementString), emptyList())
}
override fun install(requirements: List<PyRequirement>?, extraArgs: List<String>) {
val args = if (requirements == null || requirements.isEmpty()) {
listOfNotNull(listOf("install"),
extraArgs)
.flatten()
} else {
listOfNotNull(listOf("add"),
requirements.map { it.name },
extraArgs)
.flatten()
}
try {
runPoetry(sdk, *args.toTypedArray())
} finally {
sdk.associatedModuleDir?.refresh(true, false)
refreshAndGetPackages(true)
}
}
override fun uninstall(packages: List<PyPackage>) {
val args = listOf("remove") +
packages.map { it.name }
try {
runPoetry(sdk, *args.toTypedArray())
} finally {
sdk.associatedModuleDir?.refresh(true, false)
refreshAndGetPackages(true)
}
}
override fun refresh() {
with(ApplicationManager.getApplication()) {
invokeLater {
runWriteAction {
val files = sdk.rootProvider.getFiles(OrderRootType.CLASSES)
VfsUtil.markDirtyAndRefresh(true, true, true, *files)
}
PythonSdkType.getInstance().setupSdkPaths(sdk)
}
}
}
override fun createVirtualEnv(destinationDir: String, useGlobalSite: Boolean): String {
throw ExecutionException("Creating virtual environments based on Poetry environments is not supported")
}
override fun getPackages() = packages
fun getOutdatedPackages() = outdatedPackages
override fun refreshAndGetPackages(alwaysRefresh: Boolean): List<PyPackage> {
if (alwaysRefresh || packages == null) {
packages = null
val outputInstallDryRun = try {
runPoetry(sdk, "install", "--dry-run", "--no-root")
} catch (e: ExecutionException) {
packages = emptyList()
return packages ?: emptyList()
}
val allPackage = parsePoetryInstallDryRun(outputInstallDryRun)
packages = allPackage.first
requirements = allPackage.second
val outputOutdatedPackages = try {
runPoetry(sdk, "show", "--outdated")
} catch (e: ExecutionException) {
outdatedPackages = emptyMap()
}
if (outputOutdatedPackages is String) {
outdatedPackages = parsePoetryShowOutdated(outputOutdatedPackages)
}
ApplicationManager.getApplication().messageBus.syncPublisher(PACKAGE_MANAGER_TOPIC).packagesRefreshed(sdk)
}
return packages ?: emptyList()
}
override fun getRequirements(module: Module): List<PyRequirement>? {
return requirements
}
override fun parseRequirements(text: String): List<PyRequirement> =
PyRequirementParser.fromText(text)
override fun parseRequirement(line: String): PyRequirement? =
PyRequirementParser.fromLine(line)
override fun parseRequirements(file: VirtualFile): List<PyRequirement> =
PyRequirementParser.fromFile(file)
override fun getDependents(pkg: PyPackage): Set<PyPackage> {
// TODO: Parse the dependency information from `pipenv graph`
return emptySet()
}
private fun getVersion(version: String): String {
return if (Regex("^[0-9]").containsMatchIn(version)) "==$version" else version
}
private fun toRequirements(packages: List<PyPackage>): List<PyRequirement> =
packages
.asSequence()
// .filterNot { (_, pkg) -> pkg.editable ?: false }
// TODO: Support requirements markers (PEP 496), currently any packages with markers are ignored due to PY-30803
// .filter { (_, pkg) -> pkg.markers == null }
.flatMap { it -> this.parseRequirements("${it.name}${it.version?.let { getVersion(it) } ?: ""}").asSequence() }
.toList()
/**
* Parses the output of `poetry install --dry-run ` into a list of packages.
*/
fun parsePoetryInstallDryRun(input: String): Pair<List<PyPackage>, List<PyRequirement>> {
fun getNameAndVersion(line: String): Triple<String, String, String> {
return line.split(" ").let {
val installedVersion = it[5].replace(Regex("[():]"), "")
val requiredVersion = when {
it.size > 7 && it[6] == "->" -> it[7].replace(Regex("[():]"), "")
else -> installedVersion
}
Triple(it[4], installedVersion, requiredVersion)
}
}
val pyPackages = mutableListOf<PyPackage>()
val pyRequirements = mutableListOf<PyRequirement>()
input
.lineSequence()
.filter { listOf(")", "Already installed").any{ lastWords -> it.endsWith(lastWords) } }
.forEach { line ->
getNameAndVersion(line).also {
if (installedLines.any { installedLine -> line.contains(installedLine) } ) {
pyPackages.add(PyPackage(it.first, it.second, null, emptyList()))
this.parseRequirement(it.first + getVersion(it.third))?.let { pyRequirement -> pyRequirements.add(pyRequirement) }
} else if ( line.contains("Installing")) {
this.parseRequirement(it.first + getVersion(it.third))?.let { pyRequirement -> pyRequirements.add(pyRequirement) }
}
}
}
return Pair(pyPackages.distinct().toList(), pyRequirements.distinct().toList())
}
}
================================================
FILE: src/com/koxudaxi/poetry/PyPoetryPackageManagerProvider.kt
================================================
package com.koxudaxi.poetry
import com.intellij.openapi.projectRoots.Sdk
import com.jetbrains.python.packaging.PyPackageManager
import com.jetbrains.python.packaging.PyPackageManagerProvider
class PyPoetryPackageManagerProvider : PyPackageManagerProvider {
override fun tryCreateForSdk(sdk: Sdk): PyPackageManager? = if (sdk.isPoetry) PyPoetryPackageManager(sdk) else null
}
================================================
FILE: src/com/koxudaxi/poetry/PyPoetrySdkAdditionalData.kt
================================================
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.koxudaxi.poetry
import com.jetbrains.python.sdk.PythonSdkAdditionalData
import org.jdom.Element
/**
* Additional Poetry data associated with an SDK.
*
* @author vlan
*/
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
// TODO: Need a extension point
class PyPoetrySdkAdditionalData : PythonSdkAdditionalData {
constructor() : super(PyPoetrySdkFlavor)
constructor(data: PythonSdkAdditionalData) : super(data)
override fun save(element: Element) {
super.save(element)
// We use this flag to create an instance of the correct additional data class. The flag itself is not used after that
element.setAttribute(IS_POETRY, "true")
}
companion object {
private const val IS_POETRY = "IS_POETRY"
/**
* Loads serialized data from an XML element.
*/
@JvmStatic
fun load(element: Element): PyPoetrySdkAdditionalData? =
when {
element.getAttributeValue(IS_POETRY) == "true" -> {
PyPoetrySdkAdditionalData().apply {
load(element)
}
}
else -> null
}
/**
* Creates a new instance of data with copied fields.
*/
@JvmStatic
fun copy(data: PythonSdkAdditionalData): PyPoetrySdkAdditionalData =
PyPoetrySdkAdditionalData(data)
}
}
================================================
FILE: src/com/koxudaxi/poetry/PyPoetrySdkConfiguration.kt
================================================
package com.koxudaxi.poetry
import com.intellij.codeInspection.util.IntentionName
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
import com.intellij.execution.ExecutionException
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.ui.IdeBorderFactory
import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.JBUI
import com.jetbrains.python.sdk.*
import java.awt.BorderLayout
import java.awt.Insets
import javax.swing.JComponent
import javax.swing.JPanel
class PyPoetrySdkConfiguration : PyProjectSdkConfigurationExtension {
private val LOGGER = Logger.getInstance(PyPoetrySdkConfiguration::class.java)
override fun createAndAddSdkForConfigurator(module: Module): Sdk? = createAndAddSDk(module, false)
override fun getIntention(module: Module): @IntentionName String? =
module.pyProjectToml?.let { "Create a poetry environment using ${it.name}" }
override fun createAndAddSdkForInspection(module: Module): Sdk? = createAndAddSDk(module, true)
private fun createAndAddSDk(module: Module, inspection: Boolean): Sdk? {
val poetryEnvExecutable = askForEnvData(module, inspection) ?: return null
PropertiesComponent.getInstance().poetryPath = poetryEnvExecutable.poetryPath
return createPoetry(module)
}
private fun askForEnvData(module: Module, inspection: Boolean): PyAddNewPoetryFromFilePanel.Data? {
val poetryExecutable = getPoetryExecutable()?.absolutePath
if (inspection && validatePoetryExecutable(poetryExecutable) == null) {
return PyAddNewPoetryFromFilePanel.Data(poetryExecutable!!)
}
var permitted = false
var envData: PyAddNewPoetryFromFilePanel.Data? = null
ApplicationManager.getApplication().invokeAndWait {
val dialog = Dialog(module)
permitted = dialog.showAndGet()
envData = dialog.envData
LOGGER.debug("Dialog exit code: ${dialog.exitCode}, $permitted")
}
return if (permitted) envData else null
}
private fun createPoetry(module: Module): Sdk? {
ProgressManager.progress("Setting up poetry environment")
LOGGER.debug("Creating poetry environment")
val basePath = module.basePath ?: return null
val poetry = try {
val init = StandardFileSystems.local().findFileByPath(basePath)?.findChild(PY_PROJECT_TOML)?.let { getPyProjectTomlForPoetry(it) } == null
setupPoetry(FileUtil.toSystemDependentName(basePath), null, true, init)
}
catch (e: ExecutionException) {
LOGGER.warn("Exception during creating poetry environment", e)
showSdkExecutionException(null, e, "Failed To Create Poetry Environment")
return null
}
val path = PythonSdkUtil.getPythonExecutable(poetry).also {
if (it == null) {
LOGGER.warn("Python executable is not found: $poetry")
}
} ?: return null
val file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path).also {
if (it == null) {
LOGGER.warn("Python executable file is not found: $path")
}
} ?: return null
LOGGER.debug("Setting up associated poetry environment: $path, $basePath")
val sdk = SdkConfigurationUtil.setupSdk(
ProjectJdkTable.getInstance().allJdks,
file,
PythonSdkType.getInstance(),
false,
null,
suggestedSdkName(basePath)
) ?: return null
ApplicationManager.getApplication().invokeAndWait {
LOGGER.debug("Adding associated poetry environment: $path, $basePath")
SdkConfigurationUtil.addSdk(sdk)
sdk.isPoetry = true
sdk.associateWithModule(module, null)
}
return sdk
}
private class Dialog(module: Module) : DialogWrapper(module.project, false, IdeModalityType.PROJECT) {
private val panel = PyAddNewPoetryFromFilePanel(module)
val envData
get() = panel.envData
init {
title = "Setting Up Poetry Environment"
init()
}
override fun createCenterPanel(): JComponent {
return JPanel(BorderLayout()).apply {
val border = IdeBorderFactory.createEmptyBorder(Insets(4, 0, 6, 0))
val message = "File pyproject.toml contains project dependencies. Would you like to create a poetry environment using it?"
add(
JBUI.Panels.simplePanel(JBLabel(message)).withBorder(border),
BorderLayout.NORTH
)
add(panel, BorderLayout.CENTER)
}
}
override fun postponeValidation(): Boolean = false
override fun doValidateAll(): List<ValidationInfo> = panel.validateAll()
}
}
================================================
FILE: src/com/koxudaxi/poetry/PyPoetrySdkFlavor.kt
================================================
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.koxudaxi.poetry
import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor
import java.io.File
/**
* @author vlan
*/
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
object PyPoetrySdkFlavor : CPythonSdkFlavor() {
// TODO: Need a extension point
override fun getIcon() = POETRY_ICON
override fun isValidSdkPath(file: File) = false
}
================================================
FILE: src/com/koxudaxi/poetry/PyPoetrySdkFlavorProvider.kt
================================================
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.koxudaxi.poetry
import com.jetbrains.python.sdk.flavors.PythonFlavorProvider
/**
* @author vlan
*/
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
class PyPoetrySdkFlavorProvider : PythonFlavorProvider {
override fun getFlavor(platformIndependent: Boolean) = PyPoetrySdkFlavor
}
================================================
FILE: src/com/koxudaxi/poetry/poetry.kt
================================================
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.koxudaxi.poetry
import com.google.gson.annotations.SerializedName
import com.intellij.CommonBundle
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.execution.ExecutionException
import com.intellij.execution.RunCanceledByUserException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.process.ProcessNotCreatedException
import com.intellij.execution.process.ProcessOutput
import com.intellij.ide.util.PropertiesComponent
import com.intellij.notification.NotificationGroup
import com.intellij.notification.NotificationDisplayType
import com.intellij.notification.NotificationListener
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.editor.event.EditorFactoryEvent
import com.intellij.openapi.editor.event.EditorFactoryListener
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.serviceContainer.AlreadyDisposedException
import com.intellij.util.PathUtil
import com.intellij.util.PlatformUtils
import com.jetbrains.python.PythonModuleTypeBase
import com.jetbrains.python.inspections.PyPackageRequirementsInspection
import com.jetbrains.python.packaging.*
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.add.*
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
import com.jetbrains.python.statistics.modules
import icons.PythonIcons
import org.apache.tuweni.toml.Toml
import org.apache.tuweni.toml.TomlInvalidTypeException
import org.apache.tuweni.toml.TomlParseResult
import org.apache.tuweni.toml.TomlTable
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.Nullable
import org.jetbrains.annotations.SystemDependent
import org.jetbrains.annotations.TestOnly
import org.toml.lang.psi.TomlKey
import org.toml.lang.psi.TomlTableHeader
import java.io.File
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.function.Supplier
import java.util.regex.Pattern
import kotlin.reflect.full.memberProperties
const val PY_PROJECT_TOML: String = "pyproject.toml"
const val POETRY_LOCK: String = "poetry.lock"
const val POETRY_DEFAULT_SOURCE_URL: String = "https://pypi.org/simple"
const val POETRY_PATH_SETTING: String = "PyCharm.Poetry.Path"
const val REPLACE_PYTHON_VERSION = """import re,sys;f=open("pyproject.toml", "r+");orig=f.read();f.seek(0);f.write(re.sub(r"(python = \"\^)[^\"]+(\")", "\g<1>"+'.'.join(str(v) for v in sys.version_info[:2])+"\g<2>", orig))"""
// TODO: Provide a special icon for poetry
// TODO: Need a extension point
val POETRY_ICON = PythonIcons.Python.Virtualenv
/**
* This source code is edited by @koxudaxi (Koudai Aono)
*/
fun getPyProjectTomlForPoetry(virtualFile: VirtualFile): Pair<Long, VirtualFile?> {
return Pair(virtualFile.modificationStamp, try {
ReadAction.compute<VirtualFile, Throwable> {
Toml.parse(virtualFile.inputStream).getTable("tool.poetry")?.let { virtualFile }
}
} catch (e: Throwable) {
null
})
}
/**
* The PyProject.toml found in the main content root of the module.
*/
val pyProjectTomlCache = mutableMapOf<String, Pair<Long, VirtualFile?>>()
val Module.pyProjectToml: VirtualFile?
get() =
baseDir?.findChild(PY_PROJECT_TOML)?.let { virtualFile ->
(this.name + virtualFile.path).let { key ->
pyProjectTomlCache.getOrPut(key, { getPyProjectTomlForPoetry(virtualFile) }).let { pair ->
when (virtualFile.modificationStamp) {
pair.first -> pair.second
else -> pyProjectTomlCache.put(key, getPyProjectTomlForPoetry(virtualFile))?.second
}
}
}
}
/**
* Tells if the SDK was added as a poetry.
*/
/**
* The user-set persisted path to the poetry executable.
*/
var PropertiesComponent.poetryPath: @SystemDependent String?
get() = getValue(POETRY_PATH_SETTING)
set(value) {
setValue(POETRY_PATH_SETTING, value)
}
/**
* Detects the poetry executable in `$PATH`.
*/
fun detectPoetryExecutable(): File? {
val name = when {
SystemInfo.isWindows -> "poetry.bat"
else -> "poetry"
}
return PathEnvironmentVariableUtil.findInPath(name) ?: System.getProperty("user.home")?.let { homePath ->
File(homePath + File.separator + ".poetry" + File.separator + "bin" + File.separator + name).takeIf { it.exists() }
}
}
/**
* Returns the configured poetry executable or detects it automatically.
*/
fun getPoetryExecutable(): File? =
PropertiesComponent.getInstance().poetryPath?.let { File(it) }?.takeIf { it.exists() } ?: detectPoetryExecutable()
fun validatePoetryExecutable(poetryExecutable: @SystemDependent String?): ValidationInfo? {
val message = if (poetryExecutable.isNullOrBlank()) {
"Poetry executable is not found"
}
else {
val file = File(poetryExecutable)
when {
!file.exists() -> "File ${file.absolutePath} is not found"
!file.canExecute() || !file.isFile -> "Cannot execute ${file.absolutePath}"
else -> null
}
}
return message?.let { ValidationInfo(it) }
}
fun suggestedSdkName(basePath: @NlsSafe String): @NlsSafe String = "Poetry (${PathUtil.getFileName(basePath)})"
/**
* Sets up the poetry environment under the modal progress window.
*
* The poetry is associated with the first valid object from this list:
*
* 1. New project specified by [newProjectPath]
* 2. Existing module specified by [module]
* 3. Existing project specified by [project]
*
* @return the SDK for poetry, not stored in the SDK table yet.
*/
fun setupPoetrySdkUnderProgress(project: Project?,
module: Module?,
existingSdks: List<Sdk>,
newProjectPath: String?,
python: String?,
installPackages: Boolean,
poetryPath: String? = null): Sdk? {
val projectPath = newProjectPath ?: module?.basePath ?: project?.basePath ?: return null
val task = object : Task.WithResult<String, ExecutionException>(project, "Setting Up Poetry Environment", true) {
override fun compute(indicator: ProgressIndicator): String {
indicator.isIndeterminate = true
val poetry = when (poetryPath) {
is String -> poetryPath
else -> {
val init = StandardFileSystems.local().findFileByPath(projectPath)?.findChild(PY_PROJECT_TOML)?.let { getPyProjectTomlForPoetry(it) } == null
setupPoetry(FileUtil.toSystemDependentName(projectPath), python, installPackages, init)
}
}
return getPythonExecutable(poetry)
}
}
return createSdkByGenerateTask(task, existingSdks, null, projectPath, suggestedSdkName(projectPath))?.apply {
isPoetry = true
associateWithModule(module ?: project?.modules?.firstOrNull(), newProjectPath)
// project?.let { project ->
// existingSdks.find {
// it.associatedModulePath == projectPath && isPoetry(project, it) && it.homePath == homePath
// }?.run {
// re-use existing invalid sdk
// return null
// }
// PoetryConfigService.getInstance(project).poetryVirtualenvPaths.add(homePath!!)
// }
}
}
/**
* Sets up the poetry environment for the specified project path.
*
* @return the path to the poetry environment.
*/
fun setupPoetry(projectPath: @SystemDependent String, python: String?, installPackages: Boolean, init: Boolean): @SystemDependent String {
if (init) {
runPoetry(projectPath, *listOf("init", "-n").toTypedArray())
if (python != null) {
// Replace python version in toml
runCommand(projectPath, python, "-c", REPLACE_PYTHON_VERSION)
}
}
when {
installPackages -> {
python?.let { runPoetry(projectPath, "env", "use", it) }
runPoetry(projectPath, "install")
}
python != null -> runPoetry(projectPath, "env", "use", python)
else -> runPoetry(projectPath, "run", "python", "-V")
}
return runPoetry(projectPath, "env", "info", "-p")
}
@Deprecated("This function is for backward compatibility")
fun isPoetryFromConfig(project: Project, sdk: Sdk): Boolean {
return PoetryConfigService.getInstance(project).poetryVirtualenvPaths.contains(sdk.homePath)
}
var Sdk.isPoetry: Boolean
get() = sdkAdditionalData is PyPoetrySdkAdditionalData
set(value) {
val oldData = sdkAdditionalData
val newData = if (value) {
when (oldData) {
is PythonSdkAdditionalData -> PyPoetrySdkAdditionalData(oldData)
else -> PyPoetrySdkAdditionalData()
}
} else {
when (oldData) {
is PyPoetrySdkAdditionalData -> PythonSdkAdditionalData(PythonSdkFlavor.getFlavor(this))
else -> oldData
}
}
val modificator = sdkModificator
modificator.sdkAdditionalData = newData
ApplicationManager.getApplication().runWriteAction { modificator.commitChanges() }
}
/**
* Runs the configured poetry for the specified Poetry SDK with the associated project path.
*/
fun runPoetry(sdk: Sdk, vararg args: String): String {
val projectPath = sdk.associatedModulePath
?: throw PyExecutionException("Cannot find the project associated with this Poetry environment",
"Poetry", emptyList(), ProcessOutput())
runPoetry(projectPath, "env", "use", sdk.homePath!!)
return runPoetry(projectPath, *args)
}
/**
* Runs the configured poetry for the specified project path.
*/
fun runPoetry(projectPath: @SystemDependent String?, vararg args: String): String {
val executable = getPoetryExecutable()?.path
?: throw PyExecutionException("Cannot find Poetry", "poetry", emptyList(), ProcessOutput())
val command = listOf(executable) + args
val commandLine = GeneralCommandLine(command).withWorkDirectory(projectPath)
val handler = CapturingProcessHandler(commandLine)
val indicator = ProgressManager.getInstance().progressIndicator
val result = with(handler) {
when {
indicator != null -> {
addProcessListener(IndicatedProcessOutputListener(indicator))
runProcessWithProgressIndicator(indicator)
}
else ->
runProcess()
}
}
return with(result) {
when {
isCancelled ->
throw RunCanceledByUserException()
exitCode != 0 ->
throw PyExecutionException("Error Running Poetry", executable, args.asList(),
stdout, stderr, exitCode, emptyList())
else -> stdout.trim()
}
}
}
fun runCommand(projectPath: @SystemDependent String, command: String, vararg args: String): String {
val commandLine = GeneralCommandLine(listOf(command) + args).withWorkDirectory(projectPath)
val handler = CapturingProcessHandler(commandLine)
val result = with(handler) {
runProcess()
}
return with(result) {
when {
isCancelled ->
throw RunCanceledByUserException()
exitCode != 0 ->
throw PyExecutionException("Error Running", command, args.asList(),
stdout, stderr, exitCode, emptyList())
else -> stdout
}
}
}
/**
* Detects and sets up poetry SDK for a module with Pipfile.
*/
fun detectAndSetupPoetry(project: Project?, module: Module?, existingSdks: List<Sdk>): Sdk? {
if (module?.pyProjectToml == null || getPoetryExecutable() == null) {
return null
}
return setupPoetrySdkUnderProgress(project, module, existingSdks, null, null, false)
}
/**
* The URLs of package sources configured in the Pipfile.lock of the module associated with this SDK.
*/
val Sdk.poetrySources: List<String>
// TODO parse pyproject.toml for tool.poetry.source.url
get() = listOf(POETRY_DEFAULT_SOURCE_URL)
/**
* The list of requirements defined in the poetry.lock of the module associated with this SDK.
*/
//val Sdk.poetryLockRequirements: List<PyRequirement>?
// get() {
// return poetryLock?.let { PyPoetryPackageManager.getInstance(this).getRequirements() }
// }
/**
* A quick-fix for setting up the poetry for the module of the current PSI element.
*/
class UsePoetryQuickFix(sdk: Sdk?, module: Module) : LocalQuickFix {
private val quickFixName = when {
sdk != null && sdk.isAssociatedWithAnotherModule(module) -> "Fix Poetry interpreter"
else -> "Use Poetry interpreter"
}
companion object {
fun isApplicable(module: Module): Boolean = module.pyProjectToml != null
fun setUpPoetry(project: Project, module: Module) {
val sdksModel = ProjectSdksModel().apply {
reset(project)
}
val existingSdks = sdksModel.sdks.filter { it.sdkType is PythonSdkType }
// XXX: Should we show an error message on exceptions and on null?
val newSdk = setupPoetrySdkUnderProgress(project, module, existingSdks, null, null, false)
?: return
val existingSdk = existingSdks.find { it.isPoetry && it.homePath == newSdk.homePath }
val sdk = existingSdk ?: newSdk
if (sdk == newSdk) {
SdkConfigurationUtil.addSdk(newSdk)
} else {
sdk.associateWithModule(module, null)
}
project.pythonSdk = sdk
module.pythonSdk = sdk
PoetryConfigService.getInstance(project).poetryVirtualenvPaths.add(sdk.homePath!!)
}
}
override fun getFamilyName() = quickFixName
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement ?: return
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return
// Invoke the setup later to escape the write action of the quick fix in order to show the modal progress dialog
ApplicationManager.getApplication().invokeLater {
if (project.isDisposed || module.isDisposed) return@invokeLater
setUpPoetry(project, module)
}
}
}
/**
* A quick-fix for installing packages specified in Pipfile.lock.
*/
class PoetryInstallQuickFix : LocalQuickFix {
companion object {
fun poetryInstall(project: Project, module: Module) {
val sdk = module.pythonSdk ?: return
if (!sdk.isPoetry) return
// TODO: create UI
val listener = PyPackageRequirementsInspection.RunningPackagingTasksListener(module)
val ui = PyPackageManagerUI(project, sdk, listener)
ui.install(null, listOf())
}
}
override fun getFamilyName() = "Install requirements from poetry.lock"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement ?: return
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return
poetryInstall(project, module)
}
}
/**
* Watches for edits in PyProjectToml inside modules with a poetry SDK set.
*/
class PyProjectTomlWatcher : EditorFactoryListener {
private val changeListenerKey = Key.create<DocumentListener>("PyProjectToml.change.listener")
private val notificationActive = Key.create<Boolean>("PyProjectToml.notification.active")
private val content: String = if (poetryVersion?.let { it < "1.1.1" } == true) {
"Run <a href='#lock'>poetry lock</a> or <a href='#update'>poetry update</a>"
} else {
"Run <a href='#lock'>poetry lock</a>, <a href='#noupdate'>poetry lock --no-update</a> or <a href='#update'>poetry update</a>"
}
override fun editorCreated(event: EditorFactoryEvent) {
val project = event.editor.project
if (project == null || !isPyProjectTomlEditor(event.editor)) return
val listener = object : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
try {
val document = event.document
val module = document.virtualFile?.getModule(project) ?: return
// TODO: Should we remove listener when a sdk is changed to non-poetry sdk?
// if (!isPoetry(module.project)) {
// with(document) {
// putUserData(notificationActive, null)
// val listener = getUserData(changeListenerKey) ?: return
// removeDocumentListener(listener)
// putUserData(changeListenerKey, null)
// return
// }
// }
if (FileDocumentManager.getInstance().isDocumentUnsaved(document)) {
notifyPyProjectTomlChanged(module)
}
} catch (e: AlreadyDisposedException) {
}
}
}
with(event.editor.document) {
addDocumentListener(listener)
putUserData(changeListenerKey, listener)
}
}
override fun editorReleased(event: EditorFactoryEvent) {
val listener = event.editor.getUserData(changeListenerKey) ?: return
event.editor.document.removeDocumentListener(listener)
}
private fun notifyPyProjectTomlChanged(module: Module) {
if (module.getUserData(notificationActive) == true) return
val what = when {
module.poetryLock == null -> "not found"
else -> "out of date"
}
val title = "$POETRY_LOCK is $what"
val notification = LOCK_NOTIFICATION_GROUP.createNotification(title = title, content = content, listener = NotificationListener { notification, event ->
FileDocumentManager.getInstance().saveAllDocuments()
when (event.description) {
"#lock" ->
runPoetryInBackground(module, listOf("lock"), "Locking $POETRY_LOCK")
"#noupdate" ->
runPoetryInBackground(module, listOf("lock", "--no-update"), "Locking $POETRY_LOCK without updating")
"#update" ->
runPoetryInBackground(module, listOf("update"), "Updating Poetry environment")
}
notification.expire()
module.putUserData(notificationActive, null)
})
module.putUserData(notificationActive, true)
notification.whenExpired {
module.putUserData(notificationActive, null)
}
notification.notify(module.project)
}
private fun isPyProjectTomlEditor(editor: Editor): Boolean {
val file = editor.document.virtualFile ?: return false
if (file.name != PY_PROJECT_TOML) return false
val project = editor.project ?: return false
val module = file.getModule(project) ?: return false
val sdk = module.pythonSdk ?: return false
if (!sdk.isPoetry) return false
return module.pyProjectToml == file
}
}
private val Document.virtualFile: VirtualFile?
get() = FileDocumentManager.getInstance().getFile(this)
private fun VirtualFile.getModule(project: Project): Module? =
ModuleUtil.findModuleForFile(this, project)
private val LOCK_NOTIFICATION_GROUP = NotificationGroup("$PY_PROJECT_TOML Watcher", NotificationDisplayType.STICKY_BALLOON, false)
//private val Sdk.packageManager: PyPoetryPackageManager
// get() = PyPoetryPackageManager.getInstance(this)
//
@TestOnly
fun getPoetryLockRequirements(virtualFile: VirtualFile, packageManager: PyPackageManager): List<PyRequirement>? {
fun getVersion(version: String): String {
return if (Regex("^[0-9]").containsMatchIn(version)) "==$version" else version
}
fun toRequirements(packages: Map<String, PoetryLockPackage>): List<PyRequirement> =
packages
.asSequence()
// .filterNot { (_, pkg) -> pkg.editable ?: false }
// TODO: Support requirements markers (PEP 496), currently any packages with markers are ignored due to PY-30803
// .filter { (_, pkg) -> pkg.markers == null }
.flatMap { (name, pkg) -> packageManager.parseRequirements("$name${pkg.version?.let { getVersion(it) } ?: ""}").asSequence() }
.toList()
//TODO: Support extras
val poetryLock = parsePoetryLock(virtualFile) ?: return null
return poetryLock.packages?.let { toRequirements(it) } ?: emptyList()
}
private fun Sdk.parsePoetryLock(): PoetryLock? {
// TODO: Log errors if poetry.lock is not found
val file = poetryLock ?: return null
return parsePoetryLock(file)
}
val Sdk.poetryLock: VirtualFile?
get() =
associatedModulePath?.let { StandardFileSystems.local().findFileByPath(it)?.findChild(POETRY_LOCK) }
private val Module.poetryLock: VirtualFile?
get() = baseDir?.findChild(POETRY_LOCK)
private fun parsePoetryLock(pyProjectToml: VirtualFile): PoetryLock? {
val text = ReadAction.compute<String, Throwable> { FileDocumentManager.getInstance().getDocument(pyProjectToml)?.text }
return try {
val result: TomlParseResult = Toml.parse(text)
val packages = result.getArrayOrEmpty("package")
if (packages.isEmpty) return null
PoetryLock(packages = packages.toList().filterIsInstance(TomlTable::class.java).map {
Pair(it["name"] as String,
PoetryLockPackage(
version = it["version"] as? String
)
)
}.toMap())
// return try {
// Gson().fromJson(Toml.parse(text).toJson(), PoetryLock::class.java)
} catch (e: Throwable) {
if (e is IllegalArgumentException || e is TomlInvalidTypeException || e is ClassCastException) return null
throw e
}
}
private data class PoetryLock(
@SerializedName("package") var packages: Map<String, PoetryLockPackage>?)
//private data class PoetryLockMeta(@SerializedName("sources") var sources: List<PoetryLockSource>?)
private data class PoetryLockSource(@SerializedName("url") var url: String?)
private data class PoetryLockPackage(@SerializedName("version") var version: String?,
// @SerializedName("category") var category: String?,
// @SerializedName("editable") var editable: Boolean?,
@SerializedName("hashes") var hashes: List<String>? = null,
@SerializedName("markers") var markers: MutableList<Any> = mutableListOf(),
@SerializedName("extras") var extras: List<MutableMap<String, List<String>>>? = null)
fun runPoetryInBackground(module: Module, args: List<String>, description: String) {
val task = object : Task.Backgroundable(module.project, StringUtil.toTitleCase(description), true) {
override fun run(indicator: ProgressIndicator) {
val sdk = module.pythonSdk ?: return
indicator.text = "$description..."
try {
runPoetry(sdk, *args.toTypedArray())
} catch (e: RunCanceledByUserException) {
} catch (e: ExecutionException) {
showSdkExecutionException(sdk, e, "Error Running Poetry")
} finally {
PythonSdkUtil.getSitePackagesDirectory(sdk)?.refresh(true, true)
sdk.associatedModuleDir?.refresh(true, false)
PyPackageManager.getInstance(sdk).refreshAndGetPackages(true)
}
}
}
ProgressManager.getInstance().run(task)
}
private fun allowCreatingNewEnvironments(project: Project?) =
project != null || !PlatformUtils.isPyCharm() || PlatformUtils.isPyCharmEducational()
fun createPoetryPanel(project: Project?,
module: Module?,
existingSdks: List<Sdk>,
newProjectPath: String?,
context: UserDataHolder
): PyAddSdkPanel {
val newPoetryPanel = when {
allowCreatingNewEnvironments(project) -> PyAddNewPoetryPanel(project, module, existingSdks, null, context)
else -> null
}
val existingPoetryPanel = PyAddExistingPoetryEnvPanel(project, module, existingSdks, null, context)
val panels = listOfNotNull(newPoetryPanel, existingPoetryPanel)
val existingSdkPaths = sdkHomes(existingSdks)
val defaultPanel = when {
detectPoetryEnvs(module, existingSdkPaths, project?.basePath
?: newProjectPath).any { it.isAssociatedWithModule(module) } -> existingPoetryPanel
newPoetryPanel != null -> newPoetryPanel
else -> existingPoetryPanel
}
return PyAddSdkGroupPanel(Supplier { "Poetry environment" },
POETRY_ICON, panels, defaultPanel)
}
fun allModules(project: Project?): List<Module> {
return project?.let {
ModuleUtil.getModulesOfType(it, PythonModuleTypeBase.getInstance())
}?.sortedBy { it.name } ?: emptyList()
}
fun sdkHomes(sdks: List<Sdk>): Set<String> = sdks.mapNotNull { it.homePath }.toSet()
fun detectPoetryEnvs(module: Module?, existingSdkPaths: Set<String>, projectPath: String?): List<PyDetectedSdk> {
val path = module?.basePath ?: projectPath ?: return emptyList()
return try {
getPoetryEnvs(path).filterNot { existingSdkPaths.contains(getPythonExecutable(it)) }.map { PyDetectedSdk(it) }
} catch (e: Throwable) {
emptyList()
}
}
fun getPoetryEnvs(projectPath: String): List<String> =
syncRunPoetry(projectPath, "env", "list", "--full-path", defaultResult = emptyList()) { result ->
result.lineSequence().mapNotNull { it.split(" ")[0] }.filterNot { it.isEmpty() }.toList()
}
fun isVirtualEnvsInProject(projectPath: String): Boolean? =
syncRunPoetry(projectPath, "config", "virtualenvs.in-project", defaultResult = null) {
it.trim() == "true"
}
val poetryVersion: String?
get() = syncRunPoetry(null, "--version", defaultResult = "") {
it.split(' ').lastOrNull()
}
inline fun <reified T> syncRunCommand(projectPath: @SystemDependent String, command: String, vararg args: String, defaultResult: T, crossinline callback: (String) -> T): T {
return try {
ApplicationManager.getApplication().executeOnPooledThread<T> {
try {
val result = runCommand(projectPath, command, *args)
callback(result)
} catch (e: PyExecutionException) {
defaultResult
} catch (e: ProcessNotCreatedException) {
defaultResult
}
}.get(30, TimeUnit.SECONDS)
} catch (e: TimeoutException) {
defaultResult
}
}
inline fun <reified T> syncRunPoetry(projectPath: @SystemDependent String?, vararg args: String, defaultResult: T, crossinline callback: (String) -> T): T {
return try {
ApplicationManager.getApplication().executeOnPooledThread<T> {
try {
val result = runPoetry(projectPath, *args)
callback(result)
} catch (e: PyExecutionException) {
defaultResult
} catch (e: ProcessNotCreatedException) {
defaultResult
}
}.get(30, TimeUnit.SECONDS)
} catch (e: TimeoutException) {
defaultResult
}
}
fun getPythonExecutable(homePath: String): String =
PythonSdkUtil.getPythonExecutable(homePath) ?: FileUtil.join(homePath, "bin", "python")
/**
* Parses the output of `poetry show --outdated` into a list of packages.
*/
fun parsePoetryShowOutdated(input: String): Map<String, PoetryOutdatedVersion> {
return input
.lines()
.mapNotNull { line ->
line.split(Pattern.compile(" +"))
.takeIf { it.size > 3 }?.let { it[0] to PoetryOutdatedVersion(it[1], it[2]) }
}.toMap()
}
data class PoetryOutdatedVersion(
@SerializedName("currentVersion") var currentVersion: String,
@SerializedName("latestVersion") var latestVersion: String)
================================================
FILE: testData/Poetry/getPyProjectTomlForPoetry/pyproject.toml
================================================
[tool.poetry]
name = "unittest"
version = "0.1.0"
description = ""
authors = ["Koudai Aono <koxudaxi@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.7"
[tool.poetry.extras]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
================================================
FILE: testData/Poetry/getPyProjectTomlForPoetryBroken/pyproject.toml
================================================
{"name": "unittest"}
================================================
FILE: testData/Poetry/getPyProjectTomlForPoetryInvalid/pyproject.toml
================================================
[tool.invalid]
name = "unittest"
version = "0.1.0"
description = ""
================================================
FILE: testData/Poetry/parsePoetryShoOutdated/show-outdated.txt
================================================
boto3 1.13.26 1.14.38 The AWS SDK for Python
botocore 1.16.26 1.17.38 Low-level, data-driven core of boto 3.
docutils 0.15.2 0.16 Docutils -- Python Documentation Utilities
pydantic 1.4 1.6.1 Data validation and settings management using python 3.6 type hinting
================================================
FILE: testData/PyPoetryPackageManager/parsePoetryInstallDryRun1_0/dry-run-result.txt
================================================
Installing dependencies from lock file
Package operations: 2 installs, 1 updates, 0 removals, 2 skipped
- Skipping six (1.15.0) Already installed
- Skipping attrs (20.2.0) Already installed
- Installing jmespath (0.10.0)
- Installing botocore (1.18.18)
- Updating colorama (0.4.3 -> 0.4.4)
================================================
FILE: testData/PyPoetryPackageManager/parsePoetryInstallDryRun1_1/dry-run-result.txt
================================================
Installing dependencies from lock file
Package operations: 2 installs, 1 updates, 0 removals, 2 skipped
• Installing six (1.15.0): Skipped for the following reason: Already installed
• Installing attrs (20.2.0): Skipped for the following reason: Already installed
• Installing jmespath (0.10.0)
• Installing jmespath (0.10.0)
• Installing botocore (1.18.18)
• Updating colorama (0.4.3 -> 0.4.4)
================================================
FILE: testSrc/com/jetbrains/python/fixtures/PythonMockSdk.java
================================================
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.fixtures;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.projectRoots.SdkTypeId;
import com.intellij.openapi.projectRoots.impl.MockSdk;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.codeInsight.typing.PyTypeShed;
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.sdk.PythonSdkUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class PythonMockSdk {
public static final String PYTHON_SDK_ID_NAME = "Python SDK";
private PythonMockSdk() {
}
public static @NotNull Sdk create(@NotNull String name) {
return create(name, LanguageLevel.getLatest());
}
public static @NotNull Sdk create(@NotNull LanguageLevel level, VirtualFile @NotNull ... additionalRoots) {
return create("MockSdk", level, additionalRoots);
}
private static @NotNull Sdk create(@NotNull String name, @NotNull LanguageLevel level, VirtualFile @NotNull ... additionalRoots) {
return create(name, new PyMockSdkType(level), level, additionalRoots);
}
public static @NotNull Sdk create(@NotNull String pathSuffix, @NotNull SdkTypeId sdkType, @NotNull LanguageLevel level, VirtualFile @NotNull ... additionalRoots) {
String sdkName = "Mock " + PYTHON_SDK_ID_NAME + " " + level.toPythonVersion();
return create(sdkName, pathSuffix, sdkType, level, additionalRoots);
}
public static @NotNull Sdk create(@NotNull String name, @NotNull String pathSuffix, @NotNull SdkTypeId sdkType, @NotNull LanguageLevel level, VirtualFile @NotNull ... additionalRoots) {
final String mockSdkPath = PythonTestUtil.getTestDataPath() + "/" + pathSuffix;
MultiMap<OrderRootType, VirtualFile> roots = MultiMap.create();
roots.putValues(OrderRootType.CLASSES, createRoots(mockSdkPath, level));
roots.putValues(OrderRootType.CLASSES, Arrays.asList(additionalRoots));
MockSdk sdk = new MockSdk(
name,
mockSdkPath + "/bin/python",
toVersionString(level),
roots,
sdkType
);
// com.jetbrains.python.psi.resolve.PythonSdkPathCache.getInstance() corrupts SDK, so have to clone
return sdk.clone();
}
private static @NotNull List<VirtualFile> createRoots(@NotNull @NonNls String mockSdkPath, @NotNull LanguageLevel level) {
final var result = new ArrayList<VirtualFile>();
final var localFS = LocalFileSystem.getInstance();
ContainerUtil.addIfNotNull(result, localFS.refreshAndFindFileByIoFile(new File(mockSdkPath, "Lib")));
ContainerUtil.addIfNotNull(result, localFS.refreshAndFindFileByIoFile(new File(mockSdkPath, PythonSdkUtil.SKELETON_DIR_NAME)));
ContainerUtil.addIfNotNull(result, PyUserSkeletonsUtil.getUserSkeletonsDirectory());
result.addAll(PyTypeShed.INSTANCE.findRootsForLanguageLevel(level));
return result;
}
private static @NotNull String toVersionString(@NotNull LanguageLevel level) {
return "Python " + level.toPythonVersion();
}
private static final class PyMockSdkType implements SdkTypeId {
@NotNull
private final LanguageLevel myLevel;
private PyMockSdkType(@NotNull LanguageLevel level) {
myLevel = level;
}
@NotNull
@Override
public String getName() {
return PYTHON_SDK_ID_NAME;
}
@Nullable
@Override
public @NotNull String getVersionString(@NotNull Sdk sdk) {
return toVersionString(myLevel);
}
@Override
public void saveAdditionalData(@NotNull SdkAdditionalData additionalData, @NotNull Element additional) {
}
@Nullable
@Override
public SdkAdditionalData loadAdditionalData(@NotNull Sdk currentSdk, @NotNull Element additional) {
return null;
}
}
}
================================================
FILE: testSrc/com/jetbrains/python/fixtures/PythonTestUtil.java
================================================
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.fixtures;
/**
* @author yole
*/
public class PythonTestUtil {
private PythonTestUtil() {
}
public static String getTestDataPath() {
return System.getProperty("user.dir") + "/testData";
}
}
================================================
FILE: testSrc/com/koxudaxi/poetry/PoetryTest.kt
================================================
package com.koxudaxi.poetry
import com.intellij.openapi.vfs.VirtualFile
class PoetryTest : PoetryTestCase() {
private val testFile: VirtualFile
get() {
return getTestData("pyproject.toml")
}
fun testGetPyProjectTomlForPoetry() {
val result = getPyProjectTomlForPoetry(testFile)
assertEquals(result.first, 0)
assertEquals(result.second, testFile)
}
fun testGetPyProjectTomlForPoetryInvalid() {
val result = getPyProjectTomlForPoetry(testFile)
assertEquals(result.first, 0)
assertEquals(result.second, null)
}
fun testGetPyProjectTomlForPoetryBroken() {
val result = getPyProjectTomlForPoetry(testFile)
assertEquals(result.first, 0)
assertEquals(result.second, null)
}
private val testShowOutdatedDataAsText: String
get() {
return getTestDataAsText("show-outdated.txt")
}
fun testParsePoetryShoOutdated() {
val result = parsePoetryShowOutdated(testShowOutdatedDataAsText)
assertEquals(result.size, 4)
assertEquals(result,
mapOf(
"boto3" to PoetryOutdatedVersion(currentVersion = "1.13.26", latestVersion = "1.14.38"),
"botocore" to PoetryOutdatedVersion(currentVersion = "1.16.26", latestVersion = "1.17.38"),
"docutils" to PoetryOutdatedVersion(currentVersion = "0.15.2", latestVersion = "0.16"),
"pydantic" to PoetryOutdatedVersion(currentVersion = "1.4", latestVersion = "1.6.1")
)
)
}
}
================================================
FILE: testSrc/com/koxudaxi/poetry/PoetryTestCase.kt
================================================
package com.koxudaxi.poetry
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import java.io.File
abstract class PoetryTestCase : BasePlatformTestCase() {
protected open val testClassName: String = this.javaClass.simpleName.replace("Test", "")
protected val dataDir: String
get() {
return "testData" + File.separator + testClassName + File.separator + getTestName(true)
}
fun getTestData(fileName: String): VirtualFile {
return LocalFileSystemImpl.getInstance().refreshAndFindFileByPath(dataDir + File.separator + fileName)!!
}
fun getTestDataAsText(fileName: String): String {
return getTestData(fileName).inputStream.bufferedReader().readText()
}
}
================================================
FILE: testSrc/com/koxudaxi/poetry/PyPoetryPackageManagerTest.kt
================================================
package com.koxudaxi.poetry
import com.jetbrains.python.fixtures.PythonMockSdk
import com.jetbrains.python.packaging.PyPackage
import com.jetbrains.python.packaging.PyRequirement
import com.jetbrains.python.packaging.PyRequirementParser
class PyPoetryPackageManagerTest : PoetryTestCase() {
private val testDataAsText: String
get() {
return getTestDataAsText("dry-run-result.txt")
}
private fun getPyPackage(name: String, version: String) :PyPackage {
return PyPackage(name, version, null, emptyList())
}
fun getPyRequirement(name: String, version: String): PyRequirement? {
return PyRequirementParser.fromLine("${name}==${version}")
}
fun testParsePoetryInstallDryRun1_0() {
val sdk = PythonMockSdk.create("3.7")
val pyPoetryPackageManager = PyPoetryPackageManager(sdk)
val result = pyPoetryPackageManager.parsePoetryInstallDryRun(testDataAsText)
assertEquals(result.first.size, 3)
assertEquals(result.first,
listOf(
getPyPackage("six", "1.15.0"),
getPyPackage("attrs", "20.2.0"),
getPyPackage("colorama", "0.4.3")
)
)
assertEquals(result.second.size, 5)
assertEquals(result.second,
listOf(
getPyRequirement("six","1.15.0"),
getPyRequirement("attrs","20.2.0"),
getPyRequirement("jmespath","0.10.0"),
getPyRequirement("botocore","1.18.18"),
getPyRequirement("colorama","0.4.4")
)
)
}
fun testParsePoetryInstallDryRun1_1() {
val sdk = PythonMockSdk.create("3.7")
val pyPoetryPackageManager = PyPoetryPackageManager(sdk)
val result = pyPoetryPackageManager.parsePoetryInstallDryRun(testDataAsText)
assertEquals(result.first.size, 3)
assertEquals(result.first,
listOf(
getPyPackage("six", "1.15.0"),
getPyPackage("attrs", "20.2.0"),
getPyPackage("colorama", "0.4.3")
)
)
assertEquals(result.second.size, 5)
assertEquals(result.second,
listOf(
getPyRequirement("six","1.15.0"),
getPyRequirement("attrs","20.2.0"),
getPyRequirement("jmespath","0.10.0"),
getPyRequirement("botocore","1.18.18"),
getPyRequirement("colorama","0.4.4")
)
)
}
}
gitextract_e_ytidwl/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── CI.yml
│ ├── docs.yml
│ └── publish.yml
├── .gitignore
├── LICENSE
├── NOTICE.txt
├── README.md
├── build.gradle
├── docs/
│ ├── development.md
│ ├── index.md
│ └── install.md
├── gradle/
│ └── wrapper/
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── mkdocs.yml
├── requirements.txt
├── resources/
│ ├── META-INF/
│ │ ├── only-toml.xml
│ │ └── plugin.xml
│ └── inspectionDescriptions/
│ └── PoetryPackageVersion.html
├── scripts/
│ └── build_changelog.py
├── settings.gradle
├── src/
│ └── com/
│ └── koxudaxi/
│ └── poetry/
│ ├── PoetryConfigLoader.kt
│ ├── PoetryConfigService.kt
│ ├── PoetryExtrasLineMarkerContributor.kt
│ ├── PoetryInstallExtras.kt
│ ├── PoetryRunScript.kt
│ ├── PoetryScriptsLineMarkerContributor.kt
│ ├── PoetrySdkProvider.kt
│ ├── PoetryVersionInspection.kt
│ ├── PyAddExistingPoetryEnvPanel.kt
│ ├── PyAddNewPoetryFromFilePanel.kt
│ ├── PyAddNewPoetryPanel.kt
│ ├── PyAddPoetrySdkProvider.kt
│ ├── PyPoetryPackageManagementService.kt
│ ├── PyPoetryPackageManager.kt
│ ├── PyPoetryPackageManagerProvider.kt
│ ├── PyPoetrySdkAdditionalData.kt
│ ├── PyPoetrySdkConfiguration.kt
│ ├── PyPoetrySdkFlavor.kt
│ ├── PyPoetrySdkFlavorProvider.kt
│ └── poetry.kt
├── testData/
│ ├── Poetry/
│ │ ├── getPyProjectTomlForPoetry/
│ │ │ └── pyproject.toml
│ │ ├── getPyProjectTomlForPoetryBroken/
│ │ │ └── pyproject.toml
│ │ ├── getPyProjectTomlForPoetryInvalid/
│ │ │ └── pyproject.toml
│ │ └── parsePoetryShoOutdated/
│ │ └── show-outdated.txt
│ └── PyPoetryPackageManager/
│ ├── parsePoetryInstallDryRun1_0/
│ │ └── dry-run-result.txt
│ └── parsePoetryInstallDryRun1_1/
│ └── dry-run-result.txt
└── testSrc/
└── com/
├── jetbrains/
│ └── python/
│ └── fixtures/
│ ├── PythonMockSdk.java
│ └── PythonTestUtil.java
└── koxudaxi/
└── poetry/
├── PoetryTest.kt
├── PoetryTestCase.kt
└── PyPoetryPackageManagerTest.kt
SYMBOL INDEX (27 symbols across 3 files)
FILE: scripts/build_changelog.py
class Tag (line 13) | class Tag(Enum):
function get_markdown_hyperlinks (line 20) | def get_markdown_hyperlinks(text: str) -> List[str]:
class HistoryHTMLParser (line 27) | class HistoryHTMLParser(HTMLParser):
method error (line 28) | def error(self, message):
method __init__ (line 31) | def __init__(self):
method handle_starttag (line 38) | def handle_starttag(self, tag: str, attrs):
method handle_endtag (line 41) | def handle_endtag(self, tag):
method handle_data (line 44) | def handle_data(self, data: str):
function main (line 62) | def main():
FILE: testSrc/com/jetbrains/python/fixtures/PythonMockSdk.java
class PythonMockSdk (line 28) | public final class PythonMockSdk {
method PythonMockSdk (line 30) | private PythonMockSdk() {
method create (line 33) | public static @NotNull Sdk create(@NotNull String name) {
method create (line 37) | public static @NotNull Sdk create(@NotNull LanguageLevel level, Virtua...
method create (line 41) | private static @NotNull Sdk create(@NotNull String name, @NotNull Lang...
method create (line 45) | public static @NotNull Sdk create(@NotNull String pathSuffix, @NotNull...
method create (line 50) | public static @NotNull Sdk create(@NotNull String name, @NotNull Strin...
method createRoots (line 69) | private static @NotNull List<VirtualFile> createRoots(@NotNull @NonNls...
method toVersionString (line 83) | private static @NotNull String toVersionString(@NotNull LanguageLevel ...
class PyMockSdkType (line 87) | private static final class PyMockSdkType implements SdkTypeId {
method PyMockSdkType (line 92) | private PyMockSdkType(@NotNull LanguageLevel level) {
method getName (line 96) | @NotNull
method getVersionString (line 102) | @Nullable
method saveAdditionalData (line 108) | @Override
method loadAdditionalData (line 112) | @Nullable
FILE: testSrc/com/jetbrains/python/fixtures/PythonTestUtil.java
class PythonTestUtil (line 21) | public class PythonTestUtil {
method PythonTestUtil (line 22) | private PythonTestUtil() {
method getTestDataPath (line 25) | public static String getTestDataPath() {
Condensed preview — 57 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (155K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 143,
"preview": "# These are supported funding model platforms\n\ngithub: [koxudaxi]\ncustom: ['https://stakes.social/0x87E30B7640ac3949e159"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 768,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 595,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
},
{
"path": ".github/dependabot.yml",
"chars": 672,
"preview": "version: 2\nupdates:\n- package-ecosystem: gradle\n directory: \"/\"\n schedule:\n interval: daily\n time: \"10:00\"\n t"
},
{
"path": ".github/workflows/CI.yml",
"chars": 1525,
"preview": "name: CI\n\non:\n pull_request: {}\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v2\n "
},
{
"path": ".github/workflows/docs.yml",
"chars": 796,
"preview": "name: Docs\n\non:\n push:\n branches:\n - master\n\njobs:\n build-deploy:\n runs-on: ubuntu-18.04\n steps:\n - use"
},
{
"path": ".github/workflows/publish.yml",
"chars": 2551,
"preview": "name: Publish\n\non:\n push:\n tags:\n - '**'\n\njobs:\n build-n-publish:\n name: Build and publish plugin 📦 to JetB"
},
{
"path": ".gitignore",
"chars": 562,
"preview": "# Compiled class file\n*.class\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Packa"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "NOTICE.txt",
"chars": 128,
"preview": "This software includes code from IntelliJ IDEA Community Edition\nCopyright (C) JetBrains s.r.o.\nhttps://www.jetbrains.co"
},
{
"path": "README.md",
"chars": 5772,
"preview": "# Poetry PyCharm Plugin\n\n[\n[**\n\n## MarketPlace \nThe plug"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Sat May 16 18:47:10 JST 2020\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.8-all.zip\ndistribution"
},
{
"path": "gradle.properties",
"chars": 114,
"preview": "#org.gradle.jvmargs= -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m -Dfile.encoding=utf-8"
},
{
"path": "gradlew",
"chars": 5770,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 3058,
"preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
},
{
"path": "mkdocs.yml",
"chars": 606,
"preview": "site_name: Poetry PyCharm Plugin\nsite_description: A JetBrains PyCharm plugin for poetry.\n\ntheme:\n name: 'material'\n "
},
{
"path": "requirements.txt",
"chars": 37,
"preview": "mkdocs==1.2.3\nmkdocs-material==8.0.5\n"
},
{
"path": "resources/META-INF/only-toml.xml",
"chars": 721,
"preview": "<idea-plugin>\n <extensions defaultExtensionNs=\"com.intellij\">\n <runLineMarkerContributor language=\"TOML\"\n "
},
{
"path": "resources/META-INF/plugin.xml",
"chars": 9192,
"preview": "<idea-plugin url=\"https://github.com/koxudaxi/poetry-pycharm-plugin\" require-restart=\"true\">\n <id>com.koxudaxi.poetry"
},
{
"path": "resources/inspectionDescriptions/PoetryPackageVersion.html",
"chars": 103,
"preview": "<html>\n<body>\nThis inspection checks versions of installed packages and pyproject.toml.\n</body>\n</html>"
},
{
"path": "scripts/build_changelog.py",
"chars": 2292,
"preview": "import re\nimport xml.etree.ElementTree\nfrom enum import Enum\nfrom html.parser import HTMLParser\nfrom pathlib import Path"
},
{
"path": "settings.gradle",
"chars": 42,
"preview": "rootProject.name = 'poetry-pycharm-plugin'"
},
{
"path": "src/com/koxudaxi/poetry/PoetryConfigLoader.kt",
"chars": 981,
"preview": "package com.koxudaxi.poetry\n\n\nimport com.intellij.openapi.application.ApplicationManager\nimport com.intellij.openapi.pro"
},
{
"path": "src/com/koxudaxi/poetry/PoetryConfigService.kt",
"chars": 990,
"preview": "package com.koxudaxi.poetry\n\nimport com.intellij.openapi.components.PersistentStateComponent\nimport com.intellij.openapi"
},
{
"path": "src/com/koxudaxi/poetry/PoetryExtrasLineMarkerContributor.kt",
"chars": 1397,
"preview": "package com.koxudaxi.poetry\n\nimport com.intellij.execution.lineMarker.RunLineMarkerContributor\nimport com.intellij.icons"
},
{
"path": "src/com/koxudaxi/poetry/PoetryInstallExtras.kt",
"chars": 911,
"preview": "package com.koxudaxi.poetry\n\nimport com.intellij.execution.Location\nimport com.intellij.openapi.actionSystem.AnAction\nim"
},
{
"path": "src/com/koxudaxi/poetry/PoetryRunScript.kt",
"chars": 2483,
"preview": "package com.koxudaxi.poetry\n\nimport com.intellij.execution.ExecutionManager\nimport com.intellij.execution.Location\nimpor"
},
{
"path": "src/com/koxudaxi/poetry/PoetryScriptsLineMarkerContributor.kt",
"chars": 1517,
"preview": "package com.koxudaxi.poetry\n\n\nimport com.intellij.execution.lineMarker.RunLineMarkerContributor\nimport com.intellij.icon"
},
{
"path": "src/com/koxudaxi/poetry/PoetrySdkProvider.kt",
"chars": 2637,
"preview": "package com.koxudaxi.poetry\n\nimport com.intellij.codeInspection.LocalQuickFix\nimport com.intellij.openapi.module.Module\n"
},
{
"path": "src/com/koxudaxi/poetry/PoetryVersionInspection.kt",
"chars": 2387,
"preview": "package com.koxudaxi.poetry\n\nimport com.intellij.codeInspection.*\nimport com.intellij.openapi.module.Module\nimport com.i"
},
{
"path": "src/com/koxudaxi/poetry/PyAddExistingPoetryEnvPanel.kt",
"chars": 4519,
"preview": "/*\n * Copyright 2000-2017 JetBrains s.r.o.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you "
},
{
"path": "src/com/koxudaxi/poetry/PyAddNewPoetryFromFilePanel.kt",
"chars": 1438,
"preview": "package com.koxudaxi.poetry\n\n\nimport com.intellij.openapi.fileChooser.FileChooserDescriptorFactory\nimport com.intellij.o"
},
{
"path": "src/com/koxudaxi/poetry/PyAddNewPoetryPanel.kt",
"chars": 8502,
"preview": "// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found "
},
{
"path": "src/com/koxudaxi/poetry/PyAddPoetrySdkProvider.kt",
"chars": 887,
"preview": "// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found "
},
{
"path": "src/com/koxudaxi/poetry/PyPoetryPackageManagementService.kt",
"chars": 2462,
"preview": "// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found "
},
{
"path": "src/com/koxudaxi/poetry/PyPoetryPackageManager.kt",
"chars": 7337,
"preview": "// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found "
},
{
"path": "src/com/koxudaxi/poetry/PyPoetryPackageManagerProvider.kt",
"chars": 381,
"preview": "package com.koxudaxi.poetry\n\nimport com.intellij.openapi.projectRoots.Sdk\nimport com.jetbrains.python.packaging.PyPackag"
},
{
"path": "src/com/koxudaxi/poetry/PyPoetrySdkAdditionalData.kt",
"chars": 1439,
"preview": "// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found "
},
{
"path": "src/com/koxudaxi/poetry/PyPoetrySdkConfiguration.kt",
"chars": 5578,
"preview": "package com.koxudaxi.poetry\n\nimport com.intellij.codeInspection.util.IntentionName\nimport com.jetbrains.python.sdk.confi"
},
{
"path": "src/com/koxudaxi/poetry/PyPoetrySdkFlavor.kt",
"chars": 515,
"preview": "// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found "
},
{
"path": "src/com/koxudaxi/poetry/PyPoetrySdkFlavorProvider.kt",
"chars": 458,
"preview": "// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found "
},
{
"path": "src/com/koxudaxi/poetry/poetry.kt",
"chars": 30205,
"preview": "// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found "
},
{
"path": "testData/Poetry/getPyProjectTomlForPoetry/pyproject.toml",
"chars": 261,
"preview": "[tool.poetry]\nname = \"unittest\"\nversion = \"0.1.0\"\ndescription = \"\"\nauthors = [\"Koudai Aono <koxudaxi@gmail.com>\"]\n\n[tool"
},
{
"path": "testData/Poetry/getPyProjectTomlForPoetryBroken/pyproject.toml",
"chars": 20,
"preview": "{\"name\": \"unittest\"}"
},
{
"path": "testData/Poetry/getPyProjectTomlForPoetryInvalid/pyproject.toml",
"chars": 68,
"preview": "[tool.invalid]\nname = \"unittest\"\nversion = \"0.1.0\"\ndescription = \"\"\n"
},
{
"path": "testData/Poetry/parsePoetryShoOutdated/show-outdated.txt",
"chars": 276,
"preview": "boto3 1.13.26 1.14.38 The AWS SDK for Python\nbotocore 1.16.26 1.17.38 Low-level, data-driven core of boto 3.\ndocutils"
},
{
"path": "testData/PyPoetryPackageManager/parsePoetryInstallDryRun1_0/dry-run-result.txt",
"chars": 303,
"preview": "Installing dependencies from lock file\n\n\nPackage operations: 2 installs, 1 updates, 0 removals, 2 skipped\n\n - Skipping "
},
{
"path": "testData/PyPoetryPackageManager/parsePoetryInstallDryRun1_1/dry-run-result.txt",
"chars": 408,
"preview": "Installing dependencies from lock file\n\nPackage operations: 2 installs, 1 updates, 0 removals, 2 skipped\n\n • Installing"
},
{
"path": "testSrc/com/jetbrains/python/fixtures/PythonMockSdk.java",
"chars": 4646,
"preview": "// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found "
},
{
"path": "testSrc/com/jetbrains/python/fixtures/PythonTestUtil.java",
"chars": 847,
"preview": "/*\n * Copyright 2000-2013 JetBrains s.r.o.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you "
},
{
"path": "testSrc/com/koxudaxi/poetry/PoetryTest.kt",
"chars": 1626,
"preview": "package com.koxudaxi.poetry\n\n\nimport com.intellij.openapi.vfs.VirtualFile\n\n\nclass PoetryTest : PoetryTestCase() {\n pr"
},
{
"path": "testSrc/com/koxudaxi/poetry/PoetryTestCase.kt",
"chars": 843,
"preview": "package com.koxudaxi.poetry\n\nimport com.intellij.openapi.vfs.VirtualFile\nimport com.intellij.openapi.vfs.impl.local.Loca"
},
{
"path": "testSrc/com/koxudaxi/poetry/PyPoetryPackageManagerTest.kt",
"chars": 2675,
"preview": "package com.koxudaxi.poetry\n\n\nimport com.jetbrains.python.fixtures.PythonMockSdk\nimport com.jetbrains.python.packaging.P"
}
]
About this extraction
This page contains the full source code of the koxudaxi/poetry-pycharm-plugin GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 57 files (142.3 KB), approximately 36.1k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.