├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── .idea ├── .gitignore ├── artifacts │ └── gpm_main_jar.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── jarRepositories.xml ├── kotlinc.xml ├── libraries-with-intellij-classes.xml ├── markdown-navigator-enh.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── META-INF └── MANIFEST.MF ├── README.md ├── build.gradle ├── example.gif ├── extras └── logo │ ├── gpm_original_logo.png │ ├── gpmxcf.xcf │ └── npm_original_logo.png ├── gpm_completion ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── icon.png ├── install.sh ├── jdeploy-bundle ├── icon.png └── jdeploy.js ├── package.json ├── registry ├── navigation-material.json └── okhttp.json ├── settings.gradle └── src ├── main └── kotlin │ └── com │ └── theapache64 │ └── gpm │ ├── Main.kt │ ├── commands │ ├── base │ │ ├── BaseCommand.kt │ │ ├── BaseInstallUninstallViewModel.kt │ │ └── BaseViewModel.kt │ ├── gpm │ │ ├── Gpm.kt │ │ ├── GpmComponent.kt │ │ └── GpmViewModel.kt │ └── subcommands │ │ ├── docs │ │ ├── Docs.kt │ │ ├── DocsComponent.kt │ │ └── DocsViewModel.kt │ │ ├── install │ │ ├── Install.kt │ │ ├── InstallComponent.kt │ │ └── InstallViewModel.kt │ │ └── uninstall │ │ ├── Uninstall.kt │ │ ├── UninstallComponent.kt │ │ └── UninstallViewModel.kt │ ├── core │ ├── TransactionManager.kt │ └── gm │ │ ├── GradleDep.kt │ │ └── GradleManager.kt │ ├── data │ ├── remote │ │ ├── gpm │ │ │ ├── GpmApiInterface.kt │ │ │ └── models │ │ │ │ └── GpmDep.kt │ │ └── maven │ │ │ ├── MavenApiInterface.kt │ │ │ └── models │ │ │ ├── ArtifactInfo.kt │ │ │ └── SearchResult.kt │ └── repos │ │ ├── GpmRepo.kt │ │ └── MavenRepo.kt │ ├── di │ ├── Qualifiers.kt │ └── modules │ │ ├── CommandModule.kt │ │ ├── GradleModule.kt │ │ ├── MoshiModule.kt │ │ ├── NetworkModule.kt │ │ └── TransactionModule.kt │ ├── models │ └── GpmFileData.kt │ └── utils │ ├── GpmConfig.kt │ ├── GradleUtils.kt │ ├── InputUtils.kt │ ├── Logger.kt │ ├── StringExtensions.kt │ └── StringUtils.kt └── test ├── kotlin └── com │ └── theapache64 │ └── gpm │ ├── Utils.kt │ ├── commands │ └── subcommands │ │ ├── InstallTest.kt │ │ ├── docs │ │ └── DocsTest.kt │ │ └── uninstall │ │ └── UninstallTest.kt │ ├── core │ └── gm │ │ └── GradleDependencyTest.kt │ ├── data │ └── repos │ │ ├── GpmRepoTest.kt │ │ └── MavenRepoTest.kt │ ├── rules │ ├── MainCoroutineRule.kt │ └── MyDaggerMockRule.kt │ └── utils │ ├── GradleManagerTest.kt │ ├── GradleUtilsTest.kt │ └── StringUtilsTest.kt └── resources └── sample.build.gradle /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Clean Gradle 26 | run: ./gradlew clean 27 | - name: Build with Gradle 28 | run: ./gradlew build 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/kotlin,intellij,gradle 3 | # Edit at https://www.gitignore.io/?templates=kotlin,intellij,gradle 4 | 5 | ### Intellij ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/modules.xml 37 | # .idea/*.iml 38 | # .idea/modules 39 | # *.iml 40 | # *.ipr 41 | 42 | # CMake 43 | cmake-build-*/ 44 | 45 | # Mongo Explorer plugin 46 | .idea/**/mongoSettings.xml 47 | 48 | # File-based project format 49 | *.iws 50 | 51 | # IntelliJ 52 | out/ 53 | 54 | # mpeltonen/sbt-idea plugin 55 | .idea_modules/ 56 | 57 | # JIRA plugin 58 | atlassian-ide-plugin.xml 59 | 60 | # Cursive Clojure plugin 61 | .idea/replstate.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | fabric.properties 68 | 69 | # Editor-based Rest Client 70 | .idea/httpRequests 71 | 72 | # Android studio 3.1+ serialized cache file 73 | .idea/caches/build_file_checksums.ser 74 | 75 | ### Intellij Patch ### 76 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 77 | 78 | # *.iml 79 | # modules.xml 80 | # .idea/misc.xml 81 | # *.ipr 82 | 83 | # Sonarlint plugin 84 | .idea/**/sonarlint/ 85 | 86 | # SonarQube Plugin 87 | .idea/**/sonarIssues.xml 88 | 89 | # Markdown Navigator plugin 90 | .idea/**/markdown-navigator.xml 91 | .idea/**/markdown-navigator/ 92 | 93 | ### Kotlin ### 94 | # Compiled class file 95 | *.class 96 | 97 | # Log file 98 | *.log 99 | 100 | # BlueJ files 101 | *.ctxt 102 | 103 | # Mobile Tools for Java (J2ME) 104 | .mtj.tmp/ 105 | 106 | # Package Files # 107 | *.jar 108 | *.war 109 | *.nar 110 | *.ear 111 | *.zip 112 | *.tar.gz 113 | *.rar 114 | 115 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 116 | hs_err_pid* 117 | 118 | ### Gradle ### 119 | .gradle 120 | build/ 121 | 122 | # Ignore Gradle GUI config 123 | gradle-app.setting 124 | 125 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 126 | !gradle-wrapper.jar 127 | 128 | # Cache of project 129 | .gradletasknamecache 130 | 131 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 132 | # gradle/wrapper/gradle-wrapper.properties 133 | 134 | ### Gradle Patch ### 135 | **/build/ 136 | 137 | # End of https://www.gitignore.io/api/kotlin,intellij,gradle 138 | SecretConstants.kt 139 | src/test/resources/temp.build.gradle 140 | # test generated file 141 | feature/auth/src/test/resources/temp.build.gradle 142 | x.html 143 | src/test/resources/temp.gpm.json 144 | node_modules 145 | package-lock.json 146 | gpm.json -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/artifacts/gpm_main_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$ 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/libraries-with-intellij-classes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/markdown-navigator-enh.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.theapache64.gpm.MainKt 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/github/package-json/v/theapache64/gpm)](https://www.npmjs.com/package/gpm-cli) 2 | 3 |

4 | 5 |

6 | 7 | # gpm 📦 8 | 9 | ![Java CI with Gradle](https://github.com/theapache64/gpm/workflows/Java%20CI%20with%20Gradle/badge.svg) 10 | 11 | GPM is a package manager for Gradle projects. It is inspired by the famous npm package manager. GPM is not a replacement 12 | for the Gradle build system, but a helper to install the dependencies seamlessly to reduce latency. It consists of a 13 | command-line client, also called **gpm**, and an online database of a package directory called the GPM registry. It uses 14 | similar syntax as npm. 15 | 16 | ## Install ⚙️ 17 | 18 | ```shell script 19 | sudo npm install -g gpm-cli 20 | ``` 21 | 22 | ## Usage ⌨️ 23 | 24 | ```shell script 25 | Usage: gpm [-hV] [COMMAND] 26 | -h, --help Show this help message and exit. 27 | -V, --version Print version information and exit. 28 | Commands: 29 | install, i To install the dependency 30 | uninstall, u To uninstall a dependency 31 | docs, d To open library docs 32 | ``` 33 | 34 | **Example** 35 | 36 | To install a library 37 | 38 | ```shell script 39 | $ gpm i 40 | ``` 41 | 42 | *example* 43 | 44 | ```shell script 45 | $ gpm i okhttp 46 | ``` 47 | 48 | ![](example.gif) 49 | 50 | Other supported commands given below 51 | 52 | ## Features ⚡ 53 | 54 | | Command | Description | Implemented | Status | Milestone | 55 | |------------------------------|--------------------------------------------------------------|-------------|---------|----------------| 56 | | `install` | To install the dependency as `implementation` | ✔️ | Done | v1.0.0-alpha01 | 57 | | `install --save-dev` | To install the dependency as `testImplementation` | ✔️ | Done | v1.0.0-alpha01 | 58 | | `install --save-dev-android` | To install the dependency as `androidTestImplementation` | ✔️ | Done | v1.0.0-alpha01 | 59 | | `uninstall` | To uninstall the dependency from `implementation` | ✔️ | Done | v1.0.0-alpha01 | 60 | | `uninstall dev` | To uninstall the dependency from `testImplementation` | ✔️ | Done | v1.0.0-alpha01 | 61 | | `uninstall dev-android` | To uninstall the dependency from `androidTestImplementation` | ✔️ | Done | v1.0.0-alpha01 | 62 | | `docs` | To open the documentation in default browser | ✔️ | Done | v1.0.0-alpha01 | 63 | | `update` | To update the dependency version to latest | ❌ | Pending | - | 64 | | `list` | To list all the dependencies | ❌ | Pending | - | 65 | 66 | ### How can I add my repo to the registry? 🤗 67 | 68 | [Create an issue](https://github.com/theapache64/gpm/issues/new) with below given JSON model as the comment body. 69 | 70 | ``` 71 | { 72 | "name": "YOUR REPO NAME", // Required : Depenedency Name 73 | "github": "GITHUB REPO", // Optional: In format, user/repo 74 | "docs": "DOCUMENTATION-URL", // Optional : Can be full URL or file name. For eg. "README.md", 75 | "group_id": "LIBRARY GROUP ID", // Required : Eg. "com.squareup.okhttp3" 76 | "artifact_id": "ARTIFACT ID", // Required: Eg. okhttp 77 | "get_from" : "WHERES YOUR REPO HOSTED", // Required : Possible value are jcenter, mavenCentral, jitpack 78 | "default_type": "implementation" // Required: Possible values are implementation, testImplementation, androidTestImplementation 79 | } 80 | ``` 81 | 82 | **Example** 83 | 84 | ```json 85 | { 86 | "name": "OkHttp", 87 | "github": "square/okhttp", 88 | "docs": "https://square.github.io/okhttp/", 89 | "groupId": "com.squareup.okhttp3", 90 | "artifactId": "okhttp", 91 | "get_from": "jcenter", 92 | "default_type": "implementation" 93 | } 94 | ``` 95 | 96 | ## Project Status 👷 97 | 98 | This project is under active development. Tap the `👁️ Watch` button to get updates. 99 | 100 | ## Author ✍️ 101 | 102 | - theapache64 103 | 104 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.kotlin.jvm' version '1.9.24' 4 | } 5 | 6 | apply plugin: 'kotlin-kapt' 7 | 8 | group 'com.theapache64.gpm' 9 | version '1.0.6' 10 | 11 | repositories { 12 | mavenCentral() 13 | google() 14 | maven { url "https://jitpack.io" } 15 | } 16 | 17 | kapt { 18 | arguments { 19 | arg("project", "${project.group}/${project.name}") 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.21" 25 | 26 | // Picocli 27 | def picocli_version = '4.6.1' 28 | implementation "info.picocli:picocli:$picocli_version" 29 | kapt "info.picocli:picocli-codegen:$picocli_version" 30 | 31 | // Retrofit 32 | def retrofit_version = '2.9.0' 33 | implementation "com.squareup.retrofit2:retrofit:$retrofit_version" 34 | implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" 35 | implementation "com.squareup.retrofit2:converter-scalars:$retrofit_version" 36 | implementation 'com.squareup.moshi:moshi-kotlin:1.12.0' 37 | 38 | // Inquirer 39 | implementation 'com.github.kotlin-inquirer:kotlin-inquirer:v0.0.2-alpha' 40 | 41 | 42 | // Dagger 43 | def dagger_version = "2.51.1" 44 | implementation "com.google.dagger:dagger:$dagger_version" 45 | kapt "com.google.dagger:dagger-compiler:$dagger_version" 46 | 47 | // Coroutines 48 | def coroutines_version = "1.5.1" 49 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" 50 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" 51 | 52 | // Junit 53 | testImplementation group: 'junit', name: 'junit', version: '4.12' 54 | 55 | // ExpeKt 56 | testImplementation 'com.winterbe:expekt:0.5.0' 57 | 58 | // Mockito inline 59 | testImplementation('org.mockito:mockito-inline:3.11.2') 60 | 61 | // Mockito-koltin 62 | testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" 63 | 64 | // Apache commons text 65 | implementation 'org.apache.commons:commons-text:1.9' 66 | 67 | // DaggerMock 68 | 69 | def dagger_mock_version = '0.8.5' 70 | testImplementation "com.github.fabioCollini.daggermock:daggermock:$dagger_mock_version" 71 | testImplementation "com.github.fabioCollini.daggermock:daggermock-kotlin:$dagger_mock_version" 72 | 73 | // Truth Core:Truth Core 74 | testImplementation 'com.google.truth:truth:1.1.3' 75 | 76 | // Curl Interceptor 77 | implementation 'com.github.mrmike:ok2curl:0.8.0' 78 | } 79 | 80 | compileKotlin { 81 | kotlinOptions.jvmTarget = "1.8" 82 | } 83 | compileTestKotlin { 84 | kotlinOptions.jvmTarget = "1.8" 85 | } 86 | 87 | // Include dependent libraries in archive. 88 | jar { 89 | manifest { 90 | attributes "Main-Class": "com.theapache64.gpm.MainKt" 91 | } 92 | } 93 | 94 | task fatJar(type: Jar) { 95 | manifest.from jar.manifest 96 | archiveFileName.value("gpm.main.jar") 97 | from { 98 | configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } 99 | } { 100 | exclude "META-INF/*.SF" 101 | exclude "META-INF/*.DSA" 102 | exclude "META-INF/*.RSA" 103 | } 104 | with jar 105 | } -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/gpm/ca702ad4f65b5a0ced1d9a8189c0006f2bb34e72/example.gif -------------------------------------------------------------------------------- /extras/logo/gpm_original_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/gpm/ca702ad4f65b5a0ced1d9a8189c0006f2bb34e72/extras/logo/gpm_original_logo.png -------------------------------------------------------------------------------- /extras/logo/gpmxcf.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/gpm/ca702ad4f65b5a0ced1d9a8189c0006f2bb34e72/extras/logo/gpmxcf.xcf -------------------------------------------------------------------------------- /extras/logo/npm_original_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/gpm/ca702ad4f65b5a0ced1d9a8189c0006f2bb34e72/extras/logo/npm_original_logo.png -------------------------------------------------------------------------------- /gpm_completion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # gpm Bash Completion 4 | # ======================= 5 | # 6 | # Bash completion support for the `gpm` command, 7 | # generated by [picocli](http://picocli.info/) version 4.2.0. 8 | # 9 | # Installation 10 | # ------------ 11 | # 12 | # 1. Source all completion scripts in your .bash_profile 13 | # 14 | # cd $YOUR_APP_HOME/bin 15 | # for f in $(find . -name "*_completion"); do line=". $(pwd)/$f"; grep "$line" ~/.bash_profile || echo "$line" >> ~/.bash_profile; done 16 | # 17 | # 2. Open a new bash console, and type `gpm [TAB][TAB]` 18 | # 19 | # 1a. Alternatively, if you have [bash-completion](https://github.com/scop/bash-completion) installed: 20 | # Place this file in a `bash-completion.d` folder: 21 | # 22 | # * /etc/bash-completion.d 23 | # * /usr/local/etc/bash-completion.d 24 | # * ~/bash-completion.d 25 | # 26 | # Documentation 27 | # ------------- 28 | # The script is called by bash whenever [TAB] or [TAB][TAB] is pressed after 29 | # 'gpm (..)'. By reading entered command line parameters, 30 | # it determines possible bash completions and writes them to the COMPREPLY variable. 31 | # Bash then completes the user input if only one entry is listed in the variable or 32 | # shows the options if more than one is listed in COMPREPLY. 33 | # 34 | # References 35 | # ---------- 36 | # [1] http://stackoverflow.com/a/12495480/1440785 37 | # [2] http://tiswww.case.edu/php/chet/bash/FAQ 38 | # [3] https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html 39 | # [4] http://zsh.sourceforge.net/Doc/Release/Options.html#index-COMPLETE_005fALIASES 40 | # [5] https://stackoverflow.com/questions/17042057/bash-check-element-in-array-for-elements-in-another-array/17042655#17042655 41 | # [6] https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html#Programmable-Completion 42 | # [7] https://stackoverflow.com/questions/3249432/can-a-bash-tab-completion-script-be-used-in-zsh/27853970#27853970 43 | # 44 | 45 | if [ -n "$BASH_VERSION" ]; then 46 | # Enable programmable completion facilities when using bash (see [3]) 47 | shopt -s progcomp 48 | elif [ -n "$ZSH_VERSION" ]; then 49 | # Make alias a distinct command for completion purposes when using zsh (see [4]) 50 | setopt COMPLETE_ALIASES 51 | alias compopt=complete 52 | 53 | # Enable bash completion in zsh (see [7]) 54 | autoload -U +X compinit && compinit 55 | autoload -U +X bashcompinit && bashcompinit 56 | fi 57 | 58 | # CompWordsContainsArray takes an array and then checks 59 | # if all elements of this array are in the global COMP_WORDS array. 60 | # 61 | # Returns zero (no error) if all elements of the array are in the COMP_WORDS array, 62 | # otherwise returns 1 (error). 63 | function CompWordsContainsArray() { 64 | declare -a localArray 65 | localArray=("$@") 66 | local findme 67 | for findme in "${localArray[@]}"; do 68 | if ElementNotInCompWords "$findme"; then return 1; fi 69 | done 70 | return 0 71 | } 72 | function ElementNotInCompWords() { 73 | local findme="$1" 74 | local element 75 | for element in "${COMP_WORDS[@]}"; do 76 | if [[ "$findme" = "$element" ]]; then return 1; fi 77 | done 78 | return 0 79 | } 80 | 81 | # The `currentPositionalIndex` function calculates the index of the current positional parameter. 82 | # 83 | # currentPositionalIndex takes three parameters: 84 | # the command name, 85 | # a space-separated string with the names of options that take a parameter, and 86 | # a space-separated string with the names of boolean options (that don't take any params). 87 | # When done, this function echos the current positional index to std_out. 88 | # 89 | # Example usage: 90 | # local currIndex=$(currentPositionalIndex "mysubcommand" "$ARG_OPTS" "$FLAG_OPTS") 91 | function currentPositionalIndex() { 92 | local commandName="$1" 93 | local optionsWithArgs="$2" 94 | local booleanOptions="$3" 95 | local previousWord 96 | local result=0 97 | 98 | for i in $(seq $((COMP_CWORD - 1)) -1 0); do 99 | previousWord=${COMP_WORDS[i]} 100 | if [ "${previousWord}" = "$commandName" ]; then 101 | break 102 | fi 103 | if [[ "${optionsWithArgs}" =~ ${previousWord} ]]; then 104 | ((result-=2)) # Arg option and its value not counted as positional param 105 | elif [[ "${booleanOptions}" =~ ${previousWord} ]]; then 106 | ((result-=1)) # Flag option itself not counted as positional param 107 | fi 108 | ((result++)) 109 | done 110 | echo "$result" 111 | } 112 | 113 | # Bash completion entry point function. 114 | # _complete_gpm finds which commands and subcommands have been specified 115 | # on the command line and delegates to the appropriate function 116 | # to generate possible options and subcommands for the last specified subcommand. 117 | function _complete_gpm() { 118 | local cmds0=(install) 119 | local cmds1=(i) 120 | local cmds2=(uninstall) 121 | local cmds3=(u) 122 | local cmds4=(docs) 123 | local cmds5=(d) 124 | 125 | if CompWordsContainsArray "${cmds5[@]}"; then _picocli_gpm_d; return $?; fi 126 | if CompWordsContainsArray "${cmds4[@]}"; then _picocli_gpm_docs; return $?; fi 127 | if CompWordsContainsArray "${cmds3[@]}"; then _picocli_gpm_u; return $?; fi 128 | if CompWordsContainsArray "${cmds2[@]}"; then _picocli_gpm_uninstall; return $?; fi 129 | if CompWordsContainsArray "${cmds1[@]}"; then _picocli_gpm_i; return $?; fi 130 | if CompWordsContainsArray "${cmds0[@]}"; then _picocli_gpm_install; return $?; fi 131 | 132 | # No subcommands were specified; generate completions for the top-level command. 133 | _picocli_gpm; return $?; 134 | } 135 | 136 | # Generates completions for the options and subcommands of the `gpm` command. 137 | function _picocli_gpm() { 138 | # Get completion data 139 | local curr_word=${COMP_WORDS[COMP_CWORD]} 140 | 141 | local commands="install i uninstall u docs d" 142 | local flag_opts="-h --help -V --version" 143 | local arg_opts="" 144 | 145 | if [[ "${curr_word}" == -* ]]; then 146 | COMPREPLY=( $(compgen -W "${flag_opts} ${arg_opts}" -- "${curr_word}") ) 147 | else 148 | local positionals="" 149 | COMPREPLY=( $(compgen -W "${commands} ${positionals}" -- "${curr_word}") ) 150 | fi 151 | } 152 | 153 | # Generates completions for the options and subcommands of the `install` subcommand. 154 | function _picocli_gpm_install() { 155 | # Get completion data 156 | local curr_word=${COMP_WORDS[COMP_CWORD]} 157 | 158 | local commands="" 159 | local flag_opts="-S --save -D --save-dev -DA --save-dev-android -K --kapt" 160 | local arg_opts="" 161 | 162 | if [[ "${curr_word}" == -* ]]; then 163 | COMPREPLY=( $(compgen -W "${flag_opts} ${arg_opts}" -- "${curr_word}") ) 164 | else 165 | local positionals="" 166 | COMPREPLY=( $(compgen -W "${commands} ${positionals}" -- "${curr_word}") ) 167 | fi 168 | } 169 | 170 | # Generates completions for the options and subcommands of the `i` subcommand. 171 | function _picocli_gpm_i() { 172 | # Get completion data 173 | local curr_word=${COMP_WORDS[COMP_CWORD]} 174 | 175 | local commands="" 176 | local flag_opts="-S --save -D --save-dev -DA --save-dev-android -K --kapt" 177 | local arg_opts="" 178 | 179 | if [[ "${curr_word}" == -* ]]; then 180 | COMPREPLY=( $(compgen -W "${flag_opts} ${arg_opts}" -- "${curr_word}") ) 181 | else 182 | local positionals="" 183 | COMPREPLY=( $(compgen -W "${commands} ${positionals}" -- "${curr_word}") ) 184 | fi 185 | } 186 | 187 | # Generates completions for the options and subcommands of the `uninstall` subcommand. 188 | function _picocli_gpm_uninstall() { 189 | # Get completion data 190 | local curr_word=${COMP_WORDS[COMP_CWORD]} 191 | 192 | local commands="" 193 | local flag_opts="-S --save -D --save-dev -DA --save-dev-android -K --kapt" 194 | local arg_opts="" 195 | 196 | if [[ "${curr_word}" == -* ]]; then 197 | COMPREPLY=( $(compgen -W "${flag_opts} ${arg_opts}" -- "${curr_word}") ) 198 | else 199 | local positionals="" 200 | COMPREPLY=( $(compgen -W "${commands} ${positionals}" -- "${curr_word}") ) 201 | fi 202 | } 203 | 204 | # Generates completions for the options and subcommands of the `u` subcommand. 205 | function _picocli_gpm_u() { 206 | # Get completion data 207 | local curr_word=${COMP_WORDS[COMP_CWORD]} 208 | 209 | local commands="" 210 | local flag_opts="-S --save -D --save-dev -DA --save-dev-android -K --kapt" 211 | local arg_opts="" 212 | 213 | if [[ "${curr_word}" == -* ]]; then 214 | COMPREPLY=( $(compgen -W "${flag_opts} ${arg_opts}" -- "${curr_word}") ) 215 | else 216 | local positionals="" 217 | COMPREPLY=( $(compgen -W "${commands} ${positionals}" -- "${curr_word}") ) 218 | fi 219 | } 220 | 221 | # Generates completions for the options and subcommands of the `docs` subcommand. 222 | function _picocli_gpm_docs() { 223 | # Get completion data 224 | local curr_word=${COMP_WORDS[COMP_CWORD]} 225 | 226 | local commands="" 227 | local flag_opts="" 228 | local arg_opts="" 229 | 230 | if [[ "${curr_word}" == -* ]]; then 231 | COMPREPLY=( $(compgen -W "${flag_opts} ${arg_opts}" -- "${curr_word}") ) 232 | else 233 | local positionals="" 234 | COMPREPLY=( $(compgen -W "${commands} ${positionals}" -- "${curr_word}") ) 235 | fi 236 | } 237 | 238 | # Generates completions for the options and subcommands of the `d` subcommand. 239 | function _picocli_gpm_d() { 240 | # Get completion data 241 | local curr_word=${COMP_WORDS[COMP_CWORD]} 242 | 243 | local commands="" 244 | local flag_opts="" 245 | local arg_opts="" 246 | 247 | if [[ "${curr_word}" == -* ]]; then 248 | COMPREPLY=( $(compgen -W "${flag_opts} ${arg_opts}" -- "${curr_word}") ) 249 | else 250 | local positionals="" 251 | COMPREPLY=( $(compgen -W "${commands} ${positionals}" -- "${curr_word}") ) 252 | fi 253 | } 254 | 255 | # Define a completion specification (a compspec) for the 256 | # `gpm`, `gpm.sh`, and `gpm.bash` commands. 257 | # Uses the bash `complete` builtin (see [6]) to specify that shell function 258 | # `_complete_gpm` is responsible for generating possible completions for the 259 | # current word on the command line. 260 | # The `-o default` option means that if the function generated no matches, the 261 | # default Bash completions and the Readline default filename completions are performed. 262 | complete -F _complete_gpm -o default gpm gpm.sh gpm.bash 263 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/gpm/ca702ad4f65b5a0ced1d9a8189c0006f2bb34e72/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 28 07:14:11 IST 2020 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/gpm/ca702ad4f65b5a0ced1d9a8189c0006f2bb34e72/icon.png -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | echo "Downloading main JAR..." && 2 | wget -q "https://github.com/theapache64/gpm/releases/latest/download/gpm.main.jar" -O "gpm.main.jar" --show-progress && 3 | # cp /home/theapache64/Documents/projects/gpm/gpm.main.jar gpm.main.jar && 4 | 5 | echo "Downloading autocompletion script..." && 6 | wget -q "https://github.com/theapache64/gpm/releases/latest/download/gpm_completion" -O "gpm_completion" --show-progress && 7 | 8 | echo "Moving files to ~/.gmp" && 9 | 10 | mkdir -p ~/.gpm && 11 | mv gpm.main.jar ~/.gpm/gpm.main.jar && 12 | mv gpm_completion ~/.gpm/gpm_completion && 13 | 14 | echo "Installing..." && 15 | echo "alias gpm='java -jar ~/.gpm/gpm.main.jar'" >> ~/.bashrc && 16 | echo ". ~/.gpm/gpm_completion" >> ~/.bashrc && 17 | 18 | echo "Done" 19 | -------------------------------------------------------------------------------- /jdeploy-bundle/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/gpm/ca702ad4f65b5a0ced1d9a8189c0006f2bb34e72/jdeploy-bundle/icon.png -------------------------------------------------------------------------------- /jdeploy-bundle/jdeploy.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var jarName = "gpm.main.jar"; 4 | var mainClass = "{{MAIN_CLASS}}"; 5 | var classPath = "{{CLASSPATH}}"; 6 | var port = "0"; 7 | var warPath = ""; 8 | var javaVersionString = "17"; 9 | var tryJavaHomeFirst = false; 10 | var javafx = false; 11 | var bundleType = 'jre'; 12 | if ('{{JAVAFX}}' === 'true') { 13 | javafx = true; 14 | } 15 | if ('{{JDK}}' === 'true') { 16 | bundleType = 'jdk'; 17 | } 18 | 19 | var jdk = (bundleType === 'jdk'); 20 | var jdkProvider = 'zulu'; 21 | 22 | 23 | function njreWrap() { 24 | 'use strict' 25 | 26 | const path = require('path') 27 | const fs = require('fs') 28 | const os = require('os') 29 | const crypto = require('crypto') 30 | const fetch = require('node-fetch') 31 | const yauzl = require('yauzl') 32 | const tar = require('tar') 33 | 34 | function createDir (dir) { 35 | return new Promise((resolve, reject) => { 36 | fs.access(dir, err => { 37 | if (err && err.code === 'ENOENT') { 38 | fs.mkdir(dir, err => { 39 | if (err) reject(err) 40 | resolve() 41 | }) 42 | } else if (!err) resolve() 43 | else reject(err) 44 | }) 45 | }) 46 | } 47 | 48 | function download (dir, url) { 49 | if (url.indexOf("?") > 0 || jdkProvider === 'zulu') { 50 | var ext = ".zip"; 51 | switch (process.platform) { 52 | case 'linux': 53 | ext = ".tar.gz"; 54 | break; 55 | } 56 | var destName = bundleType + ext; 57 | } else { 58 | destName = path.basename(url); 59 | } 60 | 61 | return new Promise((resolve, reject) => { 62 | createDir(dir) 63 | .then(() => fetch(url)) 64 | .then(response => { 65 | const destFile = path.join(dir, destName) 66 | const destStream = fs.createWriteStream(destFile) 67 | response.body.pipe(destStream).on('finish', () => resolve(destFile)) 68 | }) 69 | .catch(err => reject(err)) 70 | }) 71 | } 72 | 73 | function downloadAll (dir, url) { 74 | return download(dir, url + '.sha256.txt').then(() => download(dir, url)) 75 | } 76 | 77 | function genChecksum (file) { 78 | return new Promise((resolve, reject) => { 79 | fs.readFile(file, (err, data) => { 80 | if (err) reject(err) 81 | 82 | resolve( 83 | crypto 84 | .createHash('sha256') 85 | .update(data) 86 | .digest('hex') 87 | ) 88 | }) 89 | }) 90 | } 91 | 92 | function verify (file) { 93 | return new Promise((resolve, reject) => { 94 | fs.readFile(file + '.sha256.txt', 'utf-8', (err, data) => { 95 | if (err) reject(err) 96 | 97 | genChecksum(file).then(checksum => { 98 | checksum === data.split(' ')[0] 99 | ? resolve(file) 100 | : reject(new Error('File and checksum don\'t match')) 101 | }) 102 | }) 103 | }) 104 | } 105 | 106 | function move (file) { 107 | return new Promise((resolve, reject) => { 108 | const jdeployDir = path.join(os.homedir(), '.jdeploy'); 109 | if (!fs.existsSync(jdeployDir)) { 110 | fs.mkdirSync(jdeployDir); 111 | } 112 | 113 | var jreDir = path.join(jdeployDir, bundleType); 114 | if (!fs.existsSync(jreDir)) { 115 | fs.mkdirSync(jreDir); 116 | } 117 | var vs = javaVersionString; 118 | if (javafx) { 119 | vs += 'fx'; 120 | } 121 | jreDir = path.join(jreDir, vs); 122 | if (!fs.existsSync(jreDir)) { 123 | fs.mkdirSync(jreDir); 124 | } 125 | const newFile = path.join(jreDir, file.split(path.sep).slice(-1)[0]) 126 | //console.log("Copying file "+file+" to "+newFile); 127 | fs.copyFile(file, newFile, err => { 128 | if (err) reject(err) 129 | 130 | fs.unlink(file, err => { 131 | if (err) reject(err) 132 | resolve(newFile) 133 | }) 134 | }) 135 | }) 136 | } 137 | 138 | function extractZip (file, dir) { 139 | //console.log("Extracting "+file+" to "+dir); 140 | return new Promise((resolve, reject) => { 141 | yauzl.open(file, { lazyEntries: true }, (err, zipFile) => { 142 | if (err) reject(err) 143 | 144 | zipFile.readEntry() 145 | zipFile.on('entry', entry => { 146 | const entryPath = path.join(dir, entry.fileName) 147 | 148 | if (/\/$/.test(entry.fileName)) { 149 | fs.mkdir(entryPath, { recursive: true }, err => { 150 | if (err && err.code !== 'EEXIST') reject(err) 151 | 152 | zipFile.readEntry() 153 | }) 154 | } else { 155 | zipFile.openReadStream(entry, (err, readStream) => { 156 | if (err) reject(err) 157 | 158 | readStream.on('end', () => { 159 | zipFile.readEntry() 160 | }) 161 | readStream.pipe(fs.createWriteStream(entryPath)) 162 | }) 163 | } 164 | }) 165 | zipFile.once('close', () => { 166 | fs.unlink(file, err => { 167 | if (err) reject(err) 168 | resolve(dir) 169 | }) 170 | }) 171 | }) 172 | }) 173 | } 174 | 175 | function extractTarGz (file, dir) { 176 | return tar.x({ file: file, cwd: dir }).then(() => { 177 | return new Promise((resolve, reject) => { 178 | fs.unlink(file, err => { 179 | if (err) reject(err) 180 | resolve(dir) 181 | }) 182 | }) 183 | }) 184 | } 185 | 186 | function extract (file) { 187 | var dirString = jdk? 'jdk' : 'jre'; 188 | 189 | const dir = path.join(path.dirname(file), dirString) 190 | //console.log("About to extract "+file+" to "+dir); 191 | return createDir(dir).then(() => { 192 | return path.extname(file) === '.zip' 193 | ? extractZip(file, dir) 194 | : extractTarGz(file, dir) 195 | }) 196 | } 197 | 198 | /** 199 | * Installs a JRE copy for the app 200 | * @param {number} [version = 8] - Java Version (`8`/`9`/`10`/`11`/`12`) 201 | * @param {object} [options] - Installation Options 202 | * @param {string} [options.os] - Operating System (defaults to current) (`windows`/`mac`/`linux`/`solaris`/`aix`) 203 | * @param {string} [options.arch] - Architecture (defaults to current) (`x64`/`x32`/`ppc64`/`s390x`/`ppc64le`/`aarch64`/`sparcv9`) 204 | * @param {string} [options.openjdk_impl = hotspot] - OpenJDK Implementation (`hotspot`/`openj9`) 205 | * @param {string} [options.release = latest] - Release 206 | * @param {string} [options.type = jre] - Binary Type (`jre`/`jdk`) 207 | * @param {string} [options.heap_size] - Heap Size (`normal`/`large`) 208 | * @return Promise - Resolves to the installation directory or rejects an error 209 | * @example 210 | * const njre = require('njre') 211 | * 212 | * // Use default options 213 | * njre.install() 214 | * .then(dir => { 215 | * // Do stuff 216 | * }) 217 | * .catch(err => { 218 | * // Handle the error 219 | * }) 220 | * 221 | * // or custom ones 222 | * njre.install(11, { os: 'aix', arch: 'ppc64', openjdk_impl: 'openj9' }) 223 | * .then(dir => { 224 | * // Do stuff 225 | * }) 226 | * .catch(err => { 227 | * // Handle the error 228 | * }) 229 | */ 230 | function install (version = 11, options = {}) { 231 | const { openjdk_impl = 'hotspot', release = 'latest', type = 'jre', javafx = false, provider = 'zulu' } = options 232 | options = { ...options, openjdk_impl, release, type } 233 | 234 | if (provider === 'zulu') { 235 | return installZulu(version, options); 236 | } 237 | 238 | let url = 'https://api.adoptopenjdk.net/v2/info/releases/openjdk' + version + '?' 239 | 240 | if (!options.os) { 241 | switch (process.platform) { 242 | case 'aix': 243 | options.os = 'aix' 244 | break 245 | case 'darwin': 246 | options.os = 'mac' 247 | break 248 | case 'linux': 249 | options.os = 'linux' 250 | break 251 | case 'sunos': 252 | options.os = 'solaris' 253 | break 254 | case 'win32': 255 | options.os = 'windows' 256 | break 257 | default: 258 | return Promise.reject(new Error('Unsupported operating system')) 259 | } 260 | } 261 | if (!options.arch) { 262 | if (options.os == 'mac') { 263 | // For now, for compatibility reasons use x64 always 264 | options.arch = 'x64'; 265 | } else if (/^ppc64|s390x|x32|x64$/g.test(process.arch)) options.arch = process.arch 266 | else if (process.arch === 'ia32') options.arch = 'x32' 267 | else return Promise.reject(new Error('Unsupported architecture')) 268 | } 269 | 270 | Object.keys(options).forEach(key => { url += key + '=' + options[key] + '&' }) 271 | 272 | const tmpdir = path.join(os.tmpdir(), 'njre') 273 | 274 | return fetch(url) 275 | .then(response => response.json()) 276 | .then(json => downloadAll(tmpdir, json.binaries[0]['binary_link'])) 277 | .then(verify) 278 | .then(move) 279 | .then(extract) 280 | } 281 | 282 | function installZulu(version = 11, options = {}) { 283 | const { type = 'jre', javafx = false } = options 284 | var q = { 285 | 286 | java_version: version, 287 | ext: 'zip', 288 | bundle_type: type, 289 | javafx: ''+javafx, 290 | arch: 'x86', 291 | hw_bitness: '64', 292 | 293 | }; 294 | 295 | 296 | var zuluBaseURL = "https://api.azul.com/zulu/download/community/v1.0/bundles/latest/binary?" 297 | if (!options.os) { 298 | switch (process.platform) { 299 | 300 | case 'darwin': 301 | q.os = 'macos' 302 | break 303 | case 'linux': 304 | q.os = 'linux' 305 | q.ext = 'tar.gz' 306 | break 307 | 308 | case 'win32': 309 | case 'win64': 310 | q.os = 'windows' 311 | break 312 | default: 313 | return Promise.reject(new Error('Unsupported operating system')) 314 | } 315 | } 316 | 317 | 318 | var url = zuluBaseURL; 319 | Object.keys(q).forEach(key => { url += key + '=' + q[key] + '&' }) 320 | const tmpdir = path.join(os.tmpdir(), 'njre') 321 | //console.log("Downloading "+url); 322 | return download(tmpdir, url) 323 | .then(move) 324 | .then(extract) 325 | 326 | } 327 | 328 | return {install:install}; 329 | 330 | 331 | 332 | } 333 | 334 | 335 | var fs = require('fs'); 336 | var os = require('os'); 337 | var path = require('path'); 338 | const njre = njreWrap(); 339 | const targetJavaVersion = parseInt(javaVersionString); 340 | var shell = require("shelljs/global"); 341 | function getJdeploySupportDir() { 342 | return os.homedir() + path.sep + ".jdeploy"; 343 | } 344 | 345 | function getJavaVersion(binPath) { 346 | 347 | var oldPath = env['PATH']; 348 | if (binPath) { 349 | env['PATH'] = binPath + path.delimiter + env['PATH']; 350 | } 351 | 352 | try { 353 | var javaVersionProc = exec('java -version', {silent:true}); 354 | if (javaVersionProc.code !== 0) { 355 | return false; 356 | } 357 | var stdout = javaVersionProc.stderr; 358 | var regexp = /version "(.*?)"/; 359 | var match = regexp.exec(stdout); 360 | var parts = match[1].split('.'); 361 | var join = '.'; 362 | var versionStr = ''; 363 | parts.forEach(function(v) { 364 | versionStr += v; 365 | if (join !== null) { 366 | versionStr += join; 367 | join = null; 368 | } 369 | }); 370 | versionStr = versionStr.replace('_', ''); 371 | return parseFloat(versionStr); 372 | } catch (e) { 373 | return false; 374 | } finally { 375 | env['PATH'] = oldPath; 376 | } 377 | } 378 | var getDirectories = dirPath => fs.readdirSync(dirPath).filter( 379 | file => fs.statSync(path.join(dirPath, file)).isDirectory() 380 | ); 381 | 382 | function getJavaHomeInPath(basepath) { 383 | 384 | var dirs = null; 385 | try { 386 | dirs = getDirectories(basepath); 387 | } catch (e) { 388 | return null; 389 | } 390 | if (dirs && dirs.length > 0) { 391 | basepath = path.join(basepath, dirs[0]); 392 | if (os.platform() != 'darwin') { 393 | return basepath; 394 | } 395 | if (fs.existsSync(path.join(basepath, 'Contents', 'Home'))) { 396 | return path.join(basepath, 'Contents', 'Home'); 397 | } 398 | 399 | var adapterDirectories = getDirectories(basepath).filter(subdir => { 400 | return subdir.match(/^zulu/) && fs.existsSync(path.join(basepath, subdir, 'Contents', 'Home')); 401 | }); 402 | 403 | if (adapterDirectories && adapterDirectories.length > 0) { 404 | return path.join(basepath, adapterDirectories[0], 'Contents', 'Home'); 405 | } 406 | } 407 | return null; 408 | } 409 | 410 | function findSupportedRuntime(javaVersion, jdk, javafx) { 411 | var jdeployDir = path.join(os.homedir(), ".jdeploy"); 412 | var JAVA_HOME_OVERRIDE = env['JDEPLOY_JAVA_HOME_OVERRIDE']; 413 | 414 | if (JAVA_HOME_OVERRIDE && fs.existsSync(JAVA_HOME_OVERRIDE)) { 415 | return JAVA_HOME_OVERRIDE; 416 | } 417 | 418 | // First check for the full-meal deal 419 | var _javaHomePath = getJavaHomeInPath(path.join(jdeployDir, 'jdk', javaVersion+'fx', 'jdk')); 420 | if (_javaHomePath && fs.existsSync(_javaHomePath)) { 421 | return _javaHomePath; 422 | } 423 | if (!javafx) { 424 | var _javaHomePath = getJavaHomeInPath(path.join(jdeployDir, 'jdk', javaVersion, 'jdk')); 425 | if (_javaHomePath && fs.existsSync(_javaHomePath)) { 426 | return _javaHomePath; 427 | } 428 | } 429 | 430 | if (!jdk) { 431 | var _javaHomePath = getJavaHomeInPath(path.join(jdeployDir, 'jre', javaVersion+'fx', 'jre')); 432 | if (_javaHomePath && fs.existsSync(_javaHomePath)) { 433 | return _javaHomePath; 434 | } 435 | } 436 | 437 | if (!jdk && !javafx) { 438 | var _javaHomePath = getJavaHomeInPath(path.join(jdeployDir, 'jre', javaVersion, 'jre')); 439 | if (_javaHomePath && fs.existsSync(_javaHomePath)) { 440 | return _javaHomePath; 441 | } 442 | } 443 | return null; 444 | 445 | } 446 | 447 | function getEmbeddedJavaHome() { 448 | var _platform = os.platform(); 449 | var _driver = ''; 450 | switch (_platform) { 451 | case 'darwin': _platform = 'macosx'; _driver = 'Contents' + path.sep + 'Home'; break; 452 | case 'win32': _platform = 'windows'; _driver = ''; break; 453 | case 'linux': _driver = ''; break; 454 | default: 455 | fail('unsupported platform: ' + _platform); 456 | } 457 | var vs = javaVersionString; 458 | if (javafx) { 459 | vs += 'fx'; 460 | } 461 | var typeDir = jdk ? 'jdk' : 'jre'; 462 | 463 | var jreDir = path.join(os.homedir(), '.jdeploy', 'jre', vs, 'jre'); 464 | try { 465 | var out = jreDir + path.sep + getDirectories(jreDir)[0] + (_driver ? (path.sep + _driver) : ''); 466 | return out; 467 | } catch (e) { 468 | return null; 469 | } 470 | } 471 | 472 | function javaVersionMatch(v1, v2) { 473 | if (v1 === 8) v1 = 1.8; 474 | if (v2 === 8) v2 = 1.8; 475 | if (Math.floor(v1) !== Math.floor(v2)) { 476 | 477 | return false; 478 | } 479 | if (v1 < 2) { 480 | // Up to 1.8, the version would be like 1.7, 1.8, etc.. 481 | // So we need to check the minor version for equivalency 482 | return (Math.floor(v1*10) === Math.floor(v2*10)); 483 | } else { 484 | // Starting with Java 9, the version is like 9, 10, 11, etc.. 485 | // so we just compare major version. 486 | return (Math.floor(v1) === Math.floor(v2)); 487 | } 488 | 489 | } 490 | 491 | var done = false; 492 | if (tryJavaHomeFirst) { 493 | if (env['JAVA_HOME']) { 494 | var javaHomeVersion = getJavaVersion(path.join(env['JAVA_HOME'], 'bin')); 495 | if (javaVersionMatch(javaHomeVersion, targetJavaVersion)) { 496 | done = true; 497 | env['PATH'] = path.join(env['JAVA_HOME'], 'bin') + path.delimiter + env['PATH']; 498 | run(env['JAVA_HOME']); 499 | 500 | } 501 | } 502 | 503 | if (!done) { 504 | var javaVersion = getJavaVersion(); 505 | if (javaVersionMatch(javaVersion, targetJavaVersion)) { 506 | done = true; 507 | run(); 508 | } 509 | } 510 | } 511 | 512 | 513 | if (!done) { 514 | 515 | var _javaHome = findSupportedRuntime(javaVersionString, bundleType === 'jdk', javafx); 516 | if (_javaHome && fs.existsSync(_javaHome)) { 517 | var javaVersion = getJavaVersion(path.join(_javaHome, 'bin')); 518 | if (javaVersionMatch(javaVersion, targetJavaVersion)) { 519 | env['PATH'] = path.join(_javaHome, 'bin') + path.delimiter + env['PATH']; 520 | env['JAVA_HOME'] = _javaHome; 521 | done = true; 522 | run(_javaHome); 523 | } 524 | } 525 | 526 | } 527 | 528 | if (!done) { 529 | console.log("Downloading java runtime environment for version "+targetJavaVersion); 530 | njre.install(targetJavaVersion, {type: bundleType, javafx: javafx}).then(function(dir) { 531 | var _javaHome = getJavaHomeInPath(dir); 532 | if (_javaHome == null) 533 | 534 | if (!_javaHome || !fs.existsSync(_javaHome)) { 535 | throw new Error("After install, could not find java home at "+_javaHome); 536 | } 537 | env['JAVA_HOME'] = _javaHome; 538 | 539 | var javaBinary = path.join(_javaHome, 'bin', 'java'); 540 | if (!fs.existsSync(javaBinary)) { 541 | javaBinary += '.exe'; 542 | 543 | } 544 | fs.chmodSync(javaBinary, 0o755); 545 | 546 | env['PATH'] = path.join(env['JAVA_HOME'], 'bin') + path.delimiter + env['PATH']; 547 | 548 | run(env['JAVA_HOME']); 549 | }).catch(function(err) { 550 | console.log("Failed to install JRE", err); 551 | }); 552 | } 553 | 554 | 555 | 556 | 557 | function run(_javaHome) { 558 | var fail = reason => { 559 | console.error(reason); 560 | process.exit(1); 561 | }; 562 | 563 | 564 | classPath = classPath.split(':'); 565 | var classPathStr = ''; 566 | var first = true; 567 | classPath.forEach(function(part) { 568 | if (!first) classPathStr += path.delimiter; 569 | first = false; 570 | classPathStr += __dirname + '/' + part; 571 | }); 572 | classPath = classPathStr; 573 | 574 | var userArgs = process.argv.slice(2); 575 | var javaArgs = []; 576 | javaArgs.push('-Djdeploy.base='+__dirname); 577 | javaArgs.push('-Djdeploy.port='+port); 578 | javaArgs.push('-Djdeploy.war.path='+warPath); 579 | var programArgs = []; 580 | userArgs.forEach(function(arg) { 581 | if (arg.startsWith('-D') || arg.startsWith('-X')) { 582 | javaArgs.push(arg); 583 | } else { 584 | programArgs.push(arg); 585 | } 586 | }); 587 | var cmd = 'java'; 588 | 589 | if (!_javaHome) { 590 | env['PATH'] = path.join(getEmbeddedJavaHome(), 'bin') + path.delimiter + env['PATH']; 591 | if (env['JAVA_HOME']) { 592 | env['PATH'] = env['JAVA_HOME'] + path.sep + 'bin' + path.delimiter + env['PATH']; 593 | } 594 | 595 | } else { 596 | env['JAVA_HOME'] = _javaHome; 597 | cmd = _javaHome + path.sep + 'bin' + path.sep + 'java'; 598 | } 599 | 600 | javaArgs.forEach(function(arg) { 601 | cmd += ' "'+arg+'"'; 602 | }); 603 | if (jarName !== '{'+'{JAR_NAME}}') { 604 | cmd += ' -jar "'+__dirname+'/'+jarName+'" '; 605 | } else { 606 | cmd += ' -cp "'+classPath+'" '+mainClass+' '; 607 | } 608 | 609 | programArgs.forEach(function(arg) { 610 | cmd += ' "'+arg+'"'; 611 | }); 612 | var child = exec(cmd, {async: true}); 613 | process.stdin.setEncoding('utf8'); 614 | 615 | process.stdin.on('readable', function() { 616 | var chunk = null; 617 | while (null !== (chunk = process.stdin.read())) { 618 | try { 619 | child.stdin.write(chunk); 620 | } catch(e){} 621 | } 622 | }); 623 | child.on('close', function(code) { 624 | process.exit(code); 625 | }); 626 | 627 | } 628 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "bin": { 3 | "gpm": "jdeploy-bundle/jdeploy.js" 4 | }, 5 | "author": "theapache64", 6 | "description": "GPM is a package manager for Gradle projects", 7 | "main": "index.js", 8 | "preferGlobal": "true", 9 | "repository": "https://github.com/theapache64/gpm", 10 | "version": "1.0.7", 11 | "jdeploy": { 12 | "jdk": "false", 13 | "javaVersion": "17", 14 | "jar": "gpm.main.jar", 15 | "javafx": "false", 16 | "title": "GPM" 17 | }, 18 | "dependencies": { 19 | "command-exists-promise": "2.0.2", 20 | "node-fetch": "2.6.7", 21 | "tar": "4.4.8", 22 | "yauzl": "2.10.0", 23 | "shelljs": "0.8.4" 24 | }, 25 | "license": "ISC", 26 | "name": "gpm-cli", 27 | "files": [ 28 | "jdeploy-bundle" 29 | ], 30 | "scripts": { 31 | "test": "echo \"Error: no test specified\" && exit 1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /registry/navigation-material.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Navigation Material", 3 | "github": "google/accompanist", 4 | "docs": "https://google.github.io/accompanist/navigation-material/", 5 | "group_id": "com.google.accompanist", 6 | "artifact_id": "accompanist-navigation-material", 7 | "get_from": "maven", 8 | "default_type": "implementation", 9 | "description": "Compose Material support for Jetpack Navigation Compose" 10 | } -------------------------------------------------------------------------------- /registry/okhttp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OkHttp", 3 | "github": "square/okhttp", 4 | "docs": "https://square.github.io/okhttp/", 5 | "group_id": "com.squareup.okhttp3", 6 | "artifact_id": "okhttp", 7 | "get_from": "maven", 8 | "default_type": "implementation", 9 | "description": "For networking" 10 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'gpm' 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/Main.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm 2 | 3 | import com.theapache64.gpm.commands.gpm.Gpm 4 | import picocli.CommandLine 5 | import kotlin.system.exitProcess 6 | 7 | fun main(args: Array) { 8 | 9 | val cmd = CommandLine(Gpm(false)) 10 | if (args.isEmpty()) { 11 | cmd.usage(System.out) 12 | } else { 13 | val exitCode = cmd.execute(*args) 14 | exitProcess(exitCode) 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/base/BaseCommand.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.base 2 | 3 | import com.theapache64.gpm.utils.InputUtils 4 | import kotlinx.coroutines.delay 5 | import java.util.concurrent.Callable 6 | 7 | abstract class BaseCommand( 8 | val isFromTest: Boolean 9 | ) : Callable { 10 | 11 | suspend fun chooseIndex(items: List): Int { 12 | 13 | println("Choose: ") 14 | 15 | items.forEachIndexed() { index: Int, string: String -> 16 | println("${index + 1}) $string") 17 | } 18 | 19 | @Suppress("ConstantConditionIf") 20 | return if (isFromTest) { 21 | delay(1000) 22 | 0 23 | } else { 24 | InputUtils.getInt("Choose #", 1, items.size) - 1 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/base/BaseInstallUninstallViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.base 2 | 3 | import com.theapache64.gpm.core.gm.GradleDep 4 | 5 | abstract class BaseInstallUninstallViewModel : BaseViewModel() { 6 | 7 | fun getDepTypes( 8 | isSave: Boolean, 9 | isSaveDev: Boolean, 10 | isSaveDevAndroid: Boolean, 11 | isKapt: Boolean, 12 | defaultType: String? 13 | ): List = mutableListOf().apply { 14 | 15 | if (isSave) { 16 | add(GradleDep.Type.IMP) 17 | } 18 | 19 | if (isSaveDev) { 20 | add(GradleDep.Type.TEST_IMP) 21 | } 22 | 23 | if (isSaveDevAndroid) { 24 | add(GradleDep.Type.AND_TEST_IMP) 25 | } 26 | 27 | if (isKapt) { 28 | add(GradleDep.Type.KAPT) 29 | } 30 | 31 | // Still empty 32 | if (isEmpty() && !defaultType.isNullOrBlank()) { 33 | // setting default dependency 34 | val depType = GradleDep.Type.values().find { it.key == defaultType.trim() } 35 | ?: throw IllegalArgumentException("Invalid default type '${defaultType}'") 36 | 37 | add(depType) 38 | } 39 | 40 | // Still Empty? 41 | if (isEmpty()) { 42 | // adding default 43 | add(GradleDep.Type.IMP) 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/base/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.base 2 | 3 | abstract class BaseViewModel { 4 | abstract suspend fun call(command: T): Int 5 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/gpm/Gpm.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.gpm 2 | 3 | import com.theapache64.gpm.commands.base.BaseCommand 4 | import com.theapache64.gpm.commands.subcommands.docs.Docs 5 | import com.theapache64.gpm.commands.subcommands.install.Install 6 | import com.theapache64.gpm.commands.subcommands.uninstall.Uninstall 7 | import kotlinx.coroutines.runBlocking 8 | import picocli.CommandLine 9 | import javax.inject.Inject 10 | import javax.inject.Singleton 11 | 12 | @CommandLine.Command( 13 | name = "gpm", 14 | version = ["v1.0.7"], 15 | mixinStandardHelpOptions = true, 16 | subcommands = [ 17 | Install::class, 18 | Uninstall::class, 19 | Docs::class 20 | ] 21 | ) 22 | @Singleton 23 | class Gpm constructor(isFromTest: Boolean = false) : BaseCommand(isFromTest) { 24 | 25 | 26 | init { 27 | DaggerGpmComponent.create().inject(this) 28 | } 29 | 30 | @CommandLine.Parameters( 31 | index = "0", 32 | description = ["Module path or directory, separated and started by ':'. eg: :app , :feature:login"], 33 | defaultValue = "" 34 | ) 35 | var modulePath: String? = null 36 | 37 | @Inject 38 | lateinit var viewModel: GpmViewModel 39 | 40 | override fun call(): Int = runBlocking { 41 | viewModel.call(this@Gpm) 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/gpm/GpmComponent.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.gpm 2 | 3 | import dagger.Component 4 | import javax.inject.Singleton 5 | 6 | @Singleton 7 | @Component 8 | interface GpmComponent { 9 | fun inject(gpm: Gpm) 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/gpm/GpmViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.gpm 2 | 3 | import com.theapache64.gpm.commands.base.BaseViewModel 4 | import javax.inject.Inject 5 | 6 | class GpmViewModel @Inject constructor() : BaseViewModel() { 7 | override suspend fun call(command: Gpm): Int { 8 | return 0 9 | } 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/subcommands/docs/Docs.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.docs 2 | 3 | import com.theapache64.gpm.commands.base.BaseCommand 4 | import com.theapache64.gpm.di.modules.TransactionModule 5 | import kotlinx.coroutines.runBlocking 6 | import picocli.CommandLine 7 | import javax.inject.Inject 8 | 9 | @CommandLine.Command( 10 | name = "docs", 11 | aliases = ["d"], 12 | description = ["To open library docs"] 13 | ) 14 | class Docs( 15 | isFromTest: Boolean = false 16 | ) : BaseCommand(isFromTest) { 17 | @CommandLine.Parameters(index = "0", description = ["Dependency name"]) 18 | lateinit var depName: String 19 | 20 | @Inject 21 | lateinit var docsViewModel: DocsViewModel 22 | 23 | init { 24 | DaggerDocsComponent.builder() 25 | .transactionModule(TransactionModule(isFromTest)) 26 | .build() 27 | .inject(this) 28 | } 29 | 30 | override fun call(): Int = runBlocking { 31 | docsViewModel.call(this@Docs) 32 | } 33 | 34 | fun onDocUrl(docsUrl: String) { 35 | println("Docs -> $docsUrl") 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/subcommands/docs/DocsComponent.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.docs 2 | 3 | import com.theapache64.gpm.core.TransactionManager 4 | import com.theapache64.gpm.di.GpmJsonFile 5 | import com.theapache64.gpm.di.modules.NetworkModule 6 | import com.theapache64.gpm.di.modules.TransactionModule 7 | import dagger.Component 8 | import java.io.File 9 | import javax.inject.Singleton 10 | 11 | @Singleton 12 | @Component(modules = [TransactionModule::class, NetworkModule::class]) 13 | interface DocsComponent { 14 | 15 | fun transactionManager(): TransactionManager 16 | fun inject(docs: Docs) 17 | 18 | @GpmJsonFile 19 | fun gpmJsonFile(): File 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/subcommands/docs/DocsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.docs 2 | 3 | import com.theapache64.gpm.commands.base.BaseViewModel 4 | import com.theapache64.gpm.core.TransactionManager 5 | import com.theapache64.gpm.data.repos.GpmRepo 6 | import javax.inject.Inject 7 | 8 | class DocsViewModel @Inject constructor( 9 | private val tm: TransactionManager, 10 | private val gpmRepo: GpmRepo 11 | ) : BaseViewModel() { 12 | 13 | companion object { 14 | const val RESULT_DOC_FOUND = 123 15 | const val RESULT_NOT_FOUND = 404 16 | } 17 | 18 | override suspend fun call(command: Docs): Int { 19 | 20 | val depName = command.depName.trim().toLowerCase() 21 | val transaction = tm.getInstalled(null, depName) 22 | 23 | if (transaction.isEmpty()) { 24 | // check if remote registry got info about the repo in question 25 | val remoteDep = gpmRepo.getDep(depName) ?: return RESULT_NOT_FOUND 26 | 27 | command.onDocUrl(remoteDep.docs) 28 | return RESULT_DOC_FOUND 29 | 30 | } else { 31 | 32 | val index = if (transaction.size > 1) { 33 | command.chooseIndex(transaction.map { 34 | "${it.type} ${it.gpmDep.groupId}:${it.gpmDep.artifactId}" 35 | }) 36 | } else { 37 | 0 38 | } 39 | 40 | val selDep = transaction[index] 41 | command.onDocUrl(selDep.gpmDep.docs) 42 | 43 | return RESULT_DOC_FOUND 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/subcommands/install/Install.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.install 2 | 3 | import com.theapache64.gpm.commands.base.BaseCommand 4 | import com.theapache64.gpm.commands.gpm.Gpm 5 | import com.theapache64.gpm.core.gm.GradleDep 6 | import com.theapache64.gpm.di.modules.CommandModule 7 | import com.theapache64.gpm.di.modules.GradleModule 8 | import com.theapache64.gpm.di.modules.TransactionModule 9 | import kotlinx.coroutines.runBlocking 10 | import picocli.CommandLine 11 | import javax.inject.Inject 12 | 13 | @CommandLine.Command( 14 | name = "install", 15 | aliases = ["i"], 16 | description = ["To install the dependency"] 17 | ) 18 | class Install(isFromTest: Boolean = false) : BaseCommand(isFromTest) { 19 | 20 | 21 | @CommandLine.ParentCommand 22 | var parent: Gpm? = null 23 | 24 | @CommandLine.Option( 25 | names = ["-S", "-s", "--save"], 26 | description = ["To install the dependency as 'implementation'"] 27 | ) 28 | var isSave: Boolean = false 29 | 30 | @CommandLine.Option( 31 | names = ["-FS", "-fs", "--force-search"], 32 | description = ["To skip gpm registry search check and quick search with other repos"] 33 | ) 34 | var isForceSearch: Boolean = true 35 | 36 | @CommandLine.Option( 37 | names = ["-po"], 38 | description = ["To print only. No files will be modified"] 39 | ) 40 | var isPrintOnly: Boolean = false 41 | 42 | @CommandLine.Option( 43 | names = ["-D", "-d", "--save-dev"], 44 | description = ["To install the dependency as 'testImplementation'"] 45 | ) 46 | var isSaveDev: Boolean = false 47 | 48 | @CommandLine.Option( 49 | names = ["-DA", "-da", "--save-dev-android"], 50 | description = ["To install the dependency as 'androidTestImplementation'"] 51 | ) 52 | var isSaveDevAndroid: Boolean = false 53 | 54 | @CommandLine.Option( 55 | names = ["-K", "-k", "--kapt"], 56 | description = ["To install the dependency as 'kapt'"] 57 | ) 58 | var isKapt: Boolean = false 59 | 60 | @CommandLine.Parameters(index = "0", description = ["Dependency name"]) 61 | lateinit var depName: String 62 | 63 | @Inject 64 | lateinit var installViewModel: InstallViewModel 65 | private var modulePath: String? = null 66 | 67 | override fun call(): Int = runBlocking { 68 | modulePath = parent?.modulePath?.ifBlank { null } 69 | if (!isFromTest) { 70 | DaggerInstallComponent 71 | .builder() 72 | .commandModule(CommandModule(isFromTest = isFromTest)) 73 | .gradleModule(GradleModule(isFromTest = isFromTest, modulePath)) 74 | .transactionModule(TransactionModule(isFromTest)) 75 | .build() 76 | .inject(this@Install) 77 | } 78 | 79 | installViewModel.call(this@Install) 80 | } 81 | 82 | fun onBeforeGetDep() { 83 | if (modulePath != null) { 84 | println("➡️ Module: $modulePath") 85 | } 86 | 87 | println("🔍 Searching for '$depName'") 88 | } 89 | 90 | fun onDepGot() { 91 | println("✔️ Found dependency") 92 | } 93 | 94 | fun onBeforeSearchingInGpmRegistry() { 95 | println("🔍 Searching in gpm registry for '$depName'...") 96 | } 97 | 98 | fun onBeforeSearchingInMavenCentral() { 99 | println("🔍 Searching in maven for '$depName'") 100 | } 101 | 102 | fun onDepNotFoundAnywhere() { 103 | println("❌ Couldn't find dependency with name '$depName'") 104 | } 105 | 106 | fun onBeforeAddDependency(depType: GradleDep.Type) { 107 | println("⌨️ Adding ${depType.key} to build.gradle...") 108 | } 109 | 110 | fun onAfterDependenciesAdded(newlyAddedLines: String) { 111 | val coloredLines = CommandLine.Help.Ansi.AUTO.string("@|bold,green $newlyAddedLines|@") 112 | println("✅ Added $coloredLines to build.gradle!") 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/subcommands/install/InstallComponent.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.install 2 | 3 | import com.squareup.moshi.Moshi 4 | import com.theapache64.gpm.commands.gpm.Gpm 5 | import com.theapache64.gpm.core.TransactionManager 6 | import com.theapache64.gpm.data.remote.gpm.GpmApiInterface 7 | import com.theapache64.gpm.data.remote.maven.MavenApiInterface 8 | import com.theapache64.gpm.data.repos.MavenRepo 9 | import com.theapache64.gpm.di.GpmJsonFile 10 | import com.theapache64.gpm.di.GradleFile 11 | import com.theapache64.gpm.di.modules.CommandModule 12 | import com.theapache64.gpm.di.modules.GradleModule 13 | import com.theapache64.gpm.di.modules.MoshiModule 14 | import com.theapache64.gpm.di.modules.NetworkModule 15 | import dagger.Component 16 | import java.io.File 17 | import javax.inject.Singleton 18 | 19 | @Singleton 20 | @Component( 21 | modules = [ 22 | NetworkModule::class, 23 | MoshiModule::class, 24 | GradleModule::class, 25 | CommandModule::class 26 | ] 27 | ) 28 | interface InstallComponent { 29 | /* 30 | * For testing 31 | */ 32 | fun getGpm(): Gpm 33 | fun gpmApiInterface(): GpmApiInterface 34 | fun mavenApiInterface(): MavenApiInterface 35 | 36 | @GradleFile 37 | fun gradleFile(): File 38 | 39 | @GpmJsonFile 40 | fun gpmJsonFile(): File 41 | 42 | fun mavenRepo(): MavenRepo 43 | fun gpmFileManager(): TransactionManager 44 | 45 | fun inject(install: Install) 46 | fun moshi(): Moshi 47 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/subcommands/install/InstallViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.install 2 | 3 | import com.theapache64.gpm.commands.base.BaseInstallUninstallViewModel 4 | import com.theapache64.gpm.core.gm.GradleDep 5 | import com.theapache64.gpm.core.gm.GradleManager 6 | import com.theapache64.gpm.data.remote.gpm.models.GpmDep 7 | import com.theapache64.gpm.data.repos.GpmRepo 8 | import com.theapache64.gpm.data.repos.MavenRepo 9 | import com.theapache64.gpm.utils.GradleUtils 10 | import picocli.CommandLine 11 | import javax.inject.Inject 12 | 13 | const val RESET_COLOR = "\u001b[0m" // Text Reset 14 | const val GREEN_BOLD = "\u001b[1;32m" // GREEN 15 | 16 | class InstallViewModel @Inject constructor( 17 | private val gpmRepo: GpmRepo, 18 | private val mavenRepo: MavenRepo, 19 | private val gradleManager: GradleManager 20 | ) : BaseInstallUninstallViewModel() { 21 | 22 | 23 | companion object { 24 | const val RESULT_DEP_INSTALLED = 200 25 | const val RESULT_REPO_NOT_FOUND = 404 26 | } 27 | 28 | override suspend fun call(command: Install): Int { 29 | 30 | val depName = command.depName.trim().toLowerCase() 31 | 32 | // first get from 33 | command.onBeforeGetDep() 34 | val gpmDep = getDep(command, depName) 35 | ?: return RESULT_REPO_NOT_FOUND 36 | 37 | command.onDepGot() 38 | val depTypes = getDepTypes( 39 | command.isSave, 40 | command.isSaveDev, 41 | command.isSaveDevAndroid, 42 | command.isKapt, 43 | gpmDep.defaultType 44 | ) 45 | 46 | require(depTypes.isNotEmpty()) { "Dependency type can't be empty" } 47 | 48 | // Adding each dependency 49 | for (depType in depTypes) { 50 | if(command.isPrintOnly){ 51 | // Only print. No files need to be modified 52 | val depSign = GradleUtils.getFullSignature( 53 | depType.key, 54 | gpmDep.groupId, 55 | gpmDep.artifactId, 56 | gpmDep.version!!, 57 | isGradleKts = true 58 | ) 59 | val coloredDepsSign = CommandLine.Help.Ansi.AUTO.string("@|bold,green $depSign|@") 60 | println(coloredDepsSign) 61 | }else{ 62 | // Modify files 63 | command.onBeforeAddDependency(depType) 64 | val newlyAddedLines = gradleManager.addDep( 65 | depName, 66 | depType, 67 | gpmDep 68 | ) 69 | command.onAfterDependenciesAdded(newlyAddedLines) 70 | } 71 | } 72 | 73 | 74 | return RESULT_DEP_INSTALLED 75 | } 76 | 77 | private suspend fun getDep( 78 | install: Install, 79 | depName: String 80 | ): GpmDep? { 81 | var gpmDep = if (install.isForceSearch) { 82 | // dont look at gpm repo 83 | null 84 | } else { 85 | install.onBeforeSearchingInGpmRegistry() 86 | gpmRepo.getDep(depName) 87 | } 88 | 89 | if (gpmDep == null) { 90 | // Searching for maven 91 | install.onBeforeSearchingInMavenCentral() 92 | gpmDep = getFromMaven(install, depName) 93 | } 94 | 95 | if (gpmDep == null) { 96 | install.onDepNotFoundAnywhere() 97 | } 98 | 99 | return gpmDep 100 | } 101 | 102 | private suspend fun getFromMaven(install: Install, depName: String): GpmDep? { 103 | val mavenDeps = mavenRepo.search(depName) 104 | 105 | if (mavenDeps.isNotEmpty()) { 106 | 107 | val mostUsed = mavenDeps.maxByOrNull { it.usage ?: 0 }!! 108 | val selDepIndex = if (mavenDeps.size > 1) { 109 | 110 | val choosables = mavenDeps.map { 111 | val text = "${it.groupId}:${it.artifactId}" 112 | if (it == mostUsed) { 113 | // color text 114 | "$GREEN_BOLD$text${RESET_COLOR}" 115 | } else { 116 | //normal text 117 | text 118 | } 119 | } 120 | install.chooseIndex(choosables) 121 | } else { 122 | 0 123 | } 124 | 125 | val selMavenDep = mavenDeps[selDepIndex] 126 | 127 | // Getting latest version 128 | val artifactInfo = mavenRepo.getLatestVersion( 129 | selMavenDep.groupId, 130 | selMavenDep.artifactId 131 | ) 132 | 133 | require(artifactInfo != null) { "Failed to artifact information for $selMavenDep" } 134 | 135 | return GpmDep( 136 | selMavenDep.artifactId, 137 | GradleDep.Type.IMP.key, 138 | selMavenDep.url, 139 | artifactInfo.repoName, 140 | null, 141 | selMavenDep.groupId, 142 | selMavenDep.name, 143 | selMavenDep.description, 144 | artifactInfo.version 145 | ) 146 | } else { 147 | return null 148 | } 149 | } 150 | 151 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/subcommands/uninstall/Uninstall.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.uninstall 2 | 3 | import com.theapache64.gpm.commands.base.BaseCommand 4 | import com.theapache64.gpm.core.gm.GradleDep 5 | import com.theapache64.gpm.di.modules.GradleModule 6 | import com.theapache64.gpm.di.modules.TransactionModule 7 | import kotlinx.coroutines.runBlocking 8 | import picocli.CommandLine 9 | import javax.inject.Inject 10 | 11 | @CommandLine.Command( 12 | name = "uninstall", 13 | aliases = ["u"], 14 | description = ["To uninstall a dependency"] 15 | ) 16 | class Uninstall(isFromTest: Boolean = false) : BaseCommand(isFromTest) { 17 | 18 | @CommandLine.Option( 19 | names = ["-S", "--save"], 20 | description = ["To uninstall the dependency defined as 'implementation'"] 21 | ) 22 | var isSave: Boolean = false 23 | 24 | @CommandLine.Option( 25 | names = ["-D", "--save-dev"], 26 | description = ["To uninstall the dependency defined as 'testImplementation'"] 27 | ) 28 | var isSaveDev: Boolean = false 29 | 30 | @CommandLine.Option( 31 | names = ["-DA", "--save-dev-android"], 32 | description = ["To uninstall the dependency defined as 'androidTestImplementation'"] 33 | ) 34 | var isSaveDevAndroid: Boolean = false 35 | 36 | @CommandLine.Option( 37 | names = ["-K", "--kapt"], 38 | description = ["To uninstall the dependency defined as 'kapt'"] 39 | ) 40 | var isKapt: Boolean = false 41 | 42 | @CommandLine.Parameters(index = "0", description = ["Dependency name"]) 43 | lateinit var depName: String 44 | 45 | @Inject 46 | lateinit var uninstallViewModel: UninstallViewModel 47 | 48 | init { 49 | DaggerUninstallComponent 50 | .builder() 51 | .gradleModule(GradleModule(isFromTest = false, modulePath = null)) 52 | .transactionModule(TransactionModule(isFromTest = false)) 53 | .build() 54 | .inject(this) 55 | } 56 | 57 | override fun call(): Int = runBlocking { 58 | uninstallViewModel.call(this@Uninstall) 59 | } 60 | 61 | fun onNoDepInstalled(depType: GradleDep.Type, depName: String) { 62 | println("⚠️ No dependency named '$depName' installed as '${depType.key}' using gpm. You might have installed it manually.") 63 | } 64 | 65 | fun onAfterDepRemove(depType: GradleDep.Type, depName: String) { 66 | println("🗑️ Removed '${depType.key}' of '$depName'...") 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/subcommands/uninstall/UninstallComponent.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.uninstall 2 | 3 | import com.theapache64.gpm.core.TransactionManager 4 | import com.theapache64.gpm.core.gm.GradleManager 5 | import com.theapache64.gpm.di.GpmJsonFile 6 | import com.theapache64.gpm.di.GradleFile 7 | import com.theapache64.gpm.di.modules.GradleModule 8 | import com.theapache64.gpm.di.modules.TransactionModule 9 | import dagger.Component 10 | import java.io.File 11 | import javax.inject.Singleton 12 | 13 | @Singleton 14 | @Component( 15 | modules = [GradleModule::class, TransactionModule::class] 16 | ) 17 | interface UninstallComponent { 18 | fun inject(uninstall: Uninstall) 19 | 20 | @GradleFile 21 | fun gradleFile(): File 22 | 23 | @GpmJsonFile 24 | fun gpmJsonFile(): File 25 | fun transactionManager(): TransactionManager 26 | fun gradleManager(): GradleManager 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/commands/subcommands/uninstall/UninstallViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.uninstall 2 | 3 | import com.theapache64.gpm.commands.base.BaseInstallUninstallViewModel 4 | import com.theapache64.gpm.core.TransactionManager 5 | import com.theapache64.gpm.core.gm.GradleDep 6 | import com.theapache64.gpm.core.gm.GradleManager 7 | import javax.inject.Inject 8 | 9 | class UninstallViewModel @Inject constructor( 10 | private val tm: TransactionManager, 11 | private val gradleManager: GradleManager 12 | ) : BaseInstallUninstallViewModel() { 13 | 14 | companion object { 15 | const val RESULT_DEP_UNINSTALLED = 123 16 | const val RESULT_NO_DEP_INSTALLED = 134 17 | } 18 | 19 | override suspend fun call(command: Uninstall): Int { 20 | 21 | val depName = command.depName.trim().toLowerCase() 22 | 23 | val depTypes = getDepTypes( 24 | command.isSave, 25 | command.isSaveDev, 26 | command.isSaveDevAndroid, 27 | command.isKapt, 28 | GradleDep.Type.IMP.key 29 | ) 30 | 31 | for (depType in depTypes) { 32 | 33 | 34 | val installedDeps = tm.getInstalled(depType.key, depName) 35 | 36 | if (installedDeps.isEmpty()) { 37 | command.onNoDepInstalled(depType, depName) 38 | return RESULT_NO_DEP_INSTALLED 39 | } 40 | 41 | val depToRemove = if (installedDeps.size > 1) { 42 | // multiple, choose one 43 | val selDepIndex = command.chooseIndex(installedDeps.map { 44 | "${it.type} ${it.gpmDep.groupId}:${it.gpmDep.artifactId}" 45 | }) 46 | 47 | installedDeps[selDepIndex] 48 | 49 | } else { 50 | installedDeps.first() 51 | } 52 | 53 | gradleManager.removeDep(depToRemove) 54 | command.onAfterDepRemove(depType, depName) 55 | } 56 | 57 | return RESULT_DEP_UNINSTALLED 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/core/TransactionManager.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.core 2 | 3 | import com.squareup.moshi.JsonAdapter 4 | import com.squareup.moshi.Moshi 5 | import com.theapache64.gpm.core.gm.GradleDep 6 | import com.theapache64.gpm.data.remote.gpm.models.GpmDep 7 | import com.theapache64.gpm.models.GpmFileData 8 | import java.io.File 9 | import java.io.FileNotFoundException 10 | import java.io.IOException 11 | import javax.inject.Inject 12 | 13 | class TransactionManager @Inject constructor( 14 | private val gpmJsonFile: File, 15 | private val moshi: Moshi 16 | ) { 17 | 18 | private val adapter: JsonAdapter by lazy { 19 | moshi.adapter(GpmFileData::class.java).indent(" ") 20 | } 21 | 22 | 23 | fun add(installedName: String, type: GradleDep.Type, newGpmDep: GpmDep) { 24 | 25 | // Need to login 26 | val newDepId = getLastDepAdded()?.id?.plus(1) ?: 1 27 | 28 | if (newDepId != 1) { 29 | // got prev transaction, so check if this is a duplicate one 30 | if (isDuplicate(installedName, type, newGpmDep)) { 31 | return 32 | } 33 | } 34 | 35 | val depToStore = GpmFileData.AddedDep( 36 | newDepId, 37 | type.key, 38 | installedName, 39 | newGpmDep 40 | ) 41 | val newFileData = if (!gpmJsonFile.exists()) { 42 | GpmFileData(mutableListOf(depToStore)) 43 | } else { 44 | val gpmFileData = adapter.fromJson(gpmJsonFile.readText()) 45 | gpmFileData!!.deps.add(depToStore) 46 | gpmFileData 47 | } 48 | 49 | setData(newFileData) 50 | } 51 | 52 | private fun isDuplicate(installedName: String, type: GradleDep.Type, newGpmDep: GpmDep): Boolean { 53 | return getData()?.deps?.find { 54 | it.installedName == installedName && 55 | it.type == type.key && 56 | it.gpmDep.artifactId == newGpmDep.artifactId && 57 | it.gpmDep.groupId == newGpmDep.groupId && 58 | it.gpmDep.name == newGpmDep.name 59 | } != null 60 | } 61 | 62 | private fun getLastDepAdded(): GpmFileData.AddedDep? { 63 | return try { 64 | getData()?.deps?.lastOrNull() 65 | } catch (e: FileNotFoundException) { 66 | null 67 | } 68 | } 69 | 70 | private fun setData(newFileData: GpmFileData) { 71 | val gpmFileDataJson = adapter.toJson(newFileData) 72 | gpmJsonFile.writeText(gpmFileDataJson) 73 | } 74 | 75 | fun getInstalled(type: String?, depName: String): List { 76 | return getData()?.deps?.filter { 77 | it.installedName == depName && (type == null || it.type == type) 78 | } ?: listOf() 79 | } 80 | 81 | fun remove(depToRemove: GpmFileData.AddedDep) { 82 | val data = getData() 83 | if (data != null) { 84 | val isRemoved = data.deps.removeIf { it.id == depToRemove.id } 85 | if (isRemoved) { 86 | setData(data) 87 | } else { 88 | throw IOException("Failed to remove dependency. Couldn't find dependency with id '${depToRemove.id}'") 89 | } 90 | } else { 91 | throw IOException( 92 | """ 93 | Failed to remove dependency. Are you sure you have installed it through gpm? 94 | """.trimIndent() 95 | ) 96 | } 97 | } 98 | 99 | private fun getData() = try { 100 | adapter.fromJson(gpmJsonFile.readText()) 101 | } catch (e: FileNotFoundException) { 102 | null 103 | } 104 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/core/gm/GradleDep.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.core.gm 2 | 3 | data class GradleDep( 4 | val type: Type, 5 | val groupId: String, 6 | val artifactId: String, 7 | val version: String 8 | ) { 9 | enum class Type(val key: String) { 10 | IMP("implementation"), 11 | TEST_IMP("testImplementation"), 12 | AND_TEST_IMP("androidTestImplementation"), 13 | KAPT("kapt") 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/core/gm/GradleManager.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.core.gm 2 | 3 | import com.theapache64.gpm.core.TransactionManager 4 | import com.theapache64.gpm.data.remote.gpm.models.GpmDep 5 | import com.theapache64.gpm.models.GpmFileData 6 | import com.theapache64.gpm.utils.GradleUtils 7 | import com.theapache64.gpm.utils.StringUtils 8 | import com.theapache64.gpm.utils.insertAt 9 | import java.io.File 10 | import java.io.IOException 11 | 12 | /** 13 | * Currently supports 'implementation' and 'testImplementation' only. 14 | */ 15 | class GradleManager constructor( 16 | private val transactionManager: TransactionManager, 17 | private val gradleFile: File 18 | ) { 19 | 20 | companion object { 21 | private val DEP_REGEX by lazy { 22 | "(?androidTestImplementation|testImplementation|implementation)\\s*\\(?[\"'](?.+?):(?.+?):(?.+?)[\"']\\)?".toRegex() 23 | } 24 | 25 | private const val KEY_DEP = "dependencies" 26 | } 27 | 28 | fun parseDeps(): List { 29 | val deps = mutableListOf() 30 | val fileContent = gradleFile.readText() 31 | val matchResults = DEP_REGEX.findAll(fileContent) 32 | 33 | for (result in matchResults) { 34 | 35 | val type = result.groups["type"]!!.value 36 | val groupId = result.groups["groupId"]!!.value 37 | val artifactId = result.groups["artifactId"]!!.value 38 | val version = result.groups["version"]!!.value 39 | 40 | val gdType = GradleDep.Type.values().find { it.key == type } 41 | ?: throw IllegalArgumentException("Couldn't find dependency type for '$type.'") 42 | 43 | deps.add( 44 | GradleDep( 45 | gdType, 46 | groupId, 47 | artifactId, 48 | version 49 | ) 50 | ) 51 | } 52 | 53 | return deps 54 | } 55 | 56 | /** 57 | * To add dependency 58 | */ 59 | @Throws(IndexOutOfBoundsException::class) 60 | fun addDep( 61 | installedName: String, 62 | type: GradleDep.Type, 63 | newGpmDep: GpmDep 64 | ): String { 65 | 66 | val fileContent = gradleFile.readText() 67 | val name = newGpmDep.name 68 | val brokenDescription = StringUtils.breakOnAndComment(80, newGpmDep.description) 69 | .replace("\n", "\n\t") 70 | 71 | val isGradleKts = gradleFile.extension == "kts" 72 | val fullSignature = GradleUtils.getFullSignature( 73 | type.key, 74 | newGpmDep.groupId, 75 | newGpmDep.artifactId, 76 | newGpmDep.version!!, 77 | isGradleKts 78 | ) 79 | 80 | val addedLines: String 81 | 82 | if (fileContent.contains(KEY_DEP)) { 83 | 84 | val newDepSign = "\n // $name : $brokenDescription\n $fullSignature\n" 85 | 86 | // Appending dependency 87 | val depIndex = fileContent.indexOf(KEY_DEP) 88 | val openIndex = fileContent.indexOf('{', depIndex) 89 | val closingIndex = StringUtils.getClosingIndexOf(fileContent, '{', openIndex, '}') 90 | val newContent = fileContent.insertAt(closingIndex, newDepSign) 91 | gradleFile.writeText(newContent) 92 | 93 | addedLines = newDepSign 94 | 95 | } else { 96 | 97 | // Adding first dependency 98 | val newDepSign = """// $name : $brokenDescription 99 | $fullSignature""" 100 | 101 | val firstDep = """ 102 | 103 | // Project Dependencies 104 | dependencies { 105 | 106 | $newDepSign 107 | } 108 | 109 | """.trimIndent() 110 | gradleFile.appendText(firstDep) 111 | 112 | addedLines = newDepSign 113 | } 114 | 115 | transactionManager.add(installedName, type, newGpmDep) 116 | 117 | return addedLines 118 | } 119 | 120 | /** 121 | * To remove dependency 122 | */ 123 | fun removeDep(depToRemove: GpmFileData.AddedDep) { 124 | 125 | val name = depToRemove.gpmDep.name 126 | val description = depToRemove.gpmDep.description 127 | val groupId = depToRemove.gpmDep.groupId 128 | val artifactId = depToRemove.gpmDep.artifactId 129 | 130 | val depRegEx = ("(?:\\s*\\/\\/\\s$name:$description)?\n" + 131 | "\\s*${depToRemove.type}\\s*\\(?['\"]$groupId:$artifactId:.+?['\"]\\)?").toRegex() 132 | 133 | val fileContent = gradleFile.readText() 134 | val newFileContent = fileContent.replace(depRegEx, "") 135 | 136 | if (fileContent.length != newFileContent.length) { 137 | // dep removed, update gradle file 138 | gradleFile.writeText(newFileContent) 139 | 140 | // remove the transaction also 141 | transactionManager.remove(depToRemove) 142 | } else { 143 | throw IOException( 144 | """ 145 | Failed to remove dependency. 146 | Couldn't find `${depToRemove.type} '${groupId}:$artifactId:...'` in ${gradleFile.name} 147 | Signature might have changed. 148 | """.trimIndent() 149 | ) 150 | } 151 | } 152 | 153 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/data/remote/gpm/GpmApiInterface.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.data.remote.gpm 2 | 3 | import com.theapache64.gpm.data.remote.gpm.models.GpmDep 4 | import retrofit2.http.GET 5 | import retrofit2.http.Path 6 | 7 | interface GpmApiInterface { 8 | 9 | @GET("registry/{name}.json") 10 | suspend fun getDependency( 11 | @Path("name") repoName: String 12 | ): GpmDep? 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/data/remote/gpm/models/GpmDep.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.data.remote.gpm.models 2 | 3 | import com.squareup.moshi.Json 4 | 5 | data class GpmDep( 6 | @Json(name = "artifact_id") 7 | val artifactId: String, // okhttp 8 | @Json(name = "default_type") 9 | val defaultType: String?, // implementation 10 | @Json(name = "docs") 11 | val docs: String, // https://square.github.io/okhttp/ 12 | @Json(name = "get_from") 13 | val getFrom: String, // maven 14 | @Json(name = "github") 15 | val github: String?, // square/okhttp 16 | @Json(name = "group_id") 17 | val groupId: String, // com.squareup.okhttp3 18 | @Json(name = "name") 19 | val name: String, 20 | @Json(name = "description") 21 | val description: String, 22 | @Transient 23 | var version: String? = null // OkHttp 24 | ) { 25 | 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/data/remote/maven/MavenApiInterface.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.data.remote.maven 2 | 3 | import retrofit2.http.GET 4 | import retrofit2.http.Header 5 | import retrofit2.http.Path 6 | import retrofit2.http.Query 7 | 8 | interface MavenApiInterface { 9 | 10 | @GET("search") 11 | suspend fun search( 12 | @Query("q") query: String, 13 | @Header("User-Agent") userAgent : String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" 14 | ): String 15 | 16 | @GET("artifact/{groupId}/{artifactId}") 17 | suspend fun getArtifact( 18 | @Path("groupId") groupId: String, 19 | @Path("artifactId") artifactId: String, 20 | @Header("User-Agent") userAgent : String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" 21 | ): String 22 | 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/data/remote/maven/models/ArtifactInfo.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.data.remote.maven.models 2 | 3 | class ArtifactInfo( 4 | val version: String, 5 | val repoName: String, 6 | val repoUrl: String 7 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/data/remote/maven/models/SearchResult.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.data.remote.maven.models 2 | 3 | import java.util.* 4 | 5 | data class SearchResult( 6 | val id: Int, 7 | val name: String, 8 | val groupId: String, 9 | val artifactId: String, 10 | val description: String, 11 | val usage: Int?, 12 | val lastRelease: Date 13 | ) { 14 | val url = "https://mvnrepository.com/artifact/$groupId/$artifactId" 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/data/repos/GpmRepo.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.data.repos 2 | 3 | import com.theapache64.gpm.data.remote.gpm.GpmApiInterface 4 | import com.theapache64.gpm.data.remote.gpm.models.GpmDep 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class GpmRepo @Inject constructor( 10 | private val gpmApiInterface: GpmApiInterface, 11 | private val mavenRepo: MavenRepo 12 | ) { 13 | 14 | /** 15 | * To get dependency from GPM github registry 16 | */ 17 | suspend fun getDep(depName: String): GpmDep? { 18 | return try { 19 | val dep = gpmApiInterface.getDependency(depName) 20 | if (dep == null) { 21 | null 22 | } else { 23 | val versionInfo = mavenRepo.getLatestVersion(dep.groupId, dep.artifactId) 24 | require(versionInfo != null) { "Couldn't get version info for '$depName'" } 25 | dep.version = versionInfo.version 26 | dep 27 | } 28 | } catch (e: Exception) { 29 | null 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/data/repos/MavenRepo.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.data.repos 2 | 3 | import com.theapache64.gpm.data.remote.maven.MavenApiInterface 4 | import com.theapache64.gpm.data.remote.maven.models.ArtifactInfo 5 | import com.theapache64.gpm.data.remote.maven.models.SearchResult 6 | import com.theapache64.gpm.utils.removeNewLinesAndMultipleSpaces 7 | import org.apache.commons.text.StringEscapeUtils 8 | import java.text.SimpleDateFormat 9 | import javax.inject.Inject 10 | import javax.inject.Singleton 11 | 12 | @Singleton 13 | class MavenRepo @Inject constructor( 14 | private val mavenApiInterface: MavenApiInterface 15 | ) { 16 | companion object { 17 | private val SEARCH_RESULT_REGEX by lazy { 18 | "
.+?(?\\d+?)\\. <\\/span>.+?)\\/(?.+?)\">(?.+?)<\\/a>.+?(?:(?.+?)<\\/b> usages<\\/a>.+?)?im-description\">(?.+?).+?)<\\/div".toRegex() 19 | } 20 | 21 | private val ARTIFACT_VERSION_REGEX by lazy { 22 | ".+?vbtn (?:release candidate|release|alpha|beta)\">(?.+?)<\\/a>.+?.+?)\">(?.+?)<\\/a>".toRegex() 23 | } 24 | 25 | //Apr 29, 2020 26 | private val SEARCH_RESULT_DATE_FORMAT = SimpleDateFormat("MMM dd, yyyy") 27 | } 28 | 29 | suspend fun search(depName: String): List { 30 | val searchHtml = mavenApiInterface.search(depName) 31 | val htmlResp = searchHtml.removeNewLinesAndMultipleSpaces() 32 | val matches = SEARCH_RESULT_REGEX.findAll(htmlResp).toMutableList().let { matches -> 33 | 34 | // Minimizing results to top5 35 | if (matches.size > 5) { 36 | // only first five needed 37 | matches.subList(0, 5) 38 | } else { 39 | // no change 40 | matches 41 | } 42 | } 43 | val searchResults = mutableListOf() 44 | for (match in matches) { 45 | 46 | val ( 47 | number, 48 | groupId, 49 | artifactId, 50 | name, 51 | usages, 52 | description, 53 | lastRelease 54 | ) = match.destructured 55 | 56 | val usage = usages.trim().replace(",", "").let { 57 | if (it.isEmpty()) { 58 | null 59 | } else { 60 | it.toInt() 61 | } 62 | } 63 | searchResults.add( 64 | SearchResult( 65 | number.trim().toInt(), 66 | name.trim(), 67 | groupId.trim(), 68 | artifactId.trim(), 69 | StringEscapeUtils.unescapeHtml4(description.trim()), 70 | usage, 71 | SEARCH_RESULT_DATE_FORMAT.parse(lastRelease.trim()) 72 | ) 73 | ) 74 | } 75 | return searchResults 76 | } 77 | 78 | /** 79 | * To get latest version of the given artifactId from the given group 80 | */ 81 | suspend fun getLatestVersion(groupId: String, artifactId: String): ArtifactInfo? { 82 | val htmlResp = mavenApiInterface.getArtifact(groupId, artifactId).removeNewLinesAndMultipleSpaces() 83 | val results = ARTIFACT_VERSION_REGEX.find(htmlResp) 84 | return if (results != null) { 85 | val (version, repoUrl, repoName) = results.destructured 86 | ArtifactInfo(version, repoName, repoUrl) 87 | } else { 88 | null 89 | } 90 | } 91 | 92 | 93 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/di/Qualifiers.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.di 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @MustBeDocumented 7 | @Retention(AnnotationRetention.RUNTIME) 8 | annotation class GradleFile 9 | 10 | @Qualifier 11 | @MustBeDocumented 12 | @Retention(AnnotationRetention.RUNTIME) 13 | annotation class GpmJsonFile -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/di/modules/CommandModule.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.di.modules 2 | 3 | import com.theapache64.gpm.commands.gpm.Gpm 4 | import dagger.Module 5 | import dagger.Provides 6 | import javax.inject.Singleton 7 | 8 | @Module 9 | class CommandModule(private val isFromTest: Boolean) { 10 | 11 | @Singleton 12 | @Provides 13 | fun provideGpm(): Gpm { 14 | return Gpm(isFromTest) 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/di/modules/GradleModule.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.di.modules 2 | 3 | import com.theapache64.gpm.core.TransactionManager 4 | import com.theapache64.gpm.core.gm.GradleManager 5 | import com.theapache64.gpm.di.GradleFile 6 | import dagger.Module 7 | import dagger.Provides 8 | import java.io.File 9 | import kotlin.system.exitProcess 10 | 11 | @Module(includes = [TransactionModule::class]) 12 | class GradleModule( 13 | private val isFromTest: Boolean, 14 | private val modulePath: String? 15 | ) { 16 | 17 | @Provides 18 | @GradleFile 19 | fun gradleFile(): File { 20 | 21 | @Suppress("ConstantConditionIf") 22 | return if (isFromTest) { 23 | val modPath = if (!modulePath.isNullOrBlank()) { 24 | "$modulePath/" 25 | } else { 26 | "" 27 | } 28 | val tempGradleFile = File("${modPath}src/test/resources/temp.build.gradle") 29 | val sampleFile = File("src/test/resources/sample.build.gradle") 30 | tempGradleFile.delete() 31 | sampleFile.copyTo(tempGradleFile) 32 | tempGradleFile 33 | 34 | } else { 35 | if (modulePath != null) { 36 | val androidGradleFile = File("$modulePath/build.gradle") 37 | val androidKtsGradleFile = File("$modulePath/build.gradle.kts") 38 | when { 39 | androidGradleFile.exists() -> { 40 | // android project 41 | androidGradleFile 42 | } 43 | 44 | androidKtsGradleFile.exists() -> { 45 | androidKtsGradleFile 46 | } 47 | 48 | else -> { 49 | val currentDir = File(System.getProperty("user.dir")) 50 | 51 | println( 52 | """ 53 | Couldn't find `$modulePath` inside '${currentDir.absolutePath}'. 54 | Are you sure that you're executing the command from project root? 55 | """.trimIndent() 56 | ) 57 | 58 | exitProcess(0) 59 | } 60 | 61 | } 62 | } else { 63 | findGradleFile() 64 | } 65 | } 66 | } 67 | 68 | private fun findGradleFile(): File { 69 | val androidGradleFile = File("app/build.gradle") 70 | val androidKtsGradleFile = File("app/build.gradle.kts") 71 | val jvmGradleFile = File("build.gradle") 72 | val jvmKtsGradleFile = File("build.gradle.kts") 73 | 74 | return when { 75 | androidGradleFile.exists() -> { 76 | // android project 77 | androidGradleFile 78 | } 79 | 80 | androidKtsGradleFile.exists() -> { 81 | androidKtsGradleFile 82 | } 83 | 84 | jvmGradleFile.exists() -> { 85 | jvmGradleFile 86 | } 87 | 88 | jvmKtsGradleFile.exists() -> { 89 | jvmKtsGradleFile 90 | } 91 | 92 | else -> { 93 | 94 | /** 95 | * SMART END 96 | */ 97 | 98 | val currentDir = File(System.getProperty("user.dir")) 99 | 100 | println( 101 | """ 102 | Invalid directory '${currentDir.absolutePath}'. 103 | Are you sure that you're executing the command from project root? 104 | """.trimIndent() 105 | ) 106 | 107 | exitProcess(0) 108 | } 109 | } 110 | } 111 | 112 | 113 | @Provides 114 | fun provideGradleManager( 115 | @GradleFile gradleFile: File, 116 | transactionManager: TransactionManager 117 | ): GradleManager { 118 | return GradleManager(transactionManager, gradleFile) 119 | } 120 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/di/modules/MoshiModule.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.di.modules 2 | 3 | import com.squareup.moshi.Moshi 4 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | class MoshiModule { 11 | 12 | @Singleton 13 | @Provides 14 | fun provideKotlinJsonAdapterFactory(): KotlinJsonAdapterFactory { 15 | return KotlinJsonAdapterFactory() 16 | } 17 | 18 | 19 | @Singleton 20 | @Provides 21 | fun provideMoshi(kotlinJsonAdapter: KotlinJsonAdapterFactory): Moshi { 22 | return Moshi.Builder() 23 | .add(kotlinJsonAdapter) 24 | .build() 25 | } 26 | 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/di/modules/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.di.modules 2 | 3 | import com.moczul.ok2curl.CurlInterceptor 4 | import com.moczul.ok2curl.logger.Logger 5 | import com.squareup.moshi.Moshi 6 | import com.theapache64.gpm.data.remote.gpm.GpmApiInterface 7 | import com.theapache64.gpm.data.remote.maven.MavenApiInterface 8 | import dagger.Module 9 | import dagger.Provides 10 | import okhttp3.OkHttpClient 11 | import retrofit2.Retrofit 12 | import retrofit2.converter.moshi.MoshiConverterFactory 13 | import retrofit2.converter.scalars.ScalarsConverterFactory 14 | import javax.inject.Singleton 15 | 16 | @Module(includes = [MoshiModule::class]) 17 | class NetworkModule { 18 | 19 | @Provides 20 | fun provideRetrofit(): Retrofit.Builder { 21 | val okHttp = OkHttpClient.Builder() 22 | /*.addInterceptor( 23 | CurlInterceptor(object : Logger { 24 | override fun log(message: String) { 25 | println("QuickTag: NetworkModule:log: `$message`") 26 | } 27 | }) 28 | )*/ 29 | .build() 30 | 31 | return Retrofit.Builder() 32 | .client(okHttp) 33 | } 34 | 35 | @Singleton 36 | @Provides 37 | fun provideGpmApiInterface(retrofitBuilder: Retrofit.Builder, moshi: Moshi): GpmApiInterface { 38 | return retrofitBuilder 39 | .baseUrl("https://raw.githubusercontent.com/theapache64/gpm/master/") 40 | .addConverterFactory(MoshiConverterFactory.create(moshi)) 41 | .build() 42 | .create(GpmApiInterface::class.java) 43 | } 44 | 45 | @Singleton 46 | @Provides 47 | fun provideMavenApiInterface(retrofitBuilder: Retrofit.Builder): MavenApiInterface { 48 | return retrofitBuilder 49 | .baseUrl("https://mvnrepository.com/") 50 | .addConverterFactory(ScalarsConverterFactory.create()) 51 | .build() 52 | .create(MavenApiInterface::class.java) 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/di/modules/TransactionModule.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.di.modules 2 | 3 | import com.squareup.moshi.Moshi 4 | import com.theapache64.gpm.core.TransactionManager 5 | import com.theapache64.gpm.di.GpmJsonFile 6 | import dagger.Module 7 | import dagger.Provides 8 | import java.io.File 9 | 10 | @Module(includes = [MoshiModule::class]) 11 | class TransactionModule(private val isFromTest: Boolean) { 12 | 13 | @Provides 14 | @GpmJsonFile 15 | fun provideGpmJsonFile(): File { 16 | return if (isFromTest) { 17 | File("src/test/resources/temp.gpm.json") 18 | } else { 19 | File("gpm.json") 20 | } 21 | } 22 | 23 | @Provides 24 | fun provideTransactionManager(@GpmJsonFile gpmJsonFile: File, moshi: Moshi): TransactionManager { 25 | return TransactionManager(gpmJsonFile, moshi) 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/models/GpmFileData.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.models 2 | 3 | import com.squareup.moshi.Json 4 | import com.theapache64.gpm.data.remote.gpm.models.GpmDep 5 | 6 | class GpmFileData( 7 | @Json(name = "added") 8 | val deps: MutableList 9 | ) { 10 | class AddedDep( 11 | @Json(name = "id") 12 | val id: Int, 13 | @Json(name = "type") 14 | val type: String, 15 | @Json(name = "installed_name") 16 | val installedName: String, 17 | @Json(name = "gpm_dep") 18 | val gpmDep: GpmDep 19 | ) 20 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/utils/GpmConfig.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.utils 2 | 3 | object GpmConfig { 4 | // const val IS_DEBUG_MODE = true 5 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/utils/GradleUtils.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.utils 2 | 3 | object GradleUtils { 4 | fun getFullSignature( 5 | typeKeyword: String, 6 | groupId: String, 7 | artifactId: String, 8 | version: String, 9 | isGradleKts: Boolean 10 | ): String { 11 | 12 | val quote = if (isGradleKts || version.startsWith("$")) { 13 | "\"" 14 | } else { 15 | "'" 16 | } 17 | 18 | return if (isGradleKts) { 19 | // kotlin script 20 | "$typeKeyword($quote$groupId:$artifactId:$version$quote)" 21 | } else { 22 | // normal gradle 23 | "$typeKeyword $quote$groupId:$artifactId:$version$quote" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/utils/InputUtils.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.utils 2 | 3 | import java.util.* 4 | 5 | /** 6 | * Operations related to reading text input from console 7 | */ 8 | object InputUtils { 9 | 10 | private val scanner by lazy { Scanner(System.`in`) } 11 | 12 | /** 13 | * Get a String with given prompt as prompt 14 | */ 15 | fun getString(prompt: String, isRequired: Boolean): String { 16 | print("$prompt: ") 17 | val value = scanner.nextLine() 18 | while (value.trim().isEmpty() && isRequired) { 19 | println("Invalid ${prompt.toLowerCase()} `$value`") 20 | return getString(prompt, isRequired) 21 | } 22 | return value 23 | } 24 | 25 | fun getInt(prompt: String, lowerBound: Int, upperBound: Int, but: Array = arrayOf()): Int { 26 | print("$prompt :") 27 | 28 | val sVal = scanner.nextLine() 29 | try { 30 | val value = sVal.toInt() 31 | if (!but.contains(value) && (value < lowerBound || value > upperBound)) { 32 | // error 33 | println("Input must between $lowerBound and $upperBound") 34 | return getInt(prompt, lowerBound, upperBound) 35 | } 36 | return value 37 | } catch (e: NumberFormatException) { 38 | println("Invalid input `$sVal`") 39 | return getInt(prompt, lowerBound, upperBound) 40 | } 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/utils/Logger.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.utils 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/utils/StringExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.utils 2 | 3 | fun String.removeNewLinesAndMultipleSpaces(): String { 4 | return this.replace("\n", "") 5 | .replace("\r", "") 6 | .replace("\\s{2,}".toRegex(), " ") 7 | } 8 | 9 | fun String.insertAt(position: Int, text: String): String { 10 | return this.substring(0, position) + text + this.substring( 11 | position, 12 | this.length 13 | ) 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/theapache64/gpm/utils/StringUtils.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.utils 2 | 3 | 4 | object StringUtils { 5 | 6 | fun getClosingIndexOf(text: String, openingChar: Char, openingIndex: Int, closingChar: Char): Int { 7 | var closePos: Int = openingIndex 8 | var counter = 1 9 | while (counter > 0) { 10 | val c: Char = text[++closePos] 11 | if (c == openingChar) { 12 | counter++ 13 | } else if (c == closingChar) { 14 | counter-- 15 | } 16 | } 17 | return closePos 18 | } 19 | 20 | fun breakOnAndComment(charLimit: Int, input: String): String { 21 | if (input.length < charLimit) { 22 | return input 23 | } 24 | 25 | return getChunked(input, charLimit).joinToString("\n// ") 26 | } 27 | 28 | private fun getChunked(input: String, charLimit: Int): List { 29 | val chunks = mutableListOf() 30 | val words = input.split(" ") 31 | val builder = StringBuilder() 32 | for (word in words) { 33 | builder.append(word).append(" ") 34 | if (builder.length >= charLimit) { 35 | // move to chunk 36 | val newLine = builder.toString().trim() 37 | chunks.add(newLine) 38 | builder.clear() 39 | } 40 | } 41 | if (builder.isNotEmpty()) { 42 | chunks.add(builder.toString().trim()) 43 | } 44 | return chunks 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.runBlocking 5 | 6 | fun runBlockingUnitTest(block: suspend (scope: CoroutineScope) -> Unit) = runBlocking { 7 | block(this) 8 | Unit 9 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/commands/subcommands/InstallTest.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands 2 | 3 | import com.theapache64.gpm.commands.subcommands.install.DaggerInstallComponent 4 | import com.theapache64.gpm.commands.subcommands.install.Install 5 | import com.theapache64.gpm.commands.subcommands.install.InstallComponent 6 | import com.theapache64.gpm.commands.subcommands.install.InstallViewModel 7 | import com.theapache64.gpm.di.modules.CommandModule 8 | import com.theapache64.gpm.di.modules.GradleModule 9 | import com.theapache64.gpm.di.modules.NetworkModule 10 | import com.theapache64.gpm.di.modules.TransactionModule 11 | import com.theapache64.gpm.runBlockingUnitTest 12 | import com.winterbe.expekt.should 13 | import it.cosenonjaviste.daggermock.DaggerMock 14 | import org.junit.After 15 | import org.junit.Before 16 | import org.junit.Rule 17 | import org.junit.Test 18 | import picocli.CommandLine 19 | import java.io.File 20 | import java.io.PrintWriter 21 | import java.io.StringWriter 22 | 23 | class InstallTest { 24 | 25 | 26 | private lateinit var installCmd: CommandLine 27 | private val install = Install(true) 28 | 29 | 30 | private lateinit var tempBuildGradle: File 31 | private lateinit var tempGpmJson: File 32 | 33 | companion object { 34 | private const val DUMMY_MODULE = "feature/auth" 35 | } 36 | 37 | @get:Rule 38 | val daggerRule = DaggerMock.rule(NetworkModule()) { 39 | customizeBuilder { 40 | it.gradleModule(GradleModule(isFromTest = true, DUMMY_MODULE)) // sample module 41 | .transactionModule(TransactionModule(true)) 42 | .commandModule(CommandModule(true)) 43 | } 44 | set { 45 | tempBuildGradle = it.gradleFile() 46 | tempGpmJson = it.gpmJsonFile() 47 | it.inject(install) 48 | } 49 | } 50 | 51 | 52 | @Before 53 | fun setUp() = runBlockingUnitTest { 54 | this.installCmd = CommandLine(install) 55 | installCmd.out = PrintWriter(StringWriter()) 56 | } 57 | 58 | @Test 59 | fun `Install default`() { 60 | val exitCode = installCmd.execute("okhttp") 61 | exitCode.should.equal(InstallViewModel.RESULT_DEP_INSTALLED) 62 | tempBuildGradle.readText().should.contain("implementation 'com.squareup.okhttp3:okhttp:") 63 | } 64 | 65 | @Test 66 | fun `Install default another`() { 67 | val exitCode = installCmd.execute("progressbar") 68 | exitCode.should.equal(InstallViewModel.RESULT_DEP_INSTALLED) 69 | /* 70 | tempBuildGradle.readText().should.contain("implementation 'com.squareup.okhttp3:okhttp:")*/ 71 | } 72 | 73 | @Test 74 | fun `Install non existing registry`() { 75 | val exitCode = installCmd.execute("retrofit") 76 | exitCode.should.equal(InstallViewModel.RESULT_DEP_INSTALLED) 77 | tempBuildGradle.readText().should.contain("implementation 'com.squareup.retrofit2:retrofit") 78 | } 79 | 80 | @Test 81 | fun `Install --save`() { 82 | val exitCode = installCmd.execute("--save", "okhttp") 83 | exitCode.should.equal(InstallViewModel.RESULT_DEP_INSTALLED) 84 | tempBuildGradle.readText().should.contain("implementation 'com.squareup.okhttp3:okhttp:") 85 | } 86 | 87 | @Test 88 | fun `Install --save-dev`() { 89 | val exitCode = installCmd.execute("--save-dev", "okhttp") 90 | exitCode.should.equal(InstallViewModel.RESULT_DEP_INSTALLED) 91 | tempBuildGradle.readText().should.contain("testImplementation 'com.squareup.okhttp3:okhttp:") 92 | } 93 | 94 | @Test 95 | fun `Install --save-dev-android`() { 96 | val exitCode = installCmd.execute("--save-dev-android", "okhttp") 97 | exitCode.should.equal(InstallViewModel.RESULT_DEP_INSTALLED) 98 | tempBuildGradle.readText().should.contain("androidTestImplementation 'com.squareup.okhttp3:okhttp:") 99 | } 100 | 101 | @Test 102 | fun `Install --kapt`() { 103 | val exitCode = installCmd.execute("--kapt", "dagger-compiler") 104 | exitCode.should.equal(InstallViewModel.RESULT_DEP_INSTALLED) 105 | tempBuildGradle.readText().should.contain("kapt 'com.google.dagger:dagger-compiler:") 106 | } 107 | 108 | 109 | @Test 110 | fun `Install not existing library`() { 111 | val exitCode = installCmd.execute("gdfhdfghdfghfdg") 112 | exitCode.should.equal(InstallViewModel.RESULT_REPO_NOT_FOUND) 113 | } 114 | 115 | @Test 116 | fun `Install --save into modules`() { 117 | val exitCode = installCmd.execute("--save", "okhttp") 118 | exitCode.should.equal(InstallViewModel.RESULT_DEP_INSTALLED) 119 | tempBuildGradle.readText().should.contain("implementation 'com.squareup.okhttp3:okhttp:") 120 | tempBuildGradle.absolutePath.should.contain(DUMMY_MODULE) 121 | } 122 | 123 | @Test 124 | fun `Install lengthy description library`() { 125 | val exitCode = installCmd.execute("webcam") 126 | exitCode.should.equal(InstallViewModel.RESULT_DEP_INSTALLED) 127 | tempBuildGradle.readText().should.contain("implementation 'com.github.sarxos:webcam-capture") 128 | } 129 | 130 | @After 131 | fun tearDown() { 132 | // tempBuildGradle.delete() 133 | tempGpmJson.delete() 134 | } 135 | 136 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/commands/subcommands/docs/DocsTest.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.docs 2 | 3 | import com.theapache64.gpm.core.TransactionManager 4 | import com.theapache64.gpm.core.gm.GradleDep 5 | import com.theapache64.gpm.data.remote.gpm.models.GpmDep 6 | import com.theapache64.gpm.di.modules.TransactionModule 7 | import com.winterbe.expekt.should 8 | import it.cosenonjaviste.daggermock.DaggerMock 9 | import it.cosenonjaviste.daggermock.InjectFromComponent 10 | import org.junit.After 11 | import org.junit.Before 12 | import org.junit.Rule 13 | import org.junit.Test 14 | import picocli.CommandLine 15 | import java.io.File 16 | 17 | class DocsTest { 18 | 19 | private lateinit var gpmJsonFile: File 20 | private lateinit var docsCmd: CommandLine 21 | private val docs = Docs(true) 22 | 23 | @InjectFromComponent 24 | private lateinit var tm: TransactionManager 25 | 26 | @get:Rule 27 | val daggerRule = DaggerMock.rule(TransactionModule(true)) { 28 | set { 29 | tm = it.transactionManager() 30 | gpmJsonFile = it.gpmJsonFile() 31 | } 32 | } 33 | 34 | @Before 35 | fun setUp() { 36 | this.docsCmd = CommandLine(docs) 37 | } 38 | 39 | @After 40 | fun tearDown() { 41 | gpmJsonFile.delete() 42 | } 43 | 44 | @Test 45 | fun `Opening docs for installed dependency`() { 46 | // Adding fake transaction 47 | tm.add( 48 | "okhttp", 49 | GradleDep.Type.IMP, 50 | GpmDep( 51 | "okhttp", 52 | GradleDep.Type.IMP.key, 53 | "https://square.github.io/okhttp/", 54 | "jcenter", 55 | "https://github.com/square/okhttp/", 56 | "com.square.okhttp3", 57 | "Material Colors", 58 | "A networking library", 59 | "1.0.0" 60 | ) 61 | ) 62 | 63 | val exitCode = docsCmd.execute("okhttp") 64 | exitCode.should.equal(DocsViewModel.RESULT_DOC_FOUND) 65 | 66 | } 67 | 68 | @Test 69 | fun `Opening docs for not installed dependency`() { 70 | val exitCode = docsCmd.execute("not-installed-dep") 71 | exitCode.should.equal(DocsViewModel.RESULT_NOT_FOUND) 72 | } 73 | 74 | @Test 75 | fun `Opening docs for dependency installed with similar name`() { 76 | tm.add( 77 | "okhttp", 78 | GradleDep.Type.IMP, 79 | GpmDep( 80 | "okhttp", 81 | GradleDep.Type.IMP.key, 82 | "https://square.github.io/okhttp/", 83 | "jcenter", 84 | "https://github.com/square/okhttp/", 85 | "com.square.okhttp3", 86 | "Material Colors", 87 | "A networking library", 88 | "1.0.0" 89 | ) 90 | ) 91 | 92 | tm.add( 93 | "okhttp", 94 | GradleDep.Type.IMP, 95 | GpmDep( 96 | "some-other-lib", 97 | GradleDep.Type.IMP.key, 98 | "https://square.github.io/okhttp/", 99 | "jcenter", 100 | "https://github.com/square/okhttp/", 101 | "some-other-lib", 102 | "some-other-lib", 103 | "Some lib", 104 | "1.0.0" 105 | ) 106 | ) 107 | 108 | val exitCode = docsCmd.execute("okhttp") 109 | exitCode.should.equal(DocsViewModel.RESULT_DOC_FOUND) 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/commands/subcommands/uninstall/UninstallTest.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.commands.subcommands.uninstall 2 | 3 | import com.theapache64.gpm.core.gm.GradleDep 4 | import com.theapache64.gpm.core.gm.GradleManager 5 | import com.theapache64.gpm.data.remote.gpm.models.GpmDep 6 | import com.theapache64.gpm.di.modules.GradleModule 7 | import com.theapache64.gpm.di.modules.TransactionModule 8 | import com.winterbe.expekt.should 9 | import it.cosenonjaviste.daggermock.DaggerMock 10 | import org.junit.After 11 | import org.junit.Before 12 | import org.junit.Rule 13 | import org.junit.Test 14 | import picocli.CommandLine 15 | import java.io.File 16 | import java.io.PrintWriter 17 | import java.io.StringWriter 18 | 19 | class UninstallTest { 20 | 21 | private lateinit var gm: GradleManager 22 | private lateinit var gpmJsonFile: File 23 | private lateinit var tempBuildGradle: File 24 | private lateinit var uninstallCmd: CommandLine 25 | private val uninstall = Uninstall(true) 26 | 27 | @get:Rule 28 | val daggerMock = DaggerMock.rule() { 29 | customizeBuilder { 30 | it.gradleModule(GradleModule(true, modulePath = null)) 31 | .transactionModule(TransactionModule(true)) 32 | } 33 | set { 34 | tempBuildGradle = it.gradleFile() 35 | gpmJsonFile = it.gpmJsonFile() 36 | gm = it.gradleManager() 37 | it.inject(uninstall) 38 | } 39 | } 40 | 41 | 42 | @Before 43 | fun setUp() { 44 | this.uninstallCmd = CommandLine(uninstall).apply { 45 | out = PrintWriter(StringWriter()) 46 | } 47 | } 48 | 49 | @After 50 | fun tearDown() { 51 | tempBuildGradle.delete() 52 | gpmJsonFile.delete() 53 | } 54 | 55 | @Test 56 | fun `Uninstall installed dependency`() { 57 | 58 | // Adding manual dependency 59 | gm.addDep( 60 | "materialcolors", 61 | GradleDep.Type.IMP, 62 | GpmDep( 63 | "materialcolors", 64 | GradleDep.Type.IMP.key, 65 | "https://materialcolors.github.io/materialcolors/", 66 | "jcenter", 67 | "https://github.com/square/materialcolors/", 68 | "com.theah64.materialcolors", 69 | "Material Colors", 70 | "A material color library", 71 | "1.0.0" 72 | ) 73 | ) 74 | 75 | 76 | // Uninstall dep 77 | val uninstallExitCode = uninstallCmd.execute("materialcolors") 78 | uninstallExitCode.should.equal(UninstallViewModel.RESULT_DEP_UNINSTALLED) 79 | } 80 | 81 | @Test 82 | fun `Uninstall dependency which is manually added`() { 83 | // Uninstall dep 84 | val uninstallExitCode = uninstallCmd.execute("robolectric") 85 | uninstallExitCode.should.equal(UninstallViewModel.RESULT_NO_DEP_INSTALLED) 86 | } 87 | 88 | @Test 89 | fun `Uninstall not installed dependency`() { 90 | val uninstallExitCode = uninstallCmd.execute("invalid-library") 91 | uninstallExitCode.should.equal(UninstallViewModel.RESULT_NO_DEP_INSTALLED) 92 | } 93 | 94 | @Test 95 | fun `Uninstall dependency which installed through same dependency name`() { 96 | 97 | gm.addDep( 98 | "same-name", 99 | GradleDep.Type.IMP, 100 | GpmDep( 101 | "same-name-1", 102 | GradleDep.Type.IMP.key, 103 | "", 104 | "", 105 | "", 106 | "com.theah64.same-name", 107 | "Same Name 1", 108 | "Same Name 1", 109 | "1.0.0" 110 | ) 111 | ) 112 | 113 | gm.addDep( 114 | "same-name", 115 | GradleDep.Type.IMP, 116 | GpmDep( 117 | "same-name-2", 118 | GradleDep.Type.IMP.key, 119 | "", 120 | "", 121 | "", 122 | "com.theah64.same-name", 123 | "Same Name 2", 124 | "Same Name 2", 125 | "1.0.0" 126 | ) 127 | ) 128 | 129 | 130 | val uninstallExitCode = uninstallCmd.execute("same-name") 131 | uninstallExitCode.should.equal(UninstallViewModel.RESULT_DEP_UNINSTALLED) 132 | } 133 | 134 | @Test 135 | fun `Uninstall testImplementation`() { 136 | 137 | val libName = "some-testing-library" 138 | gm.addDep( 139 | libName, 140 | GradleDep.Type.TEST_IMP, 141 | GpmDep( 142 | libName, 143 | GradleDep.Type.TEST_IMP.key, 144 | "", 145 | "", 146 | "", 147 | "com.theah64.some-testing-library", 148 | "Some Testing Library", 149 | "Some testing lib for JVM", 150 | "1.0.0" 151 | ) 152 | ) 153 | 154 | 155 | val uninstallExitCode = uninstallCmd.execute("--save-dev", libName) 156 | uninstallExitCode.should.equal(UninstallViewModel.RESULT_DEP_UNINSTALLED) 157 | } 158 | 159 | @Test 160 | fun `Uninstall androidTestImplementation`() { 161 | val libName = "some-testing-library" 162 | gm.addDep( 163 | libName, 164 | GradleDep.Type.AND_TEST_IMP, 165 | GpmDep( 166 | libName, 167 | GradleDep.Type.AND_TEST_IMP.key, 168 | "", 169 | "", 170 | "", 171 | "com.theah64.some-testing-library", 172 | "Some Testing Library", 173 | "Some testing lib for Android", 174 | "1.0.0" 175 | ) 176 | ) 177 | 178 | 179 | val uninstallExitCode = uninstallCmd.execute("--save-dev-android", libName) 180 | uninstallExitCode.should.equal(UninstallViewModel.RESULT_DEP_UNINSTALLED) 181 | } 182 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/core/gm/GradleDependencyTest.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.core.gm 2 | 3 | class GradleDependencyTest { 4 | 5 | 6 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/data/repos/GpmRepoTest.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.data.repos 2 | 3 | import com.theapache64.gpm.data.remote.gpm.GpmApiInterface 4 | import com.theapache64.gpm.rules.MyDaggerMockRule 5 | import com.theapache64.gpm.runBlockingUnitTest 6 | import com.winterbe.expekt.should 7 | import it.cosenonjaviste.daggermock.InjectFromComponent 8 | import kotlinx.coroutines.ExperimentalCoroutinesApi 9 | import org.junit.Before 10 | import org.junit.Rule 11 | import org.junit.Test 12 | 13 | @ExperimentalCoroutinesApi 14 | class GpmRepoTest { 15 | 16 | @get:Rule 17 | val daggerMockRule = MyDaggerMockRule() 18 | 19 | @InjectFromComponent 20 | private lateinit var gpmApiInterface: GpmApiInterface 21 | 22 | @InjectFromComponent 23 | private lateinit var mavenRepo: MavenRepo 24 | 25 | private lateinit var gmpRepo: GpmRepo 26 | 27 | @Before 28 | fun setUp() { 29 | gmpRepo = GpmRepo(gpmApiInterface, mavenRepo) 30 | } 31 | 32 | 33 | @Test 34 | fun `Valid search`() = runBlockingUnitTest { 35 | val dep = gmpRepo.getDep("okhttp") 36 | dep.should.not.`null` 37 | dep!!.name.should.equal("OkHttp") 38 | dep.github.should.equal("square/okhttp") 39 | dep.docs.should.equal("https://square.github.io/okhttp/") 40 | dep.groupId.should.equal("com.squareup.okhttp3") 41 | dep.artifactId.should.equal("okhttp") 42 | dep.getFrom.should.equal("maven") 43 | dep.defaultType.should.equal("implementation") 44 | } 45 | 46 | @Test 47 | fun `Invalid search`() = runBlockingUnitTest { 48 | gmpRepo.getDep("fghdfghfgh").should.`null` 49 | } 50 | 51 | 52 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/data/repos/MavenRepoTest.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.data.repos 2 | 3 | import com.theapache64.gpm.data.remote.maven.MavenApiInterface 4 | import com.theapache64.gpm.rules.MyDaggerMockRule 5 | import com.theapache64.gpm.runBlockingUnitTest 6 | import com.winterbe.expekt.should 7 | import it.cosenonjaviste.daggermock.InjectFromComponent 8 | import org.junit.Before 9 | import org.junit.Rule 10 | import org.junit.Test 11 | import retrofit2.HttpException 12 | 13 | class MavenRepoTest { 14 | 15 | @get:Rule 16 | val daggerMockRule = MyDaggerMockRule() 17 | 18 | @InjectFromComponent 19 | private lateinit var mavenApiInterface: MavenApiInterface 20 | 21 | private lateinit var mavenRepo: MavenRepo 22 | 23 | @Before 24 | fun setUp() { 25 | mavenRepo = MavenRepo(mavenApiInterface) 26 | } 27 | 28 | @Test 29 | fun `Valid search`() = runBlockingUnitTest { 30 | val dep = mavenRepo.search("okhttp") 31 | dep.size.should.above(1) 32 | } 33 | 34 | @Test 35 | fun `Invalid search`() = runBlockingUnitTest { 36 | val dep = mavenRepo.search("dsfgdfgdsf") 37 | dep.size.should.equal(0) 38 | } 39 | 40 | @Test 41 | fun `Getting valid artifact information`() = runBlockingUnitTest { 42 | val info = mavenRepo.getLatestVersion("org.junit.jupiter", "junit-jupiter-api") 43 | info.should.not.`null` 44 | info!!.version.should.not.empty 45 | info.repoName.should.equal("Central") 46 | info.repoUrl.should.equal("/repos/central") 47 | } 48 | 49 | @Test 50 | fun `Getting valid artifact information 2`() = runBlockingUnitTest { 51 | val info = mavenRepo.getLatestVersion("me.tongfei", "progressbar") 52 | info.should.not.`null` 53 | info!!.version.should.not.empty 54 | } 55 | 56 | 57 | @Test(expected = HttpException::class) 58 | fun `Getting invalid artifact information`() = runBlockingUnitTest { 59 | val info = mavenRepo.getLatestVersion("gfh", "jhj") 60 | info.should.`null` 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/rules/MainCoroutineRule.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.rules 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.ExperimentalCoroutinesApi 5 | import kotlinx.coroutines.test.TestCoroutineDispatcher 6 | import kotlinx.coroutines.test.TestCoroutineScope 7 | import kotlinx.coroutines.test.resetMain 8 | import kotlinx.coroutines.test.setMain 9 | import org.junit.rules.TestWatcher 10 | import org.junit.runner.Description 11 | 12 | @Suppress("unused") 13 | @ExperimentalCoroutinesApi 14 | class MainCoroutineRule( 15 | @Suppress("MemberVisibilityCanBePrivate") 16 | val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() 17 | ) : 18 | TestWatcher(), 19 | TestCoroutineScope by TestCoroutineScope(dispatcher) { 20 | 21 | override fun starting(description: Description?) { 22 | super.starting(description) 23 | Dispatchers.setMain(dispatcher) 24 | } 25 | 26 | override fun finished(description: Description?) { 27 | super.finished(description) 28 | Dispatchers.resetMain() 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/rules/MyDaggerMockRule.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.rules 2 | 3 | import com.theapache64.gpm.commands.subcommands.install.DaggerInstallComponent 4 | import com.theapache64.gpm.commands.subcommands.install.InstallComponent 5 | import com.theapache64.gpm.di.modules.CommandModule 6 | 7 | import com.theapache64.gpm.di.modules.GradleModule 8 | import com.theapache64.gpm.di.modules.NetworkModule 9 | import com.theapache64.gpm.di.modules.TransactionModule 10 | import it.cosenonjaviste.daggermock.DaggerMockRule 11 | 12 | class MyDaggerMockRule : DaggerMockRule( 13 | InstallComponent::class.java, 14 | NetworkModule(), 15 | GradleModule(isFromTest = true, null) 16 | ) { 17 | init { 18 | customizeBuilder { 19 | it.transactionModule(TransactionModule(true)) 20 | .commandModule(CommandModule(true)) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/utils/GradleManagerTest.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.utils 2 | 3 | import com.theapache64.gpm.commands.subcommands.install.InstallComponent 4 | import com.theapache64.gpm.core.TransactionManager 5 | import com.theapache64.gpm.core.gm.GradleDep 6 | import com.theapache64.gpm.core.gm.GradleManager 7 | import com.theapache64.gpm.data.remote.gpm.models.GpmDep 8 | import com.theapache64.gpm.di.modules.CommandModule 9 | import com.theapache64.gpm.di.modules.GradleModule 10 | import com.theapache64.gpm.di.modules.TransactionModule 11 | import com.winterbe.expekt.should 12 | import it.cosenonjaviste.daggermock.DaggerMock 13 | import it.cosenonjaviste.daggermock.InjectFromComponent 14 | import org.junit.Before 15 | import org.junit.Rule 16 | import org.junit.Test 17 | import java.io.File 18 | 19 | class GradleManagerTest { 20 | 21 | private lateinit var gradleManager: GradleManager 22 | private lateinit var gradleFile: File 23 | 24 | 25 | @get:Rule 26 | val daggerRule = DaggerMock.rule( 27 | GradleModule(isFromTest = true, modulePath = null), 28 | TransactionModule(isFromTest = true), 29 | CommandModule(isFromTest = true) 30 | ) { 31 | set { 32 | gradleFile = it.gradleFile() 33 | } 34 | } 35 | 36 | @InjectFromComponent 37 | lateinit var tm: TransactionManager 38 | 39 | @Before 40 | fun setUp() { 41 | this.gradleManager = GradleManager(tm, gradleFile) 42 | } 43 | 44 | @Test 45 | fun whenGetDependenciesSize_then10() { 46 | gradleManager.parseDeps().size.should.equal(37) 47 | gradleManager.addDep( 48 | "my-artifact", 49 | GradleDep.Type.IMP, 50 | GpmDep( 51 | "myArtifact", 52 | GradleDep.Type.IMP.key, 53 | "https://mylib.docs", 54 | "jcenter", 55 | "https://github.com/userx/myArtifact", 56 | "myGroup", 57 | "My Lib", 58 | "Some description", 59 | "1.0.0" 60 | ) 61 | ) 62 | gradleManager.parseDeps().size.should.equal(38) 63 | } 64 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/utils/GradleUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.utils 2 | 3 | import com.theapache64.gpm.core.gm.GradleDep 4 | import com.winterbe.expekt.should 5 | import org.junit.Test 6 | 7 | class GradleUtilsTest { 8 | 9 | 10 | @Test 11 | fun `Full signature for implementation`() { 12 | 13 | val gradleDependency = GradleUtils.getFullSignature( 14 | GradleDep.Type.IMP.key, 15 | "org.mockito", 16 | "mockito", 17 | "3.3.3", 18 | false 19 | ) 20 | 21 | gradleDependency.should.equal( 22 | "implementation 'org.mockito:mockito:3.3.3'" 23 | ) 24 | } 25 | 26 | @Test 27 | fun `Full signature for testImplementation`() { 28 | 29 | val gradleDependency = GradleUtils.getFullSignature( 30 | GradleDep.Type.TEST_IMP.key, 31 | "org.mockito", 32 | "mockito", 33 | "3.3.3", 34 | false 35 | ) 36 | 37 | gradleDependency.should.equal( 38 | "testImplementation 'org.mockito:mockito:3.3.3'" 39 | ) 40 | } 41 | 42 | @Test 43 | fun `Full signature for androidTestImplementation`() { 44 | 45 | val gradleDependency = GradleUtils.getFullSignature( 46 | GradleDep.Type.AND_TEST_IMP.key, 47 | "org.mockito", 48 | "mockito", 49 | "3.3.3", 50 | false 51 | ) 52 | 53 | gradleDependency.should.equal( 54 | "androidTestImplementation 'org.mockito:mockito:3.3.3'" 55 | ) 56 | } 57 | 58 | 59 | @Test 60 | fun `Full signature for implementation - kts`() { 61 | 62 | val gradleDependency = GradleUtils.getFullSignature( 63 | GradleDep.Type.IMP.key, 64 | "org.mockito", 65 | "mockito", 66 | "3.3.3", 67 | true 68 | ) 69 | 70 | gradleDependency.should.equal( 71 | "implementation(\"org.mockito:mockito:3.3.3\")" 72 | ) 73 | } 74 | 75 | @Test 76 | fun `Full signature for testImplementation - kts`() { 77 | 78 | val gradleDependency = GradleUtils.getFullSignature( 79 | GradleDep.Type.TEST_IMP.key, 80 | "org.mockito", 81 | "mockito", 82 | "3.3.3", 83 | true 84 | ) 85 | 86 | gradleDependency.should.equal( 87 | "testImplementation(\"org.mockito:mockito:3.3.3\")" 88 | ) 89 | } 90 | 91 | @Test 92 | fun `Full signature for androidTestImplementation - kts`() { 93 | 94 | val gradleDependency = GradleUtils.getFullSignature( 95 | GradleDep.Type.AND_TEST_IMP.key, 96 | "org.mockito", 97 | "mockito", 98 | "3.3.3", 99 | true 100 | ) 101 | 102 | gradleDependency.should.equal( 103 | "androidTestImplementation(\"org.mockito:mockito:3.3.3\")" 104 | ) 105 | } 106 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/theapache64/gpm/utils/StringUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package com.theapache64.gpm.utils 2 | 3 | import com.winterbe.expekt.should 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class StringUtilsTest { 8 | 9 | @Test 10 | fun `Test nested braces`() { 11 | val input = """ 12 | { 13 | x 14 | { 15 | y 16 | } 17 | } 18 | """.trimIndent() 19 | 20 | // open = 13, close = 29 21 | val endingIndex = StringUtils.getClosingIndexOf(input, '{', 13, '}') 22 | endingIndex.should.equal(29) 23 | } 24 | 25 | 26 | @Test 27 | fun `Comment break - single-line`() { 28 | val input = """ 29 | This library allows you to use your PC webcam. 30 | """.trimIndent() 31 | val expectedOutput = """ 32 | This library allows you to use your PC webcam. 33 | """.trimIndent() 34 | val actualOutput = StringUtils.breakOnAndComment(80, input) 35 | assertEquals(actualOutput, expectedOutput) 36 | } 37 | 38 | @Test 39 | fun `Comment break - multiline`() { 40 | val input = """ 41 | This library allows you to use your PC webcam, IP or network cameras directly from Java. It's compatible with most operating systems (Windows, Linux, MacOS). 42 | """.trimIndent() 43 | val expectedOutput = """ 44 | This library allows you to use your PC webcam, IP or network cameras directly from 45 | // Java. It's compatible with most operating systems (Windows, Linux, MacOS). 46 | """.trimIndent() 47 | val actualOutput = StringUtils.breakOnAndComment(80, input) 48 | assertEquals(actualOutput, expectedOutput) 49 | } 50 | } -------------------------------------------------------------------------------- /src/test/resources/sample.build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | id 'kotlin-android-extensions' 6 | id "org.jetbrains.kotlin.plugin.allopen" version "$kotlin_version" 7 | } 8 | 9 | allOpen { 10 | annotation("com.thinkpalm.test.movieapp.utils.test.OpenForTesting") 11 | } 12 | 13 | android { 14 | compileSdkVersion compile_sdk_version 15 | defaultConfig { 16 | applicationId "com.thinkpalm.test.movieapp" 17 | minSdkVersion min_sdk_version 18 | targetSdkVersion target_sdk_version 19 | versionCode 10001 20 | versionName "1.0.0-alpha01" 21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 22 | multiDexEnabled true 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | 31 | buildFeatures { 32 | dataBinding true 33 | } 34 | 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_8 37 | targetCompatibility JavaVersion.VERSION_1_8 38 | } 39 | 40 | kotlinOptions { 41 | jvmTarget = JavaVersion.VERSION_1_8.toString() 42 | } 43 | 44 | // To make `sharedTest` accessible from both unit test and instrumentation test 45 | sourceSets { 46 | String sharedTestDir = 'src/sharedTest/java' 47 | test { 48 | java.srcDir sharedTestDir 49 | } 50 | androidTest { 51 | java.srcDir sharedTestDir 52 | } 53 | } 54 | 55 | testOptions { 56 | unitTests { 57 | includeAndroidResources true 58 | returnDefaultValues true 59 | } 60 | } 61 | } 62 | 63 | dependencies { 64 | 65 | //Core 66 | implementation fileTree(dir: 'libs', include: ['*.jar']) 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 68 | implementation "androidx.appcompat:appcompat:$appcompat_version" 69 | implementation "androidx.core:core-ktx:$ktx_version" 70 | implementation "androidx.constraintlayout:constraintlayout:$constraint_version" 71 | implementation "com.google.android.material:material:$material_version" 72 | 73 | // Coroutines 74 | def coroutines_version = '1.3.5' 75 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" 76 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" 77 | androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" 78 | 79 | // Lifecycle extension 80 | implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" 81 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 82 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" 83 | 84 | // Dagger 2 85 | def dagger_version = '2.27' 86 | implementation "com.google.dagger:dagger:$dagger_version" 87 | implementation "com.google.dagger:dagger-android-support:$dagger_version" 88 | 89 | 90 | // MaterialColors 91 | implementation 'com.theah64.materialcolors:materialcolors:1.0.0' 92 | 93 | // TwinKill 94 | def twinkill_version = '1.1.0-alpha05' 95 | implementation "com.theapache64.twinkill:core:$twinkill_version" 96 | testImplementation "com.theapache64.twinkill:test:$twinkill_version" 97 | androidTestImplementation "com.theapache64.twinkill:test:$twinkill_version" 98 | implementation "com.theapache64.twinkill:logger:$twinkill_version" 99 | 100 | // Moshi 101 | implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" 102 | 103 | // Test 104 | testImplementation "junit:junit:$junit_version" 105 | testImplementation "org.mockito:mockito-core:$mockito_version" 106 | testImplementation "org.mockito:mockito-inline:$mockito_version" 107 | androidTestImplementation "org.mockito:mockito-android:$mockito_version" 108 | testImplementation('com.winterbe:expekt:0.5.0') { 109 | exclude group: "org.jetbrains.kotlin" 110 | } 111 | 112 | // Robolectric 113 | testImplementation "org.robolectric:robolectric:4.3.1" 114 | 115 | // Mocktio Kotlin 116 | testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" 117 | androidTestImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" 118 | 119 | // Arch testing 120 | def core_testing_version = '2.1.0' 121 | testImplementation "androidx.arch.core:core-testing:$core_testing_version" 122 | androidTestImplementation "androidx.arch.core:core-testing:$core_testing_version" 123 | testImplementation 'androidx.test.ext:junit:1.1.1' 124 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 125 | 126 | // Espresso 127 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version" 128 | androidTestImplementation('com.schibsted.spain:barista:3.4.0') { 129 | exclude group: 'org.jetbrains.kotlin' 130 | } 131 | 132 | // DaggerMock 133 | testImplementation 'com.github.fabioCollini.daggermock:daggermock:0.8.5' 134 | androidTestImplementation 'com.github.fabioCollini.daggermock:daggermock:0.8.5' 135 | testImplementation 'com.github.fabioCollini.daggermock:daggermock-kotlin:0.8.5' 136 | androidTestImplementation 'com.github.fabioCollini.daggermock:daggermock-kotlin:0.8.5' 137 | } 138 | --------------------------------------------------------------------------------