├── .circleci └── config.yml ├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── settings.gradle └── src ├── main ├── kotlin │ ├── config │ │ ├── DAOs.kt │ │ └── WurstProjectConfig.kt │ ├── file │ │ ├── CLICommand.kt │ │ ├── DependencyManager.kt │ │ ├── Download.kt │ │ ├── ExceptionHandler.kt │ │ ├── FileUtils.kt │ │ ├── SetupApp.kt │ │ ├── SetupMain.kt │ │ ├── YamlHelper.kt │ │ └── ZipArchiveExtractor.kt │ ├── global │ │ ├── CLIParser.kt │ │ ├── InstallationManager.kt │ │ ├── Log.kt │ │ └── WurstConfigData.kt │ ├── net │ │ └── ConnectionManager.kt │ ├── ui │ │ ├── AddRepoDialog.kt │ │ ├── MainWindow.kt │ │ ├── SetupUpdateDialog.kt │ │ ├── UiManager.kt │ │ └── UpdateFoundDialog.kt │ └── workers │ │ ├── CompilerUpdateWorker.kt │ │ ├── DependencyVerifierWorker.kt │ │ ├── DownloadWithProgressWorker.kt │ │ ├── ExtractWorker.kt │ │ ├── OnlineCheckWorker.kt │ │ ├── ProjectCreateWorker.kt │ │ ├── ProjectUpdateWorker.kt │ │ ├── RemoveWurstWorker.kt │ │ └── WurstBuildCheckWorker.kt └── resources │ ├── exitdown.png │ ├── exithover.png │ ├── exitup.png │ ├── icon.png │ ├── logback.xml │ ├── minimizedown.png │ ├── minimizehover.png │ ├── minimizeup.png │ └── wbschema.json └── test └── kotlin ├── CMDTests.kt ├── ConnectivityTests.kt └── InstallationTests.kt /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | 6 | docker: 7 | - image: cimg/openjdk:11.0 8 | 9 | steps: 10 | - checkout 11 | 12 | - run: 13 | name: Build 14 | command: ./gradlew clean versionInfoFile dist 15 | 16 | - run: 17 | name: Test 18 | command: ./gradlew test --info --stacktrace 19 | 20 | - run: 21 | name: Jacoco 22 | command: ./gradlew jacocoTestReport 23 | 24 | - run: 25 | name: Codecov 26 | command: bash <(curl -s https://codecov.io/bash) 27 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = space 4 | indent_size = 4 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | build/ 3 | .idea 4 | .gradle/ 5 | _build/ 6 | classes/ 7 | out-win/ 8 | out-mac/ 9 | out-linux/ 10 | out/ 11 | WurstSetup.iml 12 | downloads/ 13 | src-gen/ 14 | 15 | setup.log 16 | local.properties 17 | wurstpack_compiler.zip 18 | buildproject/ 19 | compiled.j.txt 20 | myname/ 21 | testproject/ 22 | ptrtestproject/ 23 | temp/ 24 | invalidbuild/ 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | 5 | before_install: 6 | - chmod +x gradlew 7 | 8 | cache: 9 | directories: 10 | - $HOME/.gradle/wrapper 11 | - $HOME/.gradle/caches/modules-2/files-2.1 12 | 13 | script: 14 | - ./gradlew clean versionInfoFile dist 15 | - ./gradlew test --info --stacktrace 16 | 17 | after_success: 18 | - ./gradlew jacocoTestReport 19 | - bash <(curl -s https://codecov.io/bash) 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/wurstscript/WurstSetup.svg?style=svg)](https://circleci.com/gh/wurstscript/WurstSetup) [![codebeat badge](https://codebeat.co/badges/279cc0f7-8ff6-400a-a763-8861690962dc)](https://codebeat.co/projects/github-com-wurstscript-wurstsetup-master) [![codecov](https://codecov.io/gh/wurstscript/WurstSetup/branch/master/graph/badge.svg)](https://codecov.io/gh/wurstscript/WurstSetup) 2 | 3 | 4 | # WurstScript Setup App 5 | 6 | Allows automated installation of a wurstscript environment and project setup. 7 | 8 | ## Grill 9 | 10 | Grill is the name of the CLI and dependency manager used by the UI internally. 11 | Pro users can make use of grill from the shell: 12 | 13 | ### Update/Remove wurst installation 14 | 15 | Update or remove the global wurst installation by using the special `wurstscript` identifier. 16 | 17 | ```cmd 18 | > grill install wurstscript 19 | > grill remove wurstscript 20 | ``` 21 | 22 | ### Creating a new project 23 | 24 | To create a new project, use `generate` and supply your name of choice. 25 | 26 | ```cmd 27 | > grill generate 28 | ``` 29 | 30 | ### Updating a project 31 | 32 | By not passing any additional arguments grill will assume that the execution location is a wurst project. 33 | 34 | To update all project dependencies use: 35 | 36 | ```cmd 37 | > grill install 38 | ``` 39 | 40 | To add a new dependency to your project, use: 41 | 42 | ```cmd 43 | > grill install 44 | ``` 45 | 46 | ### Testing a project 47 | 48 | Use `test` to compile the project at the current location and run unit tests. 49 | 50 | ```cmd 51 | > grill test 52 | ``` 53 | 54 | 55 | ### Building the project 56 | 57 | Use `build` to generate an output map according to `wurst.build` specifications. 58 | 59 | ```cmd 60 | > grill build 61 | ``` 62 | 63 | ## How it works 64 | 65 | ### Wurst Installation 66 | 67 | The wurst compiler gets downloaded into the users home directory into a wurst folder `~/.wurst` 68 | 69 | ### Project Generation 70 | 71 | The setup app downloads this repo https://github.com/wurstscript/wurst-project-template as a wurst project template and then inserts the necessary local parths as well as generating the wurst.dependencies file. 72 | Dependencies are stored in `_build/dependencies/`. 73 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.jetbrains.kotlin.jvm' 2 | apply plugin: 'java' 3 | apply plugin: 'application' 4 | apply plugin: 'idea' 5 | apply plugin: 'jacoco' 6 | 7 | String genDir = "$projectDir/src-gen" 8 | 9 | sourceSets { 10 | main { 11 | java { 12 | srcDir 'src/main/java' 13 | srcDir genDir 14 | } 15 | resources { 16 | srcDir 'src/main/resources' 17 | } 18 | } 19 | } 20 | 21 | java { 22 | toolchain { 23 | languageVersion.set(JavaLanguageVersion.of(11)) 24 | } 25 | } 26 | 27 | kotlin { 28 | jvmToolchain { 29 | languageVersion.set(JavaLanguageVersion.of(11)) 30 | } 31 | } 32 | repositories { 33 | mavenCentral() 34 | maven { url 'https://jitpack.io' } 35 | } 36 | 37 | dependencies { 38 | implementation group: 'us.monoid.web', name: 'resty', version: '0.3.2' 39 | implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.9.0.202403050737-r' 40 | implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit.ssh.apache', version: '6.9.0.202403050737-r' 41 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0' 42 | implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.17.0' 43 | implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.0' 44 | implementation 'com.github.frotty:SwingDarkFlatTable:1d9ae26e69' 45 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 46 | implementation 'io.github.microutils:kotlin-logging:3.0.5' 47 | implementation 'com.github.Frotty:SimpleRegistry:f96dda96bd' 48 | implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32' 49 | implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.4.14' 50 | testImplementation 'org.testng:testng:7.8.0' 51 | testImplementation group: 'com.github.stefanbirkner', name: 'system-lambda', version: '1.2.1' 52 | } 53 | 54 | test { 55 | useTestNG() 56 | } 57 | 58 | jacocoTestReport { 59 | reports { 60 | xml.required.set(true) 61 | } 62 | } 63 | 64 | configurations.all { 65 | exclude group: "org.slf4j", module: "slf4j-log4j12" 66 | exclude group: "log4j", module: "log4j" 67 | } 68 | 69 | buildscript { 70 | ext.kotlin_version = '1.9.23' 71 | repositories { 72 | flatDir dirs: 'src/main/resources/' 73 | mavenCentral() 74 | } 75 | dependencies { 76 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 77 | } 78 | } 79 | 80 | version '1.4.0.0' 81 | 82 | task versionInfoFile { 83 | description "Generates a file CompileTimeInfo.java with version number etc." 84 | 85 | doLast { 86 | def dir = new File("$projectDir/src-gen/file/") 87 | dir.mkdirs() 88 | def f = new File(dir, 'CompileTimeInfo.kt') 89 | 90 | String gitRevision = "unknown-version" 91 | String gitRevisionlong = "unknown-version" 92 | 93 | new ByteArrayOutputStream().withStream { os -> 94 | exec { 95 | executable = 'git' 96 | args = ['describe', '--tags', '--always'] 97 | standardOutput = os 98 | } 99 | gitRevision = os.toString().trim() 100 | } 101 | 102 | new ByteArrayOutputStream().withStream { os -> 103 | exec { 104 | executable = 'git' 105 | args = ['describe', '--tags', '--always', '--abbrev=0'] 106 | standardOutput = os 107 | } 108 | gitRevisionlong = os.toString().trim() 109 | } 110 | 111 | String setupVersion = "${version}-${gitRevision}" 112 | 113 | 114 | String currentTime = new Date().format("yyyy/MM/dd KK:mm:ss") 115 | 116 | f.text = """ 117 | package file 118 | 119 | object CompileTimeInfo { 120 | \tval time="${currentTime}" 121 | \tval revision="${gitRevision}" 122 | \tval revisionLong="${gitRevisionlong}" 123 | \tval version="${setupVersion}" 124 | }""" 125 | 126 | } 127 | } 128 | 129 | compileKotlin.dependsOn versionInfoFile 130 | 131 | task dist(type: Jar) { 132 | from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } 133 | exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA' 134 | manifest { 135 | attributes 'Implementation-Title': 'Wurst Setup', 136 | 'Implementation-Version': version, 137 | 'Main-Class': 'file.SetupMain' 138 | } 139 | with jar 140 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 141 | } 142 | dist.dependsOn classes 143 | dist.dependsOn versionInfoFile 144 | 145 | dist.archiveFileName = "WurstSetup.jar" 146 | 147 | task copy_jar(type: Copy) { 148 | mkdir("downloads/") 149 | from 'build/libs/' 150 | into 'downloads/' 151 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 152 | } 153 | 154 | task proguardDist(type: Copy) { 155 | 156 | } 157 | proguardDist.dependsOn dist 158 | 159 | processResources { 160 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 161 | } 162 | 163 | copy_jar.dependsOn(dist) 164 | 165 | mainClassName = "file.SetupMain" 166 | 167 | compileKotlin { 168 | kotlinOptions { 169 | jvmTarget = "11" 170 | } 171 | } 172 | compileTestKotlin { 173 | kotlinOptions { 174 | jvmTarget = "11" 175 | } 176 | } 177 | 178 | 179 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wurstscript/WurstSetup/dc99e0c7805e80fa21c60034896e871475398355/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | # http://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, switch paths to Windows format before running java 129 | if $cygwin ; 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=$((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 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /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 http://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 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This settings file was auto generated by the Gradle buildInit task 3 | * by 'Frotty' at '22.06.17 01:45' with Gradle 2.12 4 | * 5 | * The settings file is used to specify which projects to include in your build. 6 | * In a single project build this file can be empty or even removed. 7 | * 8 | * Detailed information about configuring a multi-project build in Gradle can be found 9 | * in the user guide at https://docs.gradle.org/2.12/userguide/multi_project_builds.html 10 | */ 11 | 12 | /* 13 | // To declare projects as part of a multi-project build use the 'include' method 14 | include 'shared' 15 | include 'api' 16 | include 'services:webservice' 17 | */ 18 | 19 | rootProject.name = 'WurstSetup' 20 | -------------------------------------------------------------------------------- /src/main/kotlin/config/DAOs.kt: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude 4 | import java.util.* 5 | import kotlin.collections.ArrayList 6 | 7 | 8 | const val CONFIG_FILE_NAME = "wurst.build" 9 | 10 | /** 11 | * The root DAO that contains the child DAOs. 12 | * Represents a complete wurst.build file. 13 | */ 14 | @JsonInclude(JsonInclude.Include.NON_DEFAULT) 15 | data class WurstProjectConfigData(var projectName: String, 16 | val dependencies: ArrayList = ArrayList(emptyList()), 17 | // val jobs: ArrayList = 18 | // ArrayList(Arrays.asList(WurstProjectBuildJob("run", 19 | // ArrayList(Arrays.asList("runcompiletimefunctions", "injectobjects", "stacktraces"))), 20 | // WurstProjectBuildJob("build", 21 | // ArrayList(Arrays.asList("runcompiletimefunctions", "injectobjects", "stacktraces", "opt", "inline", "localOptimizations"))))), 22 | val buildMapData: WurstProjectBuildMapData = WurstProjectBuildMapData()) { 23 | constructor() : this("unnamed") 24 | } 25 | 26 | /** All data needed to generate the output map */ 27 | @JsonInclude(JsonInclude.Include.NON_DEFAULT) 28 | data class WurstProjectBuildMapData(val name: String = "", 29 | val fileName: String = "", 30 | val author: String = "", 31 | val scenarioData: WurstProjectBuildScenarioData = WurstProjectBuildScenarioData(loadingScreen=null), 32 | val optionsFlags: WurstProjectBuildOptionFlagsData = WurstProjectBuildOptionFlagsData(), 33 | val players: ArrayList = ArrayList(), 34 | val forces: ArrayList = ArrayList()) 35 | 36 | /** Wurst job data with list of arguments */ 37 | @JsonInclude(JsonInclude.Include.NON_DEFAULT) 38 | data class WurstProjectBuildJob(val name: String = "DefaultJobName", 39 | val args: ArrayList = ArrayList(Arrays.asList())) 40 | 41 | /** Scenario related information */ 42 | @JsonInclude(JsonInclude.Include.NON_DEFAULT) 43 | data class WurstProjectBuildScenarioData(val description: String = "", 44 | val suggestedPlayers: String = "", 45 | var loadingScreen: WurstProjectBuildLoadingScreenData?) 46 | 47 | /** Load screen information. */ 48 | @JsonInclude(JsonInclude.Include.NON_DEFAULT) 49 | data class WurstProjectBuildLoadingScreenData(val model: String = "", 50 | val background: String = "", 51 | val title: String = "", 52 | val subTitle: String = "", 53 | val text: String = "") 54 | 55 | /** Map build flags */ 56 | @JsonInclude(JsonInclude.Include.NON_DEFAULT) 57 | data class WurstProjectBuildOptionFlagsData(val hideMinimapPreview: Boolean = false, 58 | val forcesFixed: Boolean = false, 59 | val maskedAreasPartiallyVisible: Boolean = false, 60 | val showWavesOnCliffShores: Boolean = false, 61 | val showWavesOnRollingShores: Boolean = false, 62 | val useItemClassificationSystem: Boolean = false) 63 | 64 | /** Data for one force (team) in the map */ 65 | @JsonInclude(JsonInclude.Include.NON_DEFAULT) 66 | data class WurstProjectBuildForce(val name: String = "DefaultForce", 67 | val flags: WurstProjectBuildForceFlags = WurstProjectBuildForceFlags(), 68 | val playerIds: IntArray = intArrayOf(0)) { 69 | 70 | override fun equals(other: Any?): Boolean { 71 | if (this === other) return true 72 | if (javaClass != other?.javaClass) return false 73 | 74 | other as WurstProjectBuildForce 75 | 76 | if (name != other.name) return false 77 | if (flags != other.flags) return false 78 | if (!Arrays.equals(playerIds, other.playerIds)) return false 79 | 80 | return true 81 | } 82 | 83 | override fun hashCode(): Int { 84 | var result = name.hashCode() 85 | result = 31 * result + flags.hashCode() 86 | result = 31 * result + Arrays.hashCode(playerIds) 87 | return result 88 | } 89 | } 90 | 91 | /** Force flags */ 92 | @JsonInclude(JsonInclude.Include.NON_DEFAULT) 93 | data class WurstProjectBuildForceFlags(val allied: Boolean = true, 94 | val alliedVictory: Boolean = true, 95 | val sharedVision: Boolean = true, 96 | val sharedControl: Boolean = false, 97 | val sharedControlAdvanced: Boolean = false) 98 | 99 | /** Player race */ 100 | enum class Race { 101 | HUMAN, ORC, UNDEAD, NIGHT_ELF, SELECTABLE 102 | } 103 | 104 | /** Player controller */ 105 | enum class Controller { 106 | USER, COMPUTER, NEUTRAL, RESCUABLE 107 | } 108 | 109 | /** Data for one player */ 110 | @JsonInclude(JsonInclude.Include.NON_ABSENT) 111 | data class WurstProjectBuildPlayer(val id: Int, 112 | val name: String?, 113 | val race: Race?, 114 | val controller: Controller?, 115 | val fixedStartLoc: Boolean?) 116 | 117 | -------------------------------------------------------------------------------- /src/main/kotlin/config/WurstProjectConfig.kt: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | 4 | import com.fasterxml.jackson.core.JsonFactory 5 | import com.fasterxml.jackson.core.JsonParser 6 | import com.fasterxml.jackson.core.json.JsonReadFeature 7 | import com.fasterxml.jackson.databind.ObjectMapper 8 | import com.fasterxml.jackson.databind.cfg.MapperBuilder 9 | import com.fasterxml.jackson.databind.json.JsonMapper 10 | import com.fasterxml.jackson.databind.node.ObjectNode 11 | import file.* 12 | import global.InstallationManager 13 | import global.Log 14 | import mu.KotlinLogging 15 | import ui.UiManager 16 | import java.io.IOException 17 | import java.nio.file.Files 18 | import java.nio.file.Path 19 | import java.nio.file.StandardOpenOption 20 | import javax.swing.JOptionPane 21 | /** 22 | * Created by Frotty on 10.07.2017. 23 | */ 24 | 25 | object WurstProjectConfig { 26 | private val MAPPER = JsonMapper.builder().enable(JsonReadFeature.ALLOW_TRAILING_COMMA).build() 27 | 28 | private val schema by lazy { javaClass.classLoader.getResource("wbschema.json") } 29 | private val log = KotlinLogging.logger {} 30 | 31 | fun handleCreate(projectRoot: Path, gameRoot: Path?, projectConfig: WurstProjectConfigData) { 32 | try { 33 | createProject(projectRoot, gameRoot, projectConfig) 34 | UiManager.refreshComponents() 35 | } catch (e: Exception) { 36 | Log.print("\n===ERROR PROJECT CREATE===\n" + e.message + "\nPlease report here: github.com/wurstscript/WurstScript/issues\n") 37 | } 38 | 39 | } 40 | 41 | @Throws(IOException::class) 42 | fun loadProject(buildFile: Path): WurstProjectConfigData? { 43 | Log.println("Loading project..") 44 | if (Files.exists(buildFile) && buildFile.fileName.toString().equals(CONFIG_FILE_NAME, ignoreCase = true)) { 45 | val config = YamlHelper.loadProjectConfig(buildFile) 46 | val projectRoot = buildFile.parent 47 | if (config.projectName.isEmpty()) { 48 | config.projectName = projectRoot?.fileName.toString() 49 | saveProjectConfig(projectRoot, config) 50 | } 51 | Log.print("done\n") 52 | return config 53 | } 54 | return null 55 | } 56 | 57 | @Throws(Exception::class) 58 | private fun createProject(projectRoot: Path, gameRoot: Path?, projectConfig: WurstProjectConfigData) { 59 | Log.print("Creating project root..") 60 | if (Files.exists(projectRoot) && Files.list(projectRoot).filter { !Files.isDirectory(it) }.findAny().isPresent) { 61 | log.error("Project root already exists and contains files") 62 | Log.print("\nError: Project root already exists!\n") 63 | } else { 64 | Files.createDirectories(projectRoot) 65 | Log.print("done\n") 66 | 67 | Log.print("Download template..") 68 | log.info("⏬ Downloading template..") 69 | Download.downloadBareboneProject { 70 | extractDownload(it, projectRoot, gameRoot, projectConfig) 71 | } 72 | } 73 | } 74 | 75 | private fun extractDownload(it: Path, projectRoot: Path, gameRoot: Path?, projectConfig: WurstProjectConfigData) { 76 | Log.println(" done.") 77 | 78 | Log.print("Extracting template..") 79 | val extractSuccess = ZipArchiveExtractor.extractArchive(it, projectRoot) 80 | Files.delete(it) 81 | if (extractSuccess) { 82 | Log.print("done\n") 83 | cleanupDownload(projectRoot) 84 | } else { 85 | Log.print("error\n") 86 | JOptionPane.showMessageDialog(null, 87 | "Error: Cannot extract patch files.\nWurst might still be in use.\nClose any Wurst, VSCode or Eclipse instances before updating.", 88 | "Error Massage", JOptionPane.ERROR_MESSAGE) 89 | } 90 | 91 | setupEnvironment(projectRoot, gameRoot, projectConfig) 92 | 93 | log.info("✔ Project generated.") 94 | UiManager.refreshComponents() 95 | } 96 | 97 | private fun cleanupDownload(projectRoot: Path) { 98 | Log.print("Clean up..") 99 | val folder = projectRoot.resolve("wurst-project-template-master") 100 | copyFolder(folder, projectRoot) 101 | Files.walk(folder).sorted { a, b -> b.compareTo(a) }.forEach { p -> 102 | try { 103 | Files.delete(p) 104 | } catch (e: IOException) { 105 | } 106 | } 107 | } 108 | 109 | private fun setupEnvironment(projectRoot: Path, gameRoot: Path?, projectConfig: WurstProjectConfigData) { 110 | Log.print("done\n") 111 | 112 | setupVSCode(projectRoot, gameRoot) 113 | 114 | saveProjectConfig(projectRoot, projectConfig) 115 | 116 | DependencyManager.updateDependencies(projectRoot, projectConfig) 117 | 118 | Log.print("---\n\n") 119 | if (gameRoot == null || !Files.exists(gameRoot)) { 120 | Log.print("Warning: Your game path has not been set.\n") 121 | } 122 | Log.print("Your project has been successfully created!\n" + "You can now open your project folder in VSCode.\nOpen the wurst/Hello.wurst package to continue.\n") 123 | } 124 | 125 | 126 | @Throws(IOException::class) 127 | fun saveProjectConfig(projectRoot: Path, projectConfig: WurstProjectConfigData) { 128 | val projectYaml = YamlHelper.dumpProjectConfig(projectConfig) 129 | Files.write(projectRoot.resolve(CONFIG_FILE_NAME), projectYaml.toByteArray()) 130 | } 131 | 132 | 133 | @Throws(IOException::class) 134 | private fun setupVSCode(projectRoot: Path?, gamePath: Path?) { 135 | Log.print("Updating vsconfig..") 136 | if (projectRoot == null || !Files.exists(projectRoot)) { 137 | throw IOException("Project root does not exist!") 138 | } 139 | val vsCode = projectRoot.resolve(".vscode/settings.json") 140 | createConfigFile(vsCode) 141 | val wbschema = projectRoot.resolve(".vscode/wbschema.json") 142 | 143 | wbschema.let { 144 | Files.write(it, schema!!.readBytes()) 145 | } 146 | 147 | setConfigValues(vsCode, gamePath?.toAbsolutePath()?.toString() ?: "") 148 | Log.print("done.\n") 149 | } 150 | 151 | private fun setConfigValues(vsCode: Path, gamePath: String) { 152 | val json = modifySettingsJson(vsCode, gamePath) 153 | 154 | Files.write(vsCode, json.toByteArray(), StandardOpenOption.TRUNCATE_EXISTING) 155 | } 156 | 157 | private fun createConfigFile(vsCode: Path) { 158 | if (!Files.exists(vsCode)) { 159 | Files.createDirectories(vsCode.parent) 160 | Files.write(vsCode, VSCODE_MIN_CONFIG.toByteArray(), StandardOpenOption.CREATE_NEW) 161 | } 162 | } 163 | 164 | private fun modifySettingsJson(vsCode: Path, gamePath: String): String { 165 | val json = String(Files.readAllBytes(vsCode)) 166 | val absolutePath = InstallationManager.getCompilerPath() 167 | val jsonNode = MAPPER.readTree(json) as ObjectNode 168 | 169 | jsonNode.put("wurst.wurstJar", absolutePath) 170 | 171 | if (!gamePath.isBlank()) { 172 | jsonNode.put("wurst.wc3path", gamePath) 173 | } 174 | val schemaNode = MAPPER.createObjectNode() 175 | schemaNode.put("./.vscode/wbschema.json", "/wurst.build") 176 | jsonNode.replace("yaml.schemas", schemaNode) 177 | return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode) 178 | } 179 | 180 | fun handleUpdate(projectRoot: Path, gamePath: Path?, config: WurstProjectConfigData) { 181 | Log.print("Updating project...\n") 182 | try { 183 | setupVSCode(projectRoot, gamePath) 184 | saveProjectConfig(projectRoot, config) 185 | DependencyManager.updateDependencies(projectRoot, config) 186 | 187 | Log.print("Project successfully updated!\nReload vscode to apply the changed dependencies.\n") 188 | UiManager.refreshComponents() 189 | } catch (e: Exception) { 190 | e.printStackTrace() 191 | Log.print("\n===ERROR PROJECT UPDATE===\n" + e.message + "\nPlease report here: github.com/wurstscript/WurstScript/issues\n") 192 | } 193 | 194 | } 195 | 196 | private const val VSCODE_MIN_CONFIG = 197 | "{\"wurst.javaOpts\": [\"-XX:+UseStringDeduplication\", \"-Xmx1G\"],\n" + 198 | "\t\"files.associations\": {\n" + 199 | " \"$CONFIG_FILE_NAME\": \"yaml\"\n" + 200 | " },\n" + 201 | "\t\"search.useIgnoreFiles\": false }" 202 | } 203 | 204 | -------------------------------------------------------------------------------- /src/main/kotlin/file/CLICommand.kt: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | enum class CLICommand { 4 | HELP, 5 | INSTALL, 6 | REMOVE, 7 | GENERATE, 8 | TEST, 9 | BUILD, 10 | SELF_UPDATE 11 | } 12 | 13 | enum class GlobalOptions(val optionName: String = "", val argCount: Int = 0) { 14 | REQ_CONFIRM("--request-confirmation") { 15 | override fun runOption(setupMain: SetupMain, args: List) { 16 | setupMain.requireConfirmation = true 17 | } 18 | }, 19 | PROJECT_DIR("-projectDir", 1) { 20 | override fun runOption(setupMain: SetupMain, args: List) { 21 | setupMain.setProjectDir(SetupApp.DEFAULT_DIR.resolve(args[0])) 22 | } 23 | }, 24 | NO_PJASS("--noPJass") { 25 | override fun runOption(setupMain: SetupMain, args: List) { 26 | setupMain.noPJass = true 27 | } 28 | }, 29 | MEASURE("--measure") { 30 | override fun runOption(setupMain: SetupMain, args: List) { 31 | setupMain.measure = true 32 | } 33 | }; 34 | 35 | abstract fun runOption(setupMain: SetupMain, args: List) 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/file/DependencyManager.kt: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import config.WurstProjectConfigData 4 | import global.Log 5 | import mu.KotlinLogging 6 | import org.eclipse.jgit.api.CreateBranchCommand 7 | import org.eclipse.jgit.api.Git 8 | import org.eclipse.jgit.internal.storage.file.FileRepository 9 | import java.io.File 10 | import java.io.IOException 11 | import java.nio.charset.Charset 12 | import java.nio.file.Files 13 | import java.nio.file.Path 14 | import java.util.* 15 | 16 | 17 | /** 18 | * Created by Frotty on 17.07.2017. 19 | */ 20 | object DependencyManager { 21 | private val log = KotlinLogging.logger {} 22 | 23 | fun updateDependencies(projectRoot: Path, projectConfig: WurstProjectConfigData) { 24 | val depFolders = ArrayList() 25 | // Iterate through git dependencies 26 | log.info("\uD83D\uDD37 Installing dependencies..") 27 | Log.print("Updating dependencies...\n") 28 | for (dependency in projectConfig.dependencies) { 29 | val (_, dependencyName, branch) = resolveName(dependency) 30 | log.info("\t\uD83D\uDD39 Pulling <$dependencyName:$branch>") 31 | Log.print("Updating dependency - $dependencyName ..") 32 | val depFolder = projectRoot.resolve("_build/dependencies/$dependencyName") 33 | if (Files.exists(depFolder)) { 34 | log.debug("depencency exists locally") 35 | depFolders.add(depFolder.toAbsolutePath().toString()) 36 | // clean 37 | if(!cleanRepo(depFolder, branch)) { 38 | deleteDirectoryStream(depFolder) 39 | cloneRepo(dependency, depFolder) 40 | } else { 41 | // update 42 | updateRepo(depFolder, branch) 43 | } 44 | } else { 45 | // clone 46 | cloneRepo(dependency, depFolder) 47 | depFolders.add(depFolder.toAbsolutePath().toString()) 48 | } 49 | } 50 | if (!depFolders.isEmpty()) { 51 | try { 52 | Files.write(projectRoot.resolve("wurst.dependencies"), depFolders, Charset.defaultCharset()) 53 | } catch (e: IOException) { 54 | e.printStackTrace() 55 | } 56 | } 57 | log.info("✔ Installed dependencies!") 58 | } 59 | 60 | fun resolveName(dependency: String): Triple { 61 | var dependencyName = dependency.substring(dependency.lastIndexOf("/") + 1) 62 | var branch = "master" 63 | var depURI = dependency 64 | 65 | if (dependencyName.contains(":")) { 66 | depURI = depURI.substring(0, depURI.lastIndexOf(":")) 67 | branch = dependencyName.substring(dependencyName.lastIndexOf(":") + 1) 68 | dependencyName = dependencyName.substring(0, dependencyName.lastIndexOf(":")) 69 | } 70 | return Triple(depURI, dependencyName, branch) 71 | } 72 | 73 | fun isUpdateAvailable(projectRoot: Path, projectConfig: WurstProjectConfigData): Boolean { 74 | Log.print("Checking dependencies...\n") 75 | for (dependency in projectConfig.dependencies) { 76 | val dependencyName = dependency.substring(dependency.lastIndexOf("/") + 1) 77 | Log.print("Checking dependency - $dependencyName ..") 78 | val depFolder = projectRoot.resolve("_build/dependencies/" + dependencyName) 79 | if (Files.exists(depFolder)) { 80 | isGitRepoUpToDate(depFolder) 81 | } else { 82 | return true 83 | } 84 | 85 | } 86 | return false 87 | } 88 | 89 | fun cloneRepo(dependency: String, depFolder: Path) { 90 | val (depURI, _, branch) = resolveName(dependency) 91 | try { 92 | Files.createDirectories(depFolder) 93 | } catch (e: IOException) { 94 | Log.print("error when trying to create directory") 95 | throw RuntimeException("Could not create dependency folder", e) 96 | } 97 | try { 98 | Git.cloneRepository().setURI(depURI).setBranch(branch) 99 | .setDirectory(depFolder.toFile()) 100 | .call().use { result -> Log.print("done\n") } 101 | } catch (e: Exception) { 102 | Log.print("error!\n") 103 | e.printStackTrace() 104 | } 105 | } 106 | 107 | private fun updateRepo(depFolder: Path, branch: String) { 108 | try { 109 | FileRepository(depFolder.resolve(".git").toFile()).use { repository -> 110 | try { 111 | Git(repository).use { git -> 112 | val pullResult = git.pull().call() 113 | Log.print("done (success=" + pullResult.isSuccessful + ")\n") 114 | log.debug("Was pull successful?: " + pullResult.isSuccessful) 115 | } 116 | } catch (e: Exception) { 117 | Log.print("error when trying to fetch remote\n") 118 | e.printStackTrace() 119 | } 120 | } 121 | } catch (e: Exception) { 122 | Log.print("error when trying open repository") 123 | e.printStackTrace() 124 | } 125 | } 126 | 127 | @Throws(IOException::class) 128 | private fun deleteDirectoryStream(path: Path) { 129 | Files.walk(path) 130 | .sorted(Comparator.reverseOrder()) 131 | .map { it.toFile() } 132 | .forEach { it.delete() } 133 | } 134 | 135 | 136 | private fun cleanRepo(depFolder: Path, branch: String): Boolean { 137 | try { 138 | FileRepository(depFolder.resolve(".git").toFile()).use { repository -> 139 | try { 140 | Git(repository).use { git -> 141 | git.clean().setCleanDirectories(true).setForce(true).call() 142 | git.checkout().setAllPaths(true).call() 143 | git.reset().call() 144 | log.debug("cleaned repo") 145 | return prepareRepo(git, branch) 146 | } 147 | } catch (e: Exception) { 148 | Log.print("error when trying to clean repository\n") 149 | e.printStackTrace() 150 | } 151 | } 152 | } catch (e: Exception) { 153 | Log.print("error when trying open repository") 154 | e.printStackTrace() 155 | } 156 | return false 157 | } 158 | 159 | private fun prepareRepo(git: Git, branch: String): Boolean { 160 | return try { 161 | git.checkout().setCreateBranch(true).setName(branch) 162 | .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.SET_UPSTREAM) 163 | .setStartPoint("origin/$branch").call() 164 | true 165 | } catch (e: java.lang.Exception) { 166 | try { 167 | git.checkout().setName(branch) 168 | .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.SET_UPSTREAM) 169 | .setStartPoint("origin/$branch").call() 170 | true 171 | } catch (e: java.lang.Exception) { 172 | false 173 | } 174 | } 175 | } 176 | 177 | private fun isGitRepoUpToDate(depFolder: Path): Boolean { 178 | try { 179 | try { 180 | FileRepository(depFolder.toFile()).use { repository -> 181 | try { 182 | Git(repository).use { git -> 183 | git.lsRemote().setHeads(true).call() 184 | val status = git.status().call() 185 | if (status.hasUncommittedChanges()) { 186 | Log.print("You have modified files in your dependencies folder.") 187 | } else if (status.isClean) { 188 | return true 189 | } 190 | } 191 | } catch (e: Exception) { 192 | Log.print("error when trying to fetch remote\n") 193 | e.printStackTrace() 194 | } 195 | } 196 | } catch (e: Exception) { 197 | Log.print("error when trying open repository") 198 | e.printStackTrace() 199 | } 200 | 201 | } catch (ignored: Exception) { 202 | } 203 | return false 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/main/kotlin/file/Download.kt: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import global.Log 4 | 5 | 6 | import mu.KotlinLogging 7 | import ui.MainWindow 8 | import workers.DownloadWithProgressWorker 9 | import java.io.BufferedInputStream 10 | import java.io.BufferedOutputStream 11 | import java.io.FileOutputStream 12 | import java.io.IOException 13 | import java.net.HttpURLConnection 14 | import java.net.URL 15 | import java.nio.file.Files 16 | import java.nio.file.Path 17 | import java.nio.file.Paths 18 | object Download { 19 | private val log = KotlinLogging.logger {} 20 | 21 | private const val baseUrl = "grill.wurstlang.org/hudson/job/Wurst/lastSuccessfulBuild/artifact/downloads/" 22 | private const val bareboneUrl = "github.com/wurstscript/wurst-project-template/archive/master.zip" 23 | private const val compileName = "wurstpack_compiler.zip" 24 | 25 | @Throws(IOException::class) 26 | private fun downloadFile(filePath: String, callback: (Path) -> Unit) { 27 | if (SetupApp.setup.isGUILaunch) { 28 | DownloadWithProgressWorker(filePath, MainWindow.ui.progressBar, callback).execute() 29 | } else { 30 | downloadDirect(filePath, callback) 31 | } 32 | } 33 | 34 | fun getHttpURLConnection(filePath: String): HttpURLConnection { 35 | val url = URL(filePath) 36 | val httpConnection = url.openConnection() as HttpURLConnection 37 | httpConnection.connectTimeout = 14000 38 | httpConnection.readTimeout = 20000 39 | httpConnection.addRequestProperty("User-Agent", "Chrome") 40 | return httpConnection 41 | } 42 | 43 | @Throws(IOException::class) 44 | fun downloadSetup(callback: (Path) -> Unit) { 45 | try { 46 | downloadFile("https://grill.wurstlang.org/hudson/job/WurstSetup/lastSuccessfulBuild/artifact/downloads/WurstSetup.jar", callback) 47 | } catch (e: Exception) { 48 | log.warn( "downloadCompiler Exception caught", e) 49 | Log.println("Https error, falling back to unsafe http.") 50 | downloadFile("http://grill.wurstlang.org/hudson/job/WurstSetup/lastSuccessfulBuild/artifact/downloads/WurstSetup.jar", callback) 51 | } 52 | } 53 | 54 | @Throws(IOException::class) 55 | fun downloadCompiler(callback: (Path) -> Unit) { 56 | try { 57 | downloadFile("https://$baseUrl$compileName", callback) 58 | } catch (e: Exception) { 59 | log.warn( "downloadCompiler Exception caught", e) 60 | Log.println("Https error, falling back to unsafe http.") 61 | downloadFile("http://$baseUrl$compileName", callback) 62 | } 63 | } 64 | 65 | @Throws(IOException::class) 66 | fun downloadBareboneProject(callback: (Path) -> Unit) { 67 | try { 68 | downloadFile("https://$bareboneUrl", callback) 69 | } catch (e: Exception) { 70 | log.warn( "downloadBareboneProject Exception caught", e) 71 | Log.println("Https error, falling back to unsafe http.") 72 | downloadFile("http://$bareboneUrl", callback) 73 | } 74 | } 75 | 76 | private fun downloadDirect(filePath: String, callback: (Path) -> Unit) { 77 | val httpConnection = getHttpURLConnection(filePath) 78 | val completeFileSize = httpConnection.contentLength 79 | val size = completeFileSize / 1024 / 1024 80 | log.info("\t\uD83D\uDCE5 (" + (if (size == 0) "<1" else size) + "MB)") 81 | val input = java.io.BufferedInputStream(httpConnection.inputStream) 82 | var substring = filePath.substring(filePath.lastIndexOf("/") + 1) 83 | if (Files.exists(Paths.get(substring))) { 84 | substring += ".2.jar" 85 | } 86 | 87 | readStream(substring, input) 88 | 89 | input.close() 90 | callback.invoke(Paths.get(substring)) 91 | } 92 | 93 | private fun readStream(substring: String, input: BufferedInputStream) { 94 | FileOutputStream(substring).use { fos -> 95 | BufferedOutputStream(fos, 1024).use { 96 | val data = ByteArray(1024) 97 | var downloadedFileSize: Long = 0 98 | var x = input.read(data, 0, 1024) 99 | do { 100 | downloadedFileSize += x.toLong() 101 | it.write(data, 0, x) 102 | x = input.read(data, 0, 1024) 103 | } while (x >= 0) 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/kotlin/file/ExceptionHandler.kt: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import java.io.PrintWriter 4 | import java.io.StringWriter 5 | import javax.swing.JOptionPane 6 | import javax.swing.JTextArea 7 | import javax.swing.UIManager 8 | 9 | object ExceptionHandler { 10 | 11 | fun setupExceptionHandler() { 12 | Thread.setDefaultUncaughtExceptionHandler { _, exception -> 13 | exception.printStackTrace() 14 | try { 15 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) 16 | } catch (e: Exception) { 17 | e.printStackTrace() 18 | } 19 | 20 | val sw = StringWriter() 21 | val pw = PrintWriter(sw) 22 | exception.printStackTrace(pw) 23 | val jTextField = JTextArea() 24 | jTextField.text = "Please report this crash with the following info:\nVersion: " + CompileTimeInfo.version + "\n" + sw.toString() 25 | jTextField.isEditable = false 26 | JOptionPane.showMessageDialog(null, jTextField, "Sorry, Exception occured :(", JOptionPane.ERROR_MESSAGE) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/kotlin/file/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import mu.KotlinLogging 4 | import java.nio.file.Files 5 | import java.nio.file.Path 6 | import java.nio.file.StandardCopyOption 7 | 8 | val log = KotlinLogging.logger {} 9 | 10 | fun clearFolder(dir: Path) { 11 | log.debug("clearing: $dir") 12 | Files.walk(dir).forEach { 13 | clearPathInternal(it, dir) 14 | } 15 | } 16 | 17 | fun clearFile(it: Path) { 18 | try { 19 | Files.delete(it) 20 | } catch (_e: Exception) { 21 | if (_e.message?.contains("it is being used by another process") == true) { 22 | log.warn("It seems like wurst is still running. some files might not be removed.") 23 | } else { 24 | log.error("Exception: ", _e) 25 | } 26 | } 27 | } 28 | 29 | fun copyFolder(src: Path, dest: Path) { 30 | try { 31 | Files.walk(src) 32 | .forEach { source -> 33 | try { 34 | val target = dest.resolve(src.relativize(source)) 35 | copyPath(source, target) 36 | } catch (e: Exception) { 37 | e.printStackTrace() 38 | } 39 | } 40 | } catch (ex: Exception) { 41 | ex.printStackTrace() 42 | } 43 | } 44 | 45 | private fun copyPath(source: Path?, target: Path?) { 46 | if (Files.isDirectory(source)) { 47 | if (!Files.exists(target)) { 48 | Files.createDirectory(target) 49 | } 50 | } else { 51 | Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING) 52 | } 53 | } 54 | 55 | 56 | private fun clearPathInternal(it: Path, dir: Path) { 57 | if (it != dir) { 58 | if (Files.isDirectory(it)) { 59 | clearFolder(it) 60 | } else { 61 | clearFile(it) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/kotlin/file/SetupApp.kt: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import config.CONFIG_FILE_NAME 4 | import config.WurstProjectConfig 5 | import config.WurstProjectConfigData 6 | import global.InstallationManager 7 | import global.Log 8 | import mu.KotlinLogging 9 | import net.ConnectionManager 10 | import net.NetStatus 11 | import org.eclipse.jgit.api.Git 12 | import ui.UiManager 13 | import ui.UpdateFoundDialog 14 | import java.awt.GraphicsEnvironment 15 | import java.lang.ProcessBuilder.Redirect 16 | import java.nio.file.Files 17 | import java.nio.file.Path 18 | import java.nio.file.Paths 19 | import java.nio.file.StandardCopyOption 20 | import java.util.* 21 | import kotlin.system.exitProcess 22 | 23 | 24 | object SetupApp { 25 | val DEFAULT_DIR: Path = Paths.get(".") 26 | private val log = KotlinLogging.logger {} 27 | lateinit var setup: SetupMain 28 | 29 | fun handleArgs(setup: SetupMain) { 30 | this.setup = setup 31 | updateGrillJar() 32 | if (setup.isGUILaunch) { 33 | log.info("\uD83D\uDDBC No arguments found. Launching Wurst Setup GUI..") 34 | if (GraphicsEnvironment.isHeadless()) { 35 | log.error("\uD83D\uDD25 Error: Can't run GUI in headless environment!") 36 | exitProcess(1) 37 | } 38 | UiManager.initUI() 39 | } else { 40 | log.info("\uD83D\uDD25 Grill warming up..") 41 | handleCMD() 42 | } 43 | } 44 | 45 | private fun handleCMD() { 46 | ConnectionManager.checkConnectivity("http://google.com") 47 | ConnectionManager.checkWurstBuild() 48 | InstallationManager.verifyInstallation() 49 | if (ConnectionManager.netStatus == NetStatus.ONLINE) { 50 | val latestSetupBuild = ConnectionManager.getLatestSetupBuild() 51 | val jenkinsBuildVer = InstallationManager.getJenkinsBuildVer(CompileTimeInfo.version) 52 | log.debug("current setup ver: $jenkinsBuildVer latest Setup: $latestSetupBuild") 53 | if (latestSetupBuild > jenkinsBuildVer) { 54 | log.info("\uD83D\uDD04 Grill update available: $jenkinsBuildVer -> $latestSetupBuild. Run `grill install grill` to update.") 55 | } 56 | } 57 | log.info("\uD83D\uDD25 Ready. Version: <{}>", CompileTimeInfo.version) 58 | handleRunArgs() 59 | } 60 | 61 | private fun handleRunArgs() { 62 | log.debug("handle runargs") 63 | val configFile = setup.projectRoot.resolve(CONFIG_FILE_NAME) 64 | var configData: WurstProjectConfigData? = null 65 | if (Files.exists(configFile)) { 66 | configData = WurstProjectConfig.loadProject(configFile)!! 67 | } else { 68 | log.warn("⚠️ No wurst.build configuration file at current location.") 69 | } 70 | 71 | when { 72 | setup.command == CLICommand.HELP -> { 73 | log.info("Use one of the following commands: ${CLICommand.values().joinToString(", ")}") 74 | } 75 | setup.command == CLICommand.INSTALL -> { 76 | if (setup.commandArg.isBlank()) { 77 | if (configData != null) { 78 | handleUpdateProject(configData) 79 | } 80 | } else if (setup.commandArg.toLowerCase() == "wurstscript") { 81 | handleInstallWurst() 82 | } else if (setup.commandArg.toLowerCase() == "grill") { 83 | handleUpdateGrill() 84 | } else { 85 | if (configData != null) { 86 | handleInstallDep(configData) 87 | WurstProjectConfig.saveProjectConfig(setup.projectRoot, configData) 88 | handleUpdateProject(configData) 89 | } 90 | } 91 | } 92 | setup.command == CLICommand.REMOVE -> { 93 | if (setup.commandArg.toLowerCase() == "wurstscript") { 94 | handleRemoveWurst() 95 | } else { 96 | if (configData != null) { 97 | handleRemoveDep(configData) 98 | WurstProjectConfig.saveProjectConfig(setup.projectRoot, configData) 99 | } 100 | } 101 | } 102 | setup.command == CLICommand.GENERATE -> { 103 | log.info("✈ Generating project..") 104 | if (configData == null) { 105 | WurstProjectConfig.handleCreate(DEFAULT_DIR.resolve(setup.commandArg), null, 106 | WurstProjectConfigData(setup.commandArg, ArrayList(mutableListOf("https://github.com/wurstscript/wurstStdlib2")))) 107 | } 108 | } 109 | setup.command == CLICommand.TEST -> { 110 | log.info("⚗️ Testing project..") 111 | if (InstallationManager.status != InstallationManager.InstallationStatus.NOT_INSTALLED && configData != null) { 112 | testProject(configData) 113 | } 114 | } 115 | setup.command == CLICommand.BUILD -> { 116 | log.info("\uD83D\uDD28 Building project..") 117 | if (setup.commandArg.isBlank()) { 118 | log.error("\t❌ No input map specified.") 119 | } else if (!Files.exists(setup.projectRoot.resolve(setup.commandArg))) { 120 | log.error("\t❌ Input map cannot be found at project root.") 121 | } else { 122 | if (InstallationManager.status != InstallationManager.InstallationStatus.NOT_INSTALLED && configData != null) { 123 | buildProject(configData) 124 | } 125 | } 126 | } 127 | setup.command == CLICommand.SELF_UPDATE -> { 128 | log.info("\uD83D\uDD04 Updating..") 129 | try { 130 | log.info("✔ Updated succeeded.") 131 | if (setup.isGUILaunch) { 132 | Runtime.getRuntime().exec(arrayOf("java", "-jar", InstallationManager.installDir.toString())) 133 | } 134 | exitProcess(0) 135 | } catch(e: Exception) { 136 | log.error("Grill update failed. Original files might still be in use.") 137 | } 138 | } 139 | } 140 | 141 | } 142 | 143 | private fun handleUpdateGrill() { 144 | Download.downloadSetup { 145 | log.info("\uD83D\uDCC1 Copying files..") 146 | Runtime.getRuntime().exec(arrayOf("java", "-jar", it.toAbsolutePath().toString(), "self_update")) 147 | exitProcess(0) 148 | } 149 | } 150 | 151 | private fun buildProject(configData: WurstProjectConfigData) { 152 | val args = commonArgs(configData) 153 | 154 | args.add("-build") 155 | 156 | if (setup.measure) { 157 | args.add("-measure") 158 | } 159 | 160 | args.add("-workspaceroot") 161 | args.add(setup.projectRoot.toAbsolutePath().toString()) 162 | 163 | args.add("-inputmap") 164 | args.add(setup.projectRoot.resolve(setup.commandArg).toAbsolutePath().toString()) 165 | 166 | val result = startWurstProcess(args) 167 | when (result) { 168 | 0 -> log.info("\uD83D\uDDFA️ Map has been built!") 169 | else -> { 170 | log.info("❌ There was an issue with the wurst build process.") 171 | exitProcess(1) 172 | } 173 | } 174 | } 175 | 176 | private fun testProject(configData: WurstProjectConfigData) { 177 | val args = commonArgs(configData) 178 | 179 | args.add("-runtests") 180 | 181 | val result = startWurstProcess(args) 182 | when (result) { 183 | 0 -> log.info("✔ All tests succeeded.") 184 | else -> { 185 | log.info("❌ Tests did not execute successfully.") 186 | exitProcess(1) 187 | } 188 | } 189 | } 190 | 191 | private fun startWurstProcess(args: ArrayList): Int { 192 | val pb = ProcessBuilder(args) 193 | pb.redirectOutput(Redirect.INHERIT) 194 | pb.redirectError(Redirect.INHERIT) 195 | val p = pb.start() 196 | return p.waitFor() 197 | } 198 | 199 | private fun commonArgs(configData: WurstProjectConfigData): ArrayList { 200 | val args = arrayListOf("java", "-jar", 201 | InstallationManager.installDir.resolve("wurstscript.jar").toAbsolutePath().toString()) 202 | 203 | val buildFolder = setup.projectRoot.resolve("_build") 204 | val jassdoc = buildFolder.resolve("dependencies").resolve("jassdoc") 205 | if (Files.exists(jassdoc)) { 206 | for (f in jassdoc.toFile().listFiles()!!) { 207 | if (f.name.endsWith(".j") && !f.name.startsWith("builtin-types")) { 208 | args.add(f.absolutePath.toString()) 209 | } 210 | } 211 | } else { 212 | val common = if (Files.exists(buildFolder.resolve("common.j"))) { 213 | buildFolder.resolve("common.j") 214 | } else { 215 | InstallationManager.installDir.resolve("common.j") 216 | } 217 | val blizzard = if (Files.exists(buildFolder.resolve("blizzard.j"))) { 218 | buildFolder.resolve("blizzard.j") 219 | } else { 220 | InstallationManager.installDir.resolve("blizzard.j") 221 | } 222 | args.add(common.toAbsolutePath().toString()) 223 | args.add(blizzard.toAbsolutePath().toString()) 224 | } 225 | 226 | args.add(setup.projectRoot.resolve("wurst").toAbsolutePath().toString()) 227 | args.add("-runcompiletimefunctions") 228 | if (setup.noPJass) { 229 | args.add("-noPJass") 230 | } 231 | 232 | configData.dependencies.stream().forEach { 233 | args.add("-lib") 234 | val (_, dependencyName, _) = DependencyManager.resolveName(it) 235 | args.add(buildFolder.resolve("dependencies").resolve(dependencyName).toAbsolutePath().toString()) 236 | } 237 | return args 238 | } 239 | 240 | private fun handleRemoveDep(configData: WurstProjectConfigData) { 241 | log.error("removing ${setup.commandArg}") 242 | if (configData.dependencies.contains(setup.commandArg)) { 243 | configData.dependencies.remove(setup.commandArg) 244 | } else { 245 | log.error("dependency does not exist in project") 246 | } 247 | } 248 | 249 | private fun handleRemoveWurst() { 250 | if (!setup.requireConfirmation) { 251 | InstallationManager.handleRemove() 252 | } 253 | } 254 | 255 | private fun handleUpdateProject(configData: WurstProjectConfigData) { 256 | WurstProjectConfig.handleUpdate(setup.projectRoot, null, configData) 257 | } 258 | 259 | val REPO_REGEX = Regex("((git@|http(s)?://)([\\w.@]+)([/:]))([\\w,\\-,_]+)/([\\w,\\-,_]+)(.git)?((/)?)") 260 | 261 | private fun handleInstallDep(configData: WurstProjectConfigData) { 262 | val resolvedName = DependencyManager.resolveName(setup.commandArg) 263 | if (!REPO_REGEX.matches(resolvedName.first)) { 264 | log.info("<${setup.commandArg}> does not appear to be a valid git repo link (e.g. https://github.com/user/repo)") 265 | exitProcess(1) 266 | } 267 | log.info("\uD83D\uDD39 Installing ${resolvedName.second}") 268 | if (configData.dependencies.contains(setup.commandArg)) { 269 | log.info("Dependency is already installed.") 270 | return 271 | } 272 | try { 273 | val result = Git.lsRemoteRepository() 274 | .setRemote(resolvedName.first) 275 | .call() 276 | if (!result.isEmpty()) { 277 | Log.print("valid!\n") 278 | configData.dependencies.add(setup.commandArg) 279 | } else { 280 | log.error("Entered invalid git repo.") 281 | } 282 | } catch (e: Exception) { 283 | log.error("Entered invalid git repo.") 284 | e.printStackTrace() 285 | } 286 | } 287 | 288 | private fun handleInstallWurst() { 289 | log.info("\uD83C\uDF2D Installing WurstScript..") 290 | if (InstallationManager.status != InstallationManager.InstallationStatus.INSTALLED_UPTODATE) { 291 | log.info("\tUpdate available!") 292 | if (setup.requireConfirmation) { 293 | if (setup.isGUILaunch) { 294 | UpdateFoundDialog("A Wurst compiler update has been found!") 295 | } else { 296 | log.info("Do you want to update your wurst installation? (y/n)") 297 | val sc = Scanner(System.`in`) 298 | val line = sc.nextLine() 299 | if (line == "y") { 300 | InstallationManager.handleUpdate() 301 | } 302 | } 303 | } else { 304 | InstallationManager.handleUpdate() 305 | } 306 | } else { 307 | log.info("Already up to date.") 308 | } 309 | } 310 | 311 | private fun updateGrillJar() { 312 | val url = InstallationManager::class.java.protectionDomain.codeSource.location 313 | val ownFile = Paths.get(url.toURI()) 314 | if (ownFile.endsWith(".2.jar")) { 315 | log.debug("copy jar from own") 316 | Files.copy(ownFile, ownFile.resolveSibling("WurstSetup.jar"), StandardCopyOption.REPLACE_EXISTING) 317 | } 318 | log.debug("path: $url") 319 | log.debug("file: " + ownFile.toAbsolutePath()) 320 | if (Files.exists(ownFile) && (ownFile.parent == null || ownFile.parent?.fileName?.toString() != ".wurst")) { 321 | log.debug("copy jar") 322 | Files.copy(ownFile, Paths.get(InstallationManager.installDir.toString(), "WurstSetup.jar"), StandardCopyOption.REPLACE_EXISTING) 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/main/kotlin/file/SetupMain.kt: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import mu.KotlinLogging 4 | import java.nio.file.Files 5 | import java.nio.file.Path 6 | import kotlin.jvm.Throws 7 | import kotlin.system.exitProcess 8 | 9 | 10 | class SetupMain { 11 | private val log = KotlinLogging.logger {} 12 | var isGUILaunch = false 13 | var command = CLICommand.HELP 14 | 15 | var commandArg = "" 16 | 17 | var measure = false 18 | 19 | var projectRoot: Path = SetupApp.DEFAULT_DIR 20 | 21 | var requireConfirmation = false 22 | 23 | var noPJass = false 24 | 25 | fun setProjectDir(dir: Path) { 26 | Files.createDirectories(dir) 27 | if (Files.exists(dir)) { 28 | projectRoot = dir 29 | } 30 | } 31 | 32 | fun doMain(args: Array) { 33 | ExceptionHandler.setupExceptionHandler() 34 | val argsList = args.asList() 35 | if (argsList.isEmpty()) { 36 | isGUILaunch = true 37 | } else { 38 | parseCLIArgs(argsList) 39 | } 40 | SetupApp.handleArgs(this) 41 | } 42 | 43 | @Throws(IllegalArgumentException::class) 44 | private fun parseCLIArgs(argsList: List) { 45 | val first = argsList[0] 46 | try { 47 | command = CLICommand.valueOf(first.toUpperCase()) 48 | log.debug("found $command") 49 | if (argsList.size > 1) { 50 | if (!argsList[1].startsWith("-")) { 51 | commandArg = argsList[1] 52 | parseGlobalArgs(argsList, 2) 53 | } else { 54 | parseGlobalArgs(argsList, 1) 55 | } 56 | 57 | } 58 | } catch(e: IllegalArgumentException) { 59 | log.error("\uD83D\uDD25 Invalid grill command <$first> ! Available commands: [generate|install|remove|test|build] ") 60 | exitProcess(1) 61 | } 62 | } 63 | 64 | private fun parseGlobalArgs(argsList: List, start: Int) { 65 | var skip = 0 66 | for (i in start until argsList.size) { 67 | if (skip > 0) { 68 | skip -= 1 69 | continue 70 | } else { 71 | GlobalOptions.values().forEach { 72 | if(it.optionName == argsList[start]) { 73 | it.runOption(this, argsList.subList(i, i + it.argCount)) 74 | } 75 | } 76 | } 77 | } 78 | 79 | } 80 | 81 | companion object { 82 | @JvmStatic 83 | fun main(args: Array) { 84 | SetupMain().doMain(args) 85 | } 86 | } 87 | } 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/main/kotlin/file/YamlHelper.kt: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude 4 | import com.fasterxml.jackson.core.JsonParser 5 | import com.fasterxml.jackson.databind.DeserializationFeature 6 | import com.fasterxml.jackson.databind.ObjectMapper 7 | import com.fasterxml.jackson.databind.SerializationFeature 8 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory 9 | import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator 10 | import com.fasterxml.jackson.module.kotlin.KotlinModule 11 | import config.WurstProjectConfigData 12 | import mu.KotlinLogging 13 | import java.nio.file.Files 14 | import java.nio.file.Path 15 | 16 | object YamlHelper { 17 | private var mapper: ObjectMapper 18 | private val log = KotlinLogging.logger {} 19 | 20 | init { 21 | val yamlFactory = YAMLFactory() 22 | yamlFactory.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) 23 | yamlFactory.enable(JsonParser.Feature.ALLOW_MISSING_VALUES) 24 | 25 | mapper = ObjectMapper(yamlFactory) 26 | mapper.registerModule(KotlinModule.Builder().build()) 27 | mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) 28 | mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) 29 | mapper.enable(SerializationFeature.INDENT_OUTPUT) 30 | } 31 | 32 | 33 | fun loadProjectConfig(path: Path): WurstProjectConfigData { 34 | Files.newBufferedReader(path).use { 35 | try { 36 | return mapper.readValue(it, WurstProjectConfigData::class.java) 37 | } catch (e: Exception) { 38 | log.error("The project's wurst.build file could not be read. Input malformed or corrupt.", e) 39 | throw YamlException("The project's wurst.build file could not be read. Input malformed or corrupt.") 40 | } 41 | } 42 | } 43 | 44 | fun dumpProjectConfig(configData: WurstProjectConfigData): String { 45 | return mapper.writeValueAsString(configData) 46 | } 47 | 48 | class YamlException(msg: String): RuntimeException(msg) 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/main/kotlin/file/ZipArchiveExtractor.kt: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | 4 | import global.Log 5 | import mu.KotlinLogging 6 | import java.io.* 7 | import java.nio.file.Files 8 | import java.nio.file.Path 9 | import java.util.zip.ZipFile 10 | 11 | object ZipArchiveExtractor { 12 | 13 | private val log = KotlinLogging.logger {} 14 | 15 | /** 16 | * 17 | * @param archive 18 | * @param destDir 19 | * @return true if all files were successfully extracted 20 | * @throws Exception 21 | */ 22 | @Throws(Exception::class) 23 | fun extractArchive(archive: Path, destDir: Path): Boolean { 24 | if (!Files.exists(destDir)) { 25 | Files.createDirectories(destDir) 26 | } 27 | 28 | val zipFile = ZipFile(archive.toFile()) 29 | val entries = zipFile.entries() 30 | 31 | val buffer = ByteArray(16384) 32 | while (entries.hasMoreElements()) { 33 | val entry = entries.nextElement() 34 | 35 | val entryFileName = entry.name 36 | 37 | val dir = buildDirectoryHierarchyFor(entryFileName, destDir.toFile()) 38 | if (!dir.exists()) { 39 | dir.mkdirs() 40 | } 41 | 42 | if (!entry.isDirectory) { 43 | val targetFile = File(destDir.toFile(), entryFileName) 44 | if (targetFile.exists() && !targetFile.canWrite()) { 45 | zipFile.close() 46 | return false 47 | } 48 | 49 | try { 50 | val bos = BufferedOutputStream(FileOutputStream(targetFile)) 51 | 52 | val bis = BufferedInputStream(zipFile.getInputStream(entry)) 53 | 54 | var len = bis.read(buffer) 55 | while (len > 0) { 56 | bos.write(buffer, 0, len) 57 | len = bis.read(buffer) 58 | } 59 | 60 | bos.flush() 61 | bos.close() 62 | bis.close() 63 | } catch (e: FileNotFoundException) { 64 | log.warn("Warning: <$entryFileName> could not be extracted (might be in use)") 65 | Log.print("\nWarning: <$entryFileName> could not be extracted (might be in use)!\n") 66 | } 67 | 68 | } 69 | } 70 | zipFile.close() 71 | return true 72 | } 73 | 74 | private fun buildDirectoryHierarchyFor(entryName: String, destDir: File): File { 75 | val lastIndex = entryName.lastIndexOf('/') 76 | val internalPathToEntry = entryName.substring(0, lastIndex + 1) 77 | return File(destDir, internalPathToEntry) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/kotlin/global/CLIParser.kt: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import file.SetupApp 4 | import mu.KotlinLogging 5 | import ui.ErrorDialog 6 | import java.util.concurrent.TimeUnit 7 | 8 | object CLIParser { 9 | private val log = KotlinLogging.logger {} 10 | 11 | /** Gets the version of the wurstscript.jar via cli */ 12 | fun getVersionFomJar() { 13 | log.debug("running wurst to extract the version") 14 | val proc = Runtime.getRuntime().exec(arrayOf("java", "-jar", InstallationManager.compilerJar.toAbsolutePath().toString(), "--version")) 15 | proc.waitFor(100, TimeUnit.MILLISECONDS) 16 | val input = proc.inputStream.bufferedReader().use { it.readText() }.trim() 17 | val err = proc.errorStream.bufferedReader().use { it.readText() } 18 | 19 | if (err.isNotEmpty()) { 20 | log.error(err) 21 | } 22 | when { 23 | // If the err output contains this exception, the .jar is currently running 24 | err.contains("AccessDeniedException", true) -> showWurstInUse() 25 | // Other exceptions or failures require update to fix 26 | err.contains("Exception") || err.contains("Failed") -> { 27 | log.error("Classifying installation as outdated due to $err") 28 | InstallationManager.status = InstallationManager.InstallationStatus.INSTALLED_OUTDATED 29 | } 30 | else -> { 31 | parseCMDLine(input) 32 | } 33 | } 34 | } 35 | 36 | private fun parseCMDLine(input: String) { 37 | log.debug("parsing CMD output: $input") 38 | val lines = input.split(System.getProperty("line.separator")) 39 | lines.forEach { line -> 40 | if (InstallationManager.isJenkinsBuilt(line)) { 41 | log.debug("Found jenkins build string $line") 42 | InstallationManager.currentCompilerVersion = InstallationManager.getJenkinsBuildVer(line) 43 | InstallationManager.status = InstallationManager.InstallationStatus.INSTALLED_OUTDATED 44 | } 45 | } 46 | if (InstallationManager.status != InstallationManager.InstallationStatus.INSTALLED_OUTDATED) { 47 | log.debug("Failed to extract jenkins version from $input") 48 | throw Error("Installation failed!") 49 | } 50 | } 51 | 52 | fun showWurstInUse() { 53 | if (SetupApp.setup.isGUILaunch) { 54 | ErrorDialog("The Wurst compiler is currently in use.\n" + 55 | "Please close all running instances and vscode, then retry.", true) 56 | } 57 | log.error("The Wurst compiler is currently in use.\n" + 58 | "Please close all running instances and vscode, then retry.") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/kotlin/global/InstallationManager.kt: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import file.Download 4 | import file.SetupApp 5 | import file.ZipArchiveExtractor 6 | import file.clearFolder 7 | import mu.KotlinLogging 8 | import net.ConnectionManager 9 | import net.NetStatus 10 | import ui.ErrorDialog 11 | import ui.MainWindow 12 | import ui.UiManager 13 | import workers.ExtractWorker 14 | import java.nio.file.Files 15 | import java.nio.file.Path 16 | import java.nio.file.Paths 17 | import java.util.regex.Pattern 18 | import javax.swing.SwingUtilities 19 | 20 | 21 | /** 22 | * Manages the global Wurst installation located inside the ~/.wurst directory 23 | */ 24 | object InstallationManager { 25 | private val log = KotlinLogging.logger {} 26 | private const val FOLDER_PATH = ".wurst" 27 | private const val COMPILER_FILE_NAME = "wurstscript.jar" 28 | 29 | val installDir: Path = Paths.get(System.getProperty("user.home"), FOLDER_PATH) 30 | val compilerJar: Path = installDir.resolve(COMPILER_FILE_NAME) 31 | 32 | var wurstConfig: WurstConfigData? = null 33 | 34 | var status = InstallationStatus.NOT_INSTALLED 35 | var currentCompilerVersion = -1 36 | var latestCompilerVersion = 0 37 | 38 | fun verifyInstallation(): InstallationStatus { 39 | log.debug("verify Install") 40 | status = InstallationStatus.NOT_INSTALLED 41 | currentCompilerVersion = -1 42 | latestCompilerVersion = 0 43 | if (Files.exists(installDir) && Files.exists(compilerJar)) { 44 | log.debug("Found installation") 45 | status = InstallationStatus.INSTALLED_UNKNOWN 46 | try { 47 | if (!Files.isWritable(compilerJar)) { 48 | CLIParser.showWurstInUse() 49 | } else { 50 | CLIParser.getVersionFomJar() 51 | } 52 | } catch (_: Error) { 53 | log.warn("Custom WurstScript installation detected.") 54 | } 55 | } else { 56 | log.info("WurstScript is not currently installed.") 57 | } 58 | if (ConnectionManager.netStatus == NetStatus.ONLINE) { 59 | log.debug("Client online, check for update") 60 | latestCompilerVersion = ConnectionManager.getLatestCompilerBuild() 61 | log.debug("latest compiler: $latestCompilerVersion") 62 | if (currentCompilerVersion >= latestCompilerVersion) { 63 | status = InstallationStatus.INSTALLED_UPTODATE 64 | } 65 | } else { 66 | log.debug("Client offline, check for update") 67 | } 68 | return status 69 | } 70 | 71 | 72 | fun handleUpdate() { 73 | val isFreshInstall = status == InstallationStatus.NOT_INSTALLED 74 | try { 75 | log.debug(if (isFreshInstall) "isInstall" else "isUpdate") 76 | Log.print(if (isFreshInstall) "Installing WurstScript..\n" else "Updating WursScript..\n") 77 | Log.print("Downloading compiler..") 78 | log.info("⏬ Downloading WurstScript..") 79 | 80 | downloadCompiler(isFreshInstall) 81 | } catch (e: Exception) { 82 | log.error("Exception: ", e) 83 | Log.print("\n===ERROR COMPILER UPDATE===\n" + e.message + "\nPlease report here: github.com/wurstscript/WurstScript/issues\n") 84 | } 85 | 86 | } 87 | 88 | private fun downloadCompiler(isFreshInstall: Boolean) { 89 | Download.downloadCompiler { 90 | Log.print(" done.\n") 91 | 92 | if (SetupApp.setup.isGUILaunch) { 93 | startExtractWorker(it, isFreshInstall) 94 | } else { 95 | log.info("\t\uD83D\uDCE6 Extracting..") 96 | ZipArchiveExtractor.extractArchive(it, installDir) 97 | Files.delete(it) 98 | setGrillExectuable() 99 | log.info("✔ Installed WurstScript") 100 | } 101 | 102 | } 103 | } 104 | 105 | private fun setGrillExectuable() { 106 | try { 107 | installDir.resolve("grill").toFile().setExecutable(true) 108 | } catch (_: Exception) { 109 | } 110 | } 111 | 112 | private fun startExtractWorker(it: Path, isFreshInstall: Boolean) { 113 | ExtractWorker(it, if (SetupApp.setup.isGUILaunch) MainWindow.ui.progressBar else null) { 114 | if (it) { 115 | checkExtraction(isFreshInstall) 116 | } else { 117 | Log.print("error\n") 118 | log.error("error") 119 | ErrorDialog("Could not extract patch files.\nWurst might still be in use.\nMake sure to close VSCode before updating.", false) 120 | } 121 | UiManager.refreshComponents() 122 | }.execute() 123 | } 124 | 125 | private fun checkExtraction(isFreshInstall: Boolean) { 126 | Log.print("done\n") 127 | if (status == InstallationStatus.NOT_INSTALLED) { 128 | wurstConfig = WurstConfigData() 129 | } 130 | if (!Files.exists(compilerJar)) { 131 | Log.print("ERROR") 132 | } else { 133 | Log.print(if (isFreshInstall) "Installation complete\n" else "Update complete\n") 134 | log.debug("Installed WurstScript") 135 | if (SetupApp.setup.isGUILaunch) { 136 | SwingUtilities.invokeLater { MainWindow.ui.progressBar.value = 0 } 137 | } 138 | verifyInstallation() 139 | } 140 | } 141 | 142 | private val jenkinsVerPattern = Pattern.compile("""(?:\d\.){3}\d(?:-\w+)+-(\d+)""") 143 | 144 | fun isJenkinsBuilt(version: String): Boolean { 145 | return jenkinsVerPattern.matcher(version).matches() 146 | } 147 | 148 | fun getJenkinsBuildVer(version: String): Int { 149 | val matcher = jenkinsVerPattern.matcher(version) 150 | if (matcher.matches()) { 151 | return matcher.group(1).toInt() 152 | } 153 | return 0 154 | } 155 | 156 | fun handleRemove() { 157 | clearFolder(installDir) 158 | verifyInstallation() 159 | log.info("WurstScript has been removed.") 160 | } 161 | 162 | 163 | fun getCompilerPath(): String { 164 | return compilerJar.toAbsolutePath().toString() 165 | } 166 | 167 | enum class InstallationStatus { 168 | NOT_INSTALLED, 169 | INSTALLED_UNKNOWN, 170 | INSTALLED_OUTDATED, 171 | INSTALLED_UPTODATE 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/main/kotlin/global/Log.kt: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import file.SetupApp 4 | import ui.MainWindow 5 | import javax.swing.SwingUtilities 6 | 7 | object Log { 8 | fun print(text: String) { 9 | try { 10 | if (SetupApp.setup.isGUILaunch) { 11 | if (SwingUtilities.isEventDispatchThread()) { 12 | append(text) 13 | } else { 14 | SwingUtilities.invokeAndWait { 15 | append(text) 16 | } 17 | } 18 | } 19 | } catch (_e: UninitializedPropertyAccessException) { 20 | } 21 | } 22 | 23 | private fun append(text: String) { 24 | MainWindow.ui.jTextArea.append(text) 25 | MainWindow.ui.jTextArea.caretPosition = MainWindow.ui.jTextArea.text?.length?.minus(1) ?: 0 26 | } 27 | 28 | fun println(text: String) { 29 | print(text + "\n") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/kotlin/global/WurstConfigData.kt: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | /** 4 | * Created by Frotty on 13.07.2017. 5 | */ 6 | data class WurstConfigData(val lastUpdate: Long = 0) 7 | -------------------------------------------------------------------------------- /src/main/kotlin/net/ConnectionManager.kt: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import mu.KotlinLogging 4 | import us.monoid.json.JSONArray 5 | import us.monoid.json.JSONObject 6 | import us.monoid.web.Resty 7 | import java.io.IOException 8 | 9 | enum class NetStatus { 10 | CLIENT_OFFLINE, 11 | SERVER_OFFLINE, 12 | SERVER_CONTACT, 13 | ONLINE 14 | } 15 | 16 | object ConnectionManager { 17 | private const val WURST_SETUP_URL = "peeeq.de/hudson/job/WurstSetup/lastSuccessfulBuild/api/json" 18 | private const val WURST_COMPILER_URL = "peeeq.de/hudson/job/Wurst/lastSuccessfulBuild/api/json" 19 | private const val MASTER_BRANCH = "refs/remotes/origin/master" 20 | 21 | private val log = KotlinLogging.logger {} 22 | private val resty = Resty() 23 | var netStatus = NetStatus.CLIENT_OFFLINE 24 | 25 | private fun findJsonTag(url: String, path: String, name: String): JSONObject { 26 | val actions = JSONArray(resty.json(url).get(Resty.path(path)).toString()) 27 | 28 | return (0 until actions.length()) 29 | .map { JSONObject(actions[it].toString()) } 30 | .firstOrNull { it.has(name) } 31 | ?.getJSONObject(name) 32 | ?: JSONObject() 33 | } 34 | 35 | fun checkConnectivity(url: String): NetStatus { 36 | // If google can be reached, the client is not offline 37 | netStatus = NetStatus.SERVER_CONTACT 38 | try { 39 | val json = resty.json(url) 40 | if (netStatus == NetStatus.SERVER_CONTACT && (json == null || json.toString().isBlank())) { 41 | netStatus = NetStatus.CLIENT_OFFLINE 42 | return netStatus 43 | } 44 | } catch (e: IOException) { 45 | log.debug("couldn't contact: " + e.localizedMessage) 46 | } 47 | 48 | return netStatus 49 | } 50 | 51 | private fun contactWurstServer(url: String) { 52 | netStatus = try { 53 | val wurstResponse = resty.json(url) 54 | if (wurstResponse == null || wurstResponse.toString().isBlank()) { 55 | NetStatus.SERVER_OFFLINE 56 | } else { 57 | NetStatus.ONLINE 58 | } 59 | } catch (e: IOException) { 60 | log.debug("couldn't contact wurst jenkins: " + e.localizedMessage) 61 | NetStatus.SERVER_OFFLINE 62 | } 63 | } 64 | 65 | private fun getBuildNumber(url: String, branch: String): Int { 66 | if (netStatus != NetStatus.ONLINE) return 0 67 | val response = findJsonTag(url, "actions", "buildsByBranchName") 68 | if (response.has(branch)) { 69 | val innerObject = JSONObject(response.get(branch).toString()) 70 | return innerObject.get("buildNumber").toString().toInt() 71 | } 72 | return -1 73 | } 74 | 75 | fun getLatestSetupBuild(): Int { 76 | log.debug("getting latest setup build") 77 | return try { 78 | getBuildNumber("https://" + WURST_SETUP_URL, MASTER_BRANCH) 79 | } catch (e: IOException) { 80 | getBuildNumber("http://" + WURST_SETUP_URL, MASTER_BRANCH) 81 | } 82 | } 83 | 84 | fun getLatestCompilerBuild(): Int { 85 | log.debug("getting latest compiler build") 86 | return try { 87 | getBuildNumber("https://" + WURST_COMPILER_URL, MASTER_BRANCH) 88 | } catch (e: IOException) { 89 | getBuildNumber("http://" + WURST_COMPILER_URL, MASTER_BRANCH) 90 | } 91 | } 92 | 93 | fun checkWurstBuild(): NetStatus { 94 | log.debug("checking wurst build") 95 | contactWurstServer("https://" + WURST_COMPILER_URL) 96 | if (netStatus == NetStatus.SERVER_OFFLINE) { 97 | contactWurstServer("http://" + WURST_COMPILER_URL) 98 | } 99 | return netStatus 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/kotlin/ui/AddRepoDialog.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import tablelayout.Table 4 | import workers.DependencyVerifierWorker 5 | import java.awt.Color 6 | import java.awt.Component 7 | import java.awt.GridBagLayout 8 | import java.io.IOException 9 | import javax.imageio.ImageIO 10 | import javax.swing.JDialog 11 | import javax.swing.JLabel 12 | import javax.swing.JPanel 13 | import javax.swing.JTextField 14 | 15 | class AddRepoDialog : JDialog() { 16 | 17 | private val contentPane: JPanel = JPanel(GridBagLayout()) 18 | private val contentTable = Table() 19 | 20 | private var addButton: SetupButton = SetupButton("Add") 21 | private var cancelButton: SetupButton = SetupButton("Cancel") 22 | 23 | init { 24 | title = "Add git dependency" 25 | contentTable.setSize(340, 120) 26 | setSize(340, 120) 27 | 28 | setContentPane(contentPane) 29 | contentPane.add(contentTable) 30 | modalityType = ModalityType.APPLICATION_MODAL 31 | try { 32 | setIconImage(ImageIO.read(javaClass.classLoader.getResource("icon.png"))) 33 | } catch (e: IOException) { 34 | e.printStackTrace() 35 | } 36 | 37 | uiLayout("Enter a custom git repository link") 38 | uiStyle() 39 | getRootPane().defaultButton = addButton 40 | 41 | cancelButton.addActionListener { 42 | isVisible = false 43 | } 44 | 45 | addButton.addActionListener { 46 | DependencyVerifierWorker(jTextField.text).execute() 47 | isVisible = false 48 | } 49 | 50 | setLocationRelativeTo(null) 51 | isAlwaysOnTop = true 52 | 53 | isVisible = true 54 | } 55 | 56 | private lateinit var jTextField: JTextField 57 | 58 | private fun uiLayout(message: String) { 59 | val welcomeLabel = JLabel("
$message
") 60 | welcomeLabel.alignmentX = Component.CENTER_ALIGNMENT 61 | welcomeLabel.foreground = Color.WHITE 62 | contentTable.top() 63 | contentTable.addCell(welcomeLabel).width(300f).top().pad(-2f, 5f, 5f, 5f) 64 | contentTable.row() 65 | jTextField = JTextField() 66 | contentTable.addCell(jTextField).width(300f).top().pad(-2f, 5f, 5f, 5f) 67 | contentTable.row() 68 | 69 | val buttonTable = Table() 70 | buttonTable.addCell(addButton).pad(0f, 6f, 0f, 6f) 71 | buttonTable.addCell(cancelButton).pad(0f, 6f, 0f, 6f) 72 | 73 | contentTable.addCell(buttonTable).growX().padTop(6f) 74 | } 75 | 76 | private fun uiStyle() { 77 | contentPane.background = Color(36, 36, 36) 78 | 79 | UiStyle.setStyle(contentTable) 80 | 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /src/main/kotlin/ui/MainWindow.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import config.CONFIG_FILE_NAME 4 | import config.WurstProjectBuildMapData 5 | import config.WurstProjectConfig 6 | import config.WurstProjectConfigData 7 | import de.ralleytn.simple.registry.Registry 8 | import file.CompileTimeInfo 9 | import file.SetupApp 10 | import global.InstallationManager 11 | import global.Log 12 | import mu.KotlinLogging 13 | import net.ConnectionManager 14 | import net.NetStatus 15 | import tablelayout.Table 16 | import workers.* 17 | import java.awt.* 18 | import java.awt.GridBagConstraints.NORTHWEST 19 | import java.awt.GridBagConstraints.VERTICAL 20 | import java.awt.event.MouseAdapter 21 | import java.awt.event.MouseEvent 22 | import java.awt.event.MouseMotionAdapter 23 | import java.io.File 24 | import java.io.IOException 25 | import java.nio.file.Files 26 | import java.nio.file.Paths 27 | import java.util.* 28 | import java.util.regex.Pattern 29 | import java.util.stream.Collectors 30 | import javax.imageio.ImageIO 31 | import javax.swing.* 32 | import javax.swing.border.CompoundBorder 33 | import javax.swing.border.EmptyBorder 34 | import javax.swing.event.DocumentEvent 35 | import javax.swing.event.DocumentListener 36 | import javax.swing.filechooser.FileNameExtensionFilter 37 | import javax.swing.text.DefaultCaret 38 | import kotlin.collections.ArrayList 39 | import kotlin.system.exitProcess 40 | 41 | object MainWindow : JFrame() { 42 | private val log = KotlinLogging.logger {} 43 | private val exitIcon by lazy { ImageIcon(ImageIO.read(javaClass.classLoader.getResource("exitup.png"))) } 44 | private val minIcon by lazy { ImageIcon(ImageIO.read(javaClass.classLoader.getResource("minimizeup.png"))) } 45 | private val exitIconDown by lazy { ImageIcon(ImageIO.read(javaClass.classLoader.getResource("exitdown.png"))) } 46 | private val minIconDown by lazy { ImageIcon(ImageIO.read(javaClass.classLoader.getResource("minimizedown.png"))) } 47 | private val exitIconHover by lazy { ImageIcon(ImageIO.read(javaClass.classLoader.getResource("exithover.png"))) } 48 | private val minIconHover by lazy { ImageIcon(ImageIO.read(javaClass.classLoader.getResource("minimizehover.png"))) } 49 | 50 | val ui by lazy { UI() } 51 | 52 | private lateinit var saveChooser: JSystemFileChooser 53 | private lateinit var importChooser: JSystemFileChooser 54 | val point by lazy { Point() } 55 | 56 | /** 57 | * Create the frame. 58 | */ 59 | fun init() { 60 | initFilechooser() 61 | log.debug("init UI") 62 | layout = BorderLayout() 63 | setSize(570, 355) 64 | background = Color(36, 36, 36) 65 | centerWindow() 66 | if(!isDisplayable) { 67 | isUndecorated = true 68 | } 69 | defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE 70 | isResizable = false 71 | ui.initComponents() 72 | add(ui, BorderLayout.CENTER) 73 | addMouseListener(object : MouseAdapter() { 74 | 75 | override fun mousePressed(e: MouseEvent) { 76 | point.x = e.x 77 | point.y = e.y 78 | } 79 | }) 80 | addMouseMotionListener(object : MouseMotionAdapter() { 81 | override fun mouseDragged(e: MouseEvent) { 82 | val p = location 83 | setLocation(p.x + e.x - point.x, p.y + e.y - point.y) 84 | } 85 | }) 86 | isVisible = true 87 | OnlineCheckWorker("http://google.com") {if (ConnectionManager.netStatus == NetStatus.SERVER_CONTACT) executeListener()}.execute() 88 | OnlineCheckWorker("http://bing.com") {if (ConnectionManager.netStatus == NetStatus.SERVER_CONTACT) executeListener()}.execute() 89 | OnlineCheckWorker("http://baidu.com") {if (ConnectionManager.netStatus == NetStatus.SERVER_CONTACT) executeListener()}.execute() 90 | } 91 | 92 | private var hasExecuted = false 93 | private fun executeListener() { 94 | if (!hasExecuted) { 95 | hasExecuted = true 96 | WurstBuildCheckWorker().execute() 97 | } 98 | } 99 | 100 | class UI : JPanel() { 101 | private val projNamePattern = Pattern.compile("(\\w|\\s)+") 102 | private var contentTable: Table = Table() 103 | private val topBar = JPanel() 104 | private val title = JPanel() 105 | private val windowLabel = JLabel(" Wurst Setup") 106 | var lblWelcome: JLabel = JLabel("Welcome to the Wurst Setup") 107 | var lblCurrentVersion: JLabel = JLabel("Installed Compiler Build: ") 108 | var lblCurVerNumber: JLabel = JLabel("(not installed)") 109 | var lblLatestVer: JLabel = JLabel("Latest Build: ") 110 | var lblLatestVerNumber: JLabel = JLabel("(unknown)") 111 | var progressBar: JProgressBar = JProgressBar() 112 | var btnCreate: SetupButton = SetupButton("Create Project") 113 | var btnUpdate: SetupButton = SetupButton("Install WurstScript") 114 | var importButton: SetupButton = SetupButton("Open Project") 115 | var btnAdvanced: SetupButton = SetupButton("Add") 116 | var jTextArea = JTextArea("Ready version: " + CompileTimeInfo.version + "\n") 117 | var projectNameTF: JTextField = JTextField("MyWurstProject") 118 | var projectRootTF: JTextField = JTextField("projectRoot") 119 | var dependencyTF: JTextField = JTextField("wurstStdlib2") 120 | private val exit = JButton(exitIcon) 121 | private val minimize = JButton(minIcon) 122 | private val gamePathTF = JTextField("Select your wc3 installation folder (optional)") 123 | 124 | private var selectedConfig: WurstProjectConfigData? = null 125 | var dependencies: MutableList = ArrayList(Arrays.asList("https://github.com/wurstscript/wurstStdlib2")) 126 | 127 | var inited = false 128 | fun initComponents() { 129 | setTitle("Wurst Setup") 130 | background = Color(36, 36, 36) 131 | 132 | setupTopBar() 133 | topBar.background = Color(64, 67, 69) 134 | title.background = Color(94, 97, 99) 135 | windowLabel.foreground = Color(255, 255, 255) 136 | contentTable.setSize(570, 350) 137 | 138 | contentPane.add(contentTable) 139 | 140 | contentTable.top() 141 | contentTable.row().height(26f) 142 | val titleTable = Table() 143 | titleTable.addCell(topBar).growX().height(26f) 144 | titleTable.addCell(title).size(100f, 26f) 145 | contentTable.addCell(titleTable).growX() 146 | 147 | contentTable.row() 148 | 149 | lblWelcome.horizontalAlignment = SwingConstants.CENTER 150 | lblWelcome.font = Font(Font.SANS_SERIF, Font.BOLD, 18) 151 | contentTable.addCell(lblWelcome).center().pad(2f) 152 | 153 | contentTable.row() 154 | 155 | val noteTable = Table() 156 | noteTable.addCell(lblCurrentVersion).center() 157 | noteTable.addCell(lblCurVerNumber).center() 158 | noteTable.addCell(lblLatestVer).center().padLeft(12f) 159 | noteTable.addCell(lblLatestVerNumber).center() 160 | contentTable.addCell(noteTable).pad(2f).growX() 161 | 162 | contentTable.row() 163 | 164 | createConfigTable() 165 | 166 | contentTable.row() 167 | 168 | jTextArea.background = Color(46, 46, 46) 169 | jTextArea.foreground = Color(255, 255, 255) 170 | jTextArea.font = Font(Font.MONOSPACED, Font.PLAIN, 12) 171 | if (jTextArea.caret is DefaultCaret) { 172 | val caret = jTextArea.caret as DefaultCaret 173 | caret.updatePolicy = DefaultCaret.ALWAYS_UPDATE 174 | caret.isSelectionVisible = true 175 | } 176 | jTextArea.isEditable = false 177 | jTextArea.margin = Insets(2, 2, 2, 2) 178 | val scrollPane = JScrollPane(jTextArea) 179 | contentTable.addCell(scrollPane).height(120f).growX().pad(2f) 180 | val line = BorderFactory.createLineBorder(Color.DARK_GRAY) 181 | scrollPane.border = line 182 | scrollPane.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_NEVER 183 | scrollPane.horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER 184 | contentTable.row() 185 | 186 | contentTable.addCell(progressBar).growX().pad(2f) 187 | 188 | contentTable.row() 189 | 190 | createButtonTable() 191 | UiStyle.setStyle(contentTable) 192 | 193 | inited = true 194 | refreshComponents() 195 | } 196 | 197 | private fun setupTopBar() { 198 | try { 199 | 200 | exit.isOpaque = false 201 | exit.isContentAreaFilled = false 202 | exit.isFocusPainted = false 203 | exit.isBorderPainted = false 204 | exit.pressedIcon = exitIconDown 205 | exit.rolloverIcon = exitIconHover 206 | 207 | minimize.isOpaque = false 208 | minimize.isContentAreaFilled = false 209 | minimize.isFocusPainted = false 210 | minimize.isBorderPainted = false 211 | minimize.pressedIcon = minIconDown 212 | minimize.rolloverIcon = minIconHover 213 | } catch (e: IOException) { 214 | e.printStackTrace() 215 | } 216 | 217 | title.layout = GridLayout(1, 2) 218 | 219 | minimize.size = Dimension(50, 26) 220 | exit.size = Dimension(50, 26) 221 | 222 | title.add(minimize) 223 | title.add(exit) 224 | topBar.layout = GridLayout(1, 1) 225 | topBar.add(windowLabel, GridBagConstraints(0, 0, 0, 0, 0.0, 0.0, NORTHWEST, VERTICAL, Insets(0, 0, 0, 0), 0, 0)) 226 | titleEvents(minimize, exit) 227 | } 228 | 229 | private fun titleEvents(minimize: JButton, exit: JButton) { 230 | minimize.addActionListener { _ -> state = Frame.ICONIFIED } 231 | exit.addActionListener { _ -> 232 | dispose() 233 | exitProcess(0) 234 | } 235 | } 236 | 237 | private var projectRootFile: File = File(".") 238 | 239 | private fun warnProjectName(): Boolean { 240 | if (projectNameTF.text.isNotEmpty() && !projNamePattern.matcher(projectNameTF.text).matches()) { 241 | JOptionPane.showMessageDialog( 242 | null, 243 | "Error: Please enter valid project name", 244 | "Error Massage", 245 | JOptionPane.ERROR_MESSAGE) 246 | return true 247 | } 248 | 249 | return false 250 | } 251 | 252 | private fun createConfigTable() { 253 | val that = this 254 | val configTable = Table() 255 | configTable.row().height(24f) 256 | configTable.addCell(JLabel("Project name:")).left() 257 | 258 | val projectInputTable = Table() 259 | projectInputTable.row().height(24f) 260 | projectNameTF.document.addDocumentListener(object : DocumentListener { 261 | override fun changedUpdate(e: DocumentEvent) { 262 | warn() 263 | } 264 | 265 | override fun removeUpdate(e: DocumentEvent) { 266 | warn() 267 | } 268 | 269 | override fun insertUpdate(e: DocumentEvent) { 270 | warn() 271 | } 272 | 273 | fun warn() { 274 | if (projectNameTF.text.isEmpty()) { 275 | btnCreate.isEnabled = false 276 | } else { 277 | projectRootTF.text = projectRootFile.absolutePath + File.separator + projectNameTF.text 278 | if (!disabled) { 279 | btnCreate.isEnabled = true 280 | } 281 | } 282 | } 283 | }) 284 | 285 | configTable.addCell(projectInputTable).growX() 286 | 287 | configTable.row().height(24f).padTop(2f) 288 | 289 | configTable.addCell(JLabel("Project root:")).left() 290 | 291 | val projectTF = Table() 292 | projectRootTF.isEditable = false 293 | projectTF.row().height(24f) 294 | projectTF.addCell(projectRootTF).growX() 295 | val selectProjectRoot = SetupButton("...") 296 | 297 | projectRootTF.text = projectRootFile.absolutePath + File.separator + projectNameTF.text 298 | projectInputTable.addCell(projectNameTF).growX() 299 | 300 | selectProjectRoot.addMouseListener(object : MouseAdapter() { 301 | override fun mouseClicked(arg0: MouseEvent) { 302 | if (saveChooser.showSaveDialog(that) == JFileChooser.APPROVE_OPTION) { 303 | projectRootFile = saveChooser.selectedFile 304 | projectRootTF.text = saveChooser.selectedFile.absolutePath + File.separator + projectNameTF.text 305 | } 306 | } 307 | }) 308 | projectTF.addCell(selectProjectRoot).pad(0f, 2f, 0f, 2f) 309 | 310 | configTable.addCell(projectTF).growX() 311 | 312 | configTable.row().height(24f).padTop(2f) 313 | 314 | configTable.addCell(JLabel("Game path:")).left() 315 | 316 | val gameTF = Table() 317 | gamePathTF.isEditable = false 318 | gameTF.addCell(gamePathTF).height(24f).growX() 319 | val selectGamePath = SetupButton("...") 320 | selectGamePath.addMouseListener(object : MouseAdapter() { 321 | override fun mouseClicked(arg0: MouseEvent) { 322 | if (saveChooser.showSaveDialog(that) == JFileChooser.APPROVE_OPTION) { 323 | gamePathTF.text = saveChooser.selectedFile.absolutePath 324 | } 325 | } 326 | }) 327 | if (System.getProperty("os.name").startsWith("Windows")) { 328 | try { 329 | val key = Registry.getKey(Registry.HKEY_CURRENT_USER + "\\SOFTWARE\\Blizzard Entertainment\\Warcraft III") 330 | var wc3Path = key?.getValueByName("InstallPath")?.rawValue 331 | if (wc3Path != null) { 332 | if (!wc3Path.endsWith(File.separator)) wc3Path += File.separator 333 | val gameFolder = Paths.get(wc3Path) 334 | if (Files.exists(gameFolder)) { 335 | gamePathTF.text = wc3Path 336 | } 337 | } else { 338 | checkDefaultWinLocation() 339 | } 340 | } catch (e: Exception) { 341 | checkDefaultWinLocation() 342 | } 343 | } 344 | gameTF.addCell(selectGamePath).height(24f).pad(0f, 2f, 0f, 2f) 345 | 346 | configTable.addCell(gameTF).growX() 347 | configTable.row().height(24f).padTop(2f) 348 | 349 | configTable.addCell(JLabel("Dependencies:")).left() 350 | 351 | val dependencyTable = Table() 352 | dependencyTF.isEditable = false 353 | dependencyTable.addCell(dependencyTF).height(24f).growX() 354 | btnAdvanced.addMouseListener(object : MouseAdapter() { 355 | override fun mouseClicked(arg0: MouseEvent) { 356 | log.debug("Adding dependency") 357 | AddRepoDialog() 358 | } 359 | }) 360 | 361 | dependencyTable.addCell(btnAdvanced).height(24f).pad(0f, 2f, 0f, 2f) 362 | 363 | configTable.addCell(dependencyTable).growX() 364 | 365 | contentTable.addCell(configTable).growX().pad(2f) 366 | } 367 | 368 | private fun checkDefaultWinLocation() { 369 | var gameFolder = Paths.get(System.getenv("ProgramFiles"))?.resolve("Warcraft III") 370 | if (gameFolder != null && Files.exists(gameFolder)) { 371 | gamePathTF.text = gameFolder.toAbsolutePath().toString() 372 | } else { 373 | gameFolder = Paths.get(System.getenv("ProgramFiles") + " (x86)")?.resolve("Warcraft III") 374 | if (gameFolder != null && Files.exists(gameFolder)) { 375 | gamePathTF.text = gameFolder.toAbsolutePath().toString() 376 | } else { 377 | log.warn("Didn't find warcraft installation.") 378 | } 379 | } 380 | } 381 | 382 | private fun handleImport() { 383 | try { 384 | val buildFile = importChooser.selectedFile.toPath() 385 | val config = WurstProjectConfig.loadProject(buildFile) 386 | if (config != null) { 387 | projectNameTF.text = config.projectName 388 | projectRootTF.text = buildFile.parent.toString() 389 | dependencyTF.text = config.dependencies.stream().map { i -> i.substring(i.lastIndexOf("/") + 1) }.collect(Collectors.joining(", ")) 390 | dependencies = ArrayList(config.dependencies) 391 | btnCreate.text = "Update Project" 392 | selectedConfig = config 393 | Log.print("Use the \"Update Project\" button to update config and dependencies.\n") 394 | } 395 | } catch (e: IOException) { 396 | e.printStackTrace() 397 | } 398 | } 399 | 400 | fun refreshComponents() { 401 | if (!inited) return 402 | SwingUtilities.invokeLater { 403 | progressBar.isIndeterminate = false 404 | if (!disabled) { 405 | importButton.isEnabled = true 406 | } 407 | when (ConnectionManager.netStatus) { 408 | NetStatus.CLIENT_OFFLINE, NetStatus.SERVER_OFFLINE -> { 409 | lblLatestVerNumber.text = "(loading..)" 410 | lblLatestVerNumber.foreground = Color.DARK_GRAY 411 | btnCreate.isEnabled = false 412 | btnUpdate.isEnabled = false 413 | } 414 | NetStatus.ONLINE -> { 415 | lblLatestVerNumber.text = InstallationManager.latestCompilerVersion.toString() 416 | lblLatestVerNumber.foreground = Color.decode("#005719") 417 | if (!disabled) { 418 | btnUpdate.isEnabled = true 419 | } 420 | } 421 | NetStatus.SERVER_CONTACT -> { 422 | lblLatestVerNumber.text = "Loading.." 423 | btnUpdate.isEnabled = false 424 | } 425 | } 426 | when (InstallationManager.status) { 427 | InstallationManager.InstallationStatus.NOT_INSTALLED -> { 428 | btnUpdate.text = "Install WurstScript" 429 | btnCreate.isEnabled = false 430 | lblCurVerNumber.foreground = Color.DARK_GRAY 431 | } 432 | InstallationManager.InstallationStatus.INSTALLED_UPTODATE -> { 433 | lblCurVerNumber.text = getVersionString() 434 | lblCurVerNumber.foreground = Color.decode("#005719") 435 | if (!disabled) { 436 | btnCreate.isEnabled = true 437 | } 438 | btnUpdate.text = "Compiler up to date" 439 | btnUpdate.isEnabled = false 440 | } 441 | InstallationManager.InstallationStatus.INSTALLED_UNKNOWN, InstallationManager.InstallationStatus.INSTALLED_OUTDATED -> { 442 | lblCurVerNumber.text = getVersionString() 443 | lblCurVerNumber.foreground = Color.decode("#702D2D") 444 | if (!disabled) { 445 | btnCreate.isEnabled = true 446 | } 447 | btnUpdate.text = "Update WurstScript" 448 | } 449 | } 450 | } 451 | } 452 | 453 | private fun getVersionString() = 454 | if (InstallationManager.currentCompilerVersion > 0) InstallationManager.currentCompilerVersion.toString() else "(unofficial build)" 455 | 456 | private var disabled = false 457 | 458 | fun enableButtons() { 459 | disabled = false 460 | } 461 | 462 | fun disableButtons() { 463 | disabled = true 464 | if (SwingUtilities.isEventDispatchThread()) { 465 | btnCreate.isEnabled = false 466 | btnUpdate.isEnabled = false 467 | importButton.isEnabled = false 468 | btnAdvanced.isEnabled = false 469 | } else { 470 | SwingUtilities.invokeLater { 471 | btnCreate.isEnabled = false 472 | btnUpdate.isEnabled = false 473 | importButton.isEnabled = false 474 | btnAdvanced.isEnabled = false 475 | } 476 | } 477 | 478 | } 479 | 480 | private fun createButtonTable() { 481 | val buttonTable = Table() 482 | buttonTable.setSize(420, 90) 483 | btnUpdate.addMouseListener(object : MouseAdapter() { 484 | override fun mouseClicked(arg0: MouseEvent) { 485 | if (btnUpdate.isEnabled && !progressBar.isIndeterminate) { 486 | handleWurstUpdate() 487 | } 488 | } 489 | }) 490 | buttonTable.addCell(btnUpdate) 491 | buttonTable.addCell().growX() 492 | btnCreate.addMouseListener(object : MouseAdapter() { 493 | override fun mouseClicked(arg0: MouseEvent) { 494 | if (btnCreate.isEnabled && !progressBar.isIndeterminate) { 495 | SwingUtilities.invokeLater { progressBar.isIndeterminate = true } 496 | disableButtons() 497 | if (selectedConfig == null) { 498 | try { 499 | selectedConfig = WurstProjectConfig.loadProject(Paths.get(projectRootTF.text, CONFIG_FILE_NAME)) 500 | } catch (e: IOException) { 501 | e.printStackTrace() 502 | } 503 | } 504 | if (selectedConfig != null) { 505 | handleUpdateProject() 506 | } else { 507 | if (warnProjectName()) { 508 | UiManager.refreshComponents() 509 | } else { 510 | handleCreateProject() 511 | } 512 | } 513 | } 514 | } 515 | }) 516 | val that = this 517 | importButton.addMouseListener(object : MouseAdapter() { 518 | override fun mouseClicked(arg0: MouseEvent) { 519 | if (importButton.isEnabled && !progressBar.isIndeterminate) { 520 | if (importChooser.showOpenDialog(that) == JFileChooser.APPROVE_OPTION) { 521 | handleImport() 522 | } 523 | } 524 | } 525 | }) 526 | buttonTable.addCell(importButton).padRight(6f) 527 | buttonTable.addCell(btnCreate) 528 | contentTable.addCell(buttonTable).growX().pad(2f) 529 | } 530 | 531 | private fun handleCreateProject() { 532 | SwingUtilities.invokeLater { progressBar.isIndeterminate = true } 533 | disableButtons() 534 | val gamePath = gamePathTF.text 535 | val projectRoot = Paths.get(projectRootTF.text) 536 | val gameRoot = if (gamePath.isNotEmpty()) Paths.get(gamePath) else null 537 | val config = WurstProjectConfigData("MyProjectName", 538 | java.util.ArrayList(mutableListOf("https://github.com/wurstscript/wurstStdlib2")), 539 | buildMapData = WurstProjectBuildMapData(name = "MyMapName", fileName = "MyMapFile", author = System.getProperty("user.name"))) 540 | config.projectName = projectNameTF.text 541 | dependencies.forEach { elem -> 542 | if (!config.dependencies.contains(elem)) { 543 | config.dependencies.add(elem) 544 | } 545 | } 546 | 547 | log.info("Generating new project.\n\t-> gamepath <{}>, root <{}>, config <{}>", gameRoot, projectRoot, config) 548 | if (SetupApp.setup.isGUILaunch) { 549 | ProjectCreateWorker(projectRoot, gameRoot, config).execute() 550 | } else { 551 | WurstProjectConfig.handleCreate(projectRoot, gameRoot, config) 552 | } 553 | } 554 | 555 | private fun handleUpdateProject() { 556 | val gameRoot = Paths.get(gamePathTF.text) 557 | val projectRoot = Paths.get(projectRootTF.text) 558 | if (selectedConfig != null) { 559 | dependencies.forEach { e -> if (selectedConfig?.dependencies?.contains(e) == false) selectedConfig?.dependencies?.add(e) } 560 | log.info("Installing project. gamepath <{}>, root <{}>", gameRoot, projectRoot) 561 | ProjectUpdateWorker(projectRoot, gameRoot, selectedConfig!!).execute() 562 | } 563 | } 564 | 565 | private fun handleWurstUpdate() { 566 | log.debug("handle wurst update") 567 | SwingUtilities.invokeLater { progressBar.isIndeterminate = true } 568 | disableButtons() 569 | CompilerUpdateWorker().execute() 570 | } 571 | 572 | } 573 | 574 | private fun initFilechooser() { 575 | saveChooser = JSystemFileChooser() 576 | saveChooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY 577 | saveChooser.currentDirectory = java.io.File(".") 578 | saveChooser.dialogTitle = "Select project root" 579 | saveChooser.isAcceptAllFileFilterUsed = false 580 | 581 | importChooser = JSystemFileChooser() 582 | importChooser.currentDirectory = java.io.File(".") 583 | importChooser.dialogTitle = "Select wurst.build file" 584 | importChooser.isAcceptAllFileFilterUsed = false 585 | importChooser.fileFilter = FileNameExtensionFilter("wurst.build files", "build") 586 | } 587 | 588 | private fun centerWindow() { 589 | val screenBounds = graphicsConfiguration.bounds 590 | 591 | val centerX = screenBounds.x + screenBounds.width / 2 592 | val centerY = screenBounds.y + screenBounds.height / 2 593 | 594 | setLocation(centerX - width / 2, centerY - height / 2) 595 | } 596 | 597 | class SetupButton internal constructor(buttonTag: String) : JButton(buttonTag) { 598 | private val textColor = Color.WHITE 599 | private val backgroundColor = Color(18, 18, 18) 600 | private val overColor = Color(120, 20, 20) 601 | private val pressedColor = Color(240, 40, 40) 602 | 603 | init { 604 | background = backgroundColor 605 | foreground = textColor 606 | isContentAreaFilled = false 607 | isFocusPainted = false 608 | 609 | val line = BorderFactory.createLineBorder(Color(80, 80, 80)) 610 | val empty = EmptyBorder(4, 4, 4, 4) 611 | val border = CompoundBorder(line, empty) 612 | setBorder(border) 613 | } 614 | 615 | override fun paintComponent(g: Graphics) { 616 | when { 617 | getModel().isPressed -> g.color = pressedColor 618 | getModel().isRollover -> g.color = overColor 619 | else -> g.color = background 620 | } 621 | g.fillRect(0, 0, width, height) 622 | super.paintComponent(g) 623 | } 624 | } 625 | 626 | } 627 | -------------------------------------------------------------------------------- /src/main/kotlin/ui/SetupUpdateDialog.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import file.Download 4 | import global.Log 5 | import tablelayout.Table 6 | import java.awt.Color 7 | import java.awt.Component 8 | import java.awt.Desktop 9 | import java.awt.GridBagLayout 10 | import java.io.IOException 11 | import java.net.URI 12 | import java.net.URISyntaxException 13 | import java.net.URL 14 | import javax.imageio.ImageIO 15 | import javax.swing.JDialog 16 | import javax.swing.JLabel 17 | import javax.swing.JPanel 18 | import kotlin.system.exitProcess 19 | 20 | 21 | class SetupUpdateDialog(message: String) : JDialog() { 22 | 23 | private val contentPane: JPanel = JPanel(GridBagLayout()) 24 | private val contentTable = Table() 25 | 26 | private val buttonVisit = SetupButton("Open Website") 27 | private val buttonNow = SetupButton("Download Now") 28 | private val buttonDeny = SetupButton("Continue") 29 | 30 | init { 31 | title = "Notification" 32 | contentTable.setSize(340, 140) 33 | setSize(340, 140) 34 | 35 | setContentPane(contentPane) 36 | contentPane.add(contentTable) 37 | modalityType = ModalityType.APPLICATION_MODAL 38 | try { 39 | setIconImage(ImageIO.read(javaClass.classLoader.getResource("icon.png"))) 40 | } catch (e: IOException) { 41 | e.printStackTrace() 42 | } 43 | 44 | uiLayout(message) 45 | uiStyle() 46 | getRootPane().defaultButton = buttonVisit 47 | 48 | buttonDeny.addActionListener { 49 | dispose() 50 | } 51 | 52 | buttonNow.addActionListener { 53 | Log.print("Updating setup..") 54 | MainWindow.ui.disableButtons() 55 | Download.downloadSetup { 56 | Runtime.getRuntime().exec(arrayOf("java", "-jar", it.fileName.toAbsolutePath().toString())) 57 | exitProcess(0) 58 | } 59 | dispose() 60 | } 61 | 62 | buttonVisit.addActionListener { 63 | openWebpage(URL("https://wurstlang.org/")) 64 | exitProcess(0) 65 | } 66 | 67 | setLocationRelativeTo(null) 68 | isAlwaysOnTop = true 69 | 70 | isVisible = true 71 | } 72 | 73 | private fun uiLayout(message: String) { 74 | val welcomeLabel = JLabel("
$message
") 75 | welcomeLabel.alignmentX = Component.CENTER_ALIGNMENT 76 | welcomeLabel.foreground = Color.WHITE 77 | contentTable.top() 78 | contentTable.addCell(welcomeLabel).width(300f).top().pad(-2f, 5f, 5f, 5f) 79 | contentTable.row() 80 | 81 | val buttonTable = Table() 82 | buttonTable.addCell(buttonVisit).pad(0f, 6f, 0f, 6f) 83 | buttonTable.addCell(buttonNow).pad(0f, 6f, 0f, 6f) 84 | buttonTable.addCell(buttonDeny).pad(0f, 6f, 0f, 6f) 85 | 86 | contentTable.addCell(buttonTable).growX().padTop(6f) 87 | } 88 | 89 | private fun uiStyle() { 90 | contentPane.background = Color(36, 36, 36) 91 | 92 | UiStyle.setStyle(contentTable) 93 | 94 | } 95 | 96 | private fun openWebpage(uri: URI) { 97 | val desktop = if (Desktop.isDesktopSupported()) Desktop.getDesktop() else null 98 | if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { 99 | try { 100 | desktop.browse(uri) 101 | } catch (e: Exception) { 102 | e.printStackTrace() 103 | } 104 | 105 | } 106 | } 107 | 108 | private fun openWebpage(url: URL) { 109 | try { 110 | openWebpage(url.toURI()) 111 | } catch (e: URISyntaxException) { 112 | e.printStackTrace() 113 | } 114 | 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/main/kotlin/ui/UiManager.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import file.SetupApp 4 | import java.io.IOException 5 | import javax.imageio.ImageIO 6 | import javax.swing.SwingUtilities 7 | 8 | object UiManager { 9 | 10 | fun initUI() { 11 | SwingUtilities.invokeLater { 12 | MainWindow.init() 13 | try { 14 | MainWindow.iconImage = ImageIO.read(javaClass.classLoader.getResource("icon.png")) 15 | } catch (e: IOException) { 16 | e.printStackTrace() 17 | } 18 | } 19 | } 20 | 21 | fun refreshComponents() { 22 | if (SetupApp.setup.isGUILaunch) { 23 | MainWindow.ui.refreshComponents() 24 | MainWindow.ui.enableButtons() 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/ui/UpdateFoundDialog.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import tablelayout.Table 4 | import java.awt.Color 5 | import java.awt.Component 6 | import java.awt.GridBagLayout 7 | import java.io.IOException 8 | import javax.imageio.ImageIO 9 | import javax.swing.JDialog 10 | import javax.swing.JLabel 11 | import javax.swing.JPanel 12 | import kotlin.system.exitProcess 13 | 14 | class UpdateFoundDialog(message: String) : JDialog() { 15 | 16 | private val contentPane: JPanel = JPanel(GridBagLayout()) 17 | private val contentTable = Table() 18 | 19 | private val buttonUpdate = SetupButton("Update") 20 | private val buttonSnooze = SetupButton("Later") 21 | private val buttonDeny = SetupButton("Close") 22 | 23 | init { 24 | title = "Notification" 25 | contentTable.setSize(340, 120) 26 | setSize(340, 120) 27 | 28 | setContentPane(contentPane) 29 | contentPane.add(contentTable) 30 | modalityType = ModalityType.APPLICATION_MODAL 31 | try { 32 | setIconImage(ImageIO.read(javaClass.classLoader.getResource("icon.png"))) 33 | } catch (e: IOException) { 34 | e.printStackTrace() 35 | } 36 | 37 | uiLayout(message) 38 | uiStyle() 39 | getRootPane().defaultButton = buttonUpdate 40 | 41 | buttonDeny.addActionListener { e -> 42 | exitProcess(0) 43 | } 44 | 45 | buttonSnooze.addActionListener { e -> 46 | exitProcess(0) 47 | } 48 | 49 | buttonUpdate.addActionListener { e -> 50 | UiManager.initUI() 51 | } 52 | 53 | setLocationRelativeTo(null) 54 | isAlwaysOnTop = true 55 | 56 | isVisible = true 57 | } 58 | 59 | private fun uiLayout(message: String) { 60 | val welcomeLabel = JLabel("
$message
") 61 | welcomeLabel.alignmentX = Component.CENTER_ALIGNMENT 62 | welcomeLabel.foreground = Color.WHITE 63 | contentTable.top() 64 | contentTable.addCell(welcomeLabel).width(300f).top().pad(-2f, 5f, 5f, 5f) 65 | contentTable.row() 66 | 67 | val buttonTable = Table() 68 | buttonTable.addCell(buttonUpdate).pad(0f, 6f, 0f, 6f) 69 | buttonTable.addCell(buttonSnooze).pad(0f, 6f, 0f, 6f) 70 | buttonTable.addCell(buttonDeny).pad(0f, 6f, 0f, 6f) 71 | 72 | contentTable.addCell(buttonTable).growX().padTop(6f) 73 | 74 | } 75 | 76 | private fun uiStyle() { 77 | contentPane.background = Color(36, 36, 36) 78 | 79 | UiStyle.setStyle(contentTable) 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/kotlin/workers/CompilerUpdateWorker.kt: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import global.InstallationManager 4 | import javax.swing.SwingWorker 5 | 6 | class CompilerUpdateWorker : SwingWorker() { 7 | 8 | @Throws(Exception::class) 9 | override fun doInBackground(): Boolean? { 10 | InstallationManager.handleUpdate() 11 | return null 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/workers/DependencyVerifierWorker.kt: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import file.DependencyManager 4 | import global.Log 5 | import org.eclipse.jgit.api.Git 6 | import ui.MainWindow 7 | import ui.UiManager 8 | import java.util.stream.Collectors 9 | import javax.swing.DesktopManager 10 | import javax.swing.SwingUtilities 11 | import javax.swing.SwingWorker 12 | 13 | class DependencyVerifierWorker(val dependencyUrl: String) : SwingWorker() { 14 | 15 | @Throws(Exception::class) 16 | override fun doInBackground(): Boolean? { 17 | Log.print("Checking git repo..") 18 | try { 19 | val resolved = DependencyManager.resolveName(dependencyUrl) 20 | val result = Git.lsRemoteRepository() 21 | .setRemote(resolved.first) 22 | .call() 23 | if (!result.isEmpty()) { 24 | Log.print("valid!\n") 25 | MainWindow.ui.dependencies.add(dependencyUrl) 26 | SwingUtilities.invokeLater { 27 | MainWindow.ui.dependencyTF.text = MainWindow.ui.dependencies.stream().map { i -> i.substring(i.lastIndexOf("/") + 1) }.collect(Collectors.joining(", ")) 28 | } 29 | UiManager.refreshComponents() 30 | } else { 31 | Log.print("Error: Entered invalid git repo\n") 32 | } 33 | } catch (e: Exception) { 34 | Log.print("Error: Entered invalid git repo\n") 35 | e.printStackTrace() 36 | } 37 | return null 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/workers/DownloadWithProgressWorker.kt: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import file.Download 4 | import global.Log 5 | import mu.KotlinLogging 6 | import ui.MainWindow 7 | import java.io.BufferedOutputStream 8 | import java.nio.file.Files 9 | import java.nio.file.Path 10 | import java.nio.file.Paths 11 | import javax.swing.JProgressBar 12 | import javax.swing.SwingUtilities 13 | import javax.swing.SwingWorker 14 | 15 | 16 | class DownloadWithProgressWorker(private val filePath: String, private val progressBar: JProgressBar, val finish: Path.() -> Unit) : SwingWorker() { 17 | private val log = KotlinLogging.logger {} 18 | 19 | private var filename = ""; 20 | 21 | @Throws(Exception::class) 22 | override fun doInBackground(): Boolean? { 23 | try { 24 | SwingUtilities.invokeLater { progressBar.isIndeterminate = false } 25 | MainWindow.ui.disableButtons() 26 | val httpConnection = Download.getHttpURLConnection(filePath) 27 | val completeFileSize = httpConnection.contentLength 28 | 29 | val size = completeFileSize / 1024 / 1024 30 | Log.print("(" + (if (size == 0) "<1" else size) + "MB)") 31 | val input = java.io.BufferedInputStream(httpConnection.inputStream) 32 | filename = filePath.substring(filePath.lastIndexOf("/") + 1) 33 | if (Files.exists(Paths.get(filename))) { 34 | filename += ".2.jar" 35 | } 36 | 37 | val fos = java.io.FileOutputStream(filename) 38 | val bout = BufferedOutputStream(fos, 1024) 39 | val data = ByteArray(1024) 40 | var downloadedFileSize: Long = 0 41 | var x = input.read(data, 0, 1024) 42 | do { 43 | downloadedFileSize += x.toLong() 44 | 45 | // calculate progress 46 | val currentProgress = (downloadedFileSize.toDouble() / completeFileSize.toDouble() * 100.0).toInt() 47 | 48 | // update progress bar 49 | SwingUtilities.invokeLater { progressBar.value = currentProgress } 50 | 51 | bout.write(data, 0, x) 52 | x = input.read(data, 0, 1024) 53 | } while (x >= 0) 54 | bout.close() 55 | input.close() 56 | fos.close() 57 | } catch (e: Exception) { 58 | log.error(e.localizedMessage) 59 | } 60 | return null 61 | } 62 | 63 | override fun done() { 64 | SwingUtilities.invokeLater { progressBar.isIndeterminate = true } 65 | finish.invoke(Paths.get(filename)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/kotlin/workers/ExtractWorker.kt: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import global.InstallationManager 4 | import global.Log 5 | import mu.KotlinLogging 6 | import java.nio.file.Files 7 | import java.nio.file.Path 8 | import javax.swing.JProgressBar 9 | import javax.swing.SwingUtilities 10 | import javax.swing.SwingWorker 11 | 12 | 13 | class ExtractWorker(private val filePath: Path, val progressBar: JProgressBar?, val callback: (Boolean) -> Unit) : SwingWorker() { 14 | private val log = KotlinLogging.logger {} 15 | private var extractSuccess: Boolean = false 16 | 17 | @Throws(Exception::class) 18 | override fun doInBackground(): Boolean? { 19 | try { 20 | if (progressBar != null) { 21 | SwingUtilities.invokeLater { 22 | progressBar.isIndeterminate = true 23 | } 24 | } 25 | Log.print("Extracting compiler..") 26 | log.debug("extract compiler") 27 | extractSuccess = file.ZipArchiveExtractor.extractArchive(filePath, InstallationManager.installDir) 28 | Files.delete(filePath) 29 | } catch (e: Exception) { 30 | log.error(e.localizedMessage) 31 | callback.invoke(false) 32 | } 33 | return null 34 | } 35 | 36 | override fun done() { 37 | if (progressBar != null) { 38 | SwingUtilities.invokeLater { 39 | progressBar.isIndeterminate = false 40 | } 41 | } 42 | callback.invoke(extractSuccess) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/workers/OnlineCheckWorker.kt: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import mu.KotlinLogging 4 | import net.ConnectionManager 5 | import ui.UiManager 6 | import javax.swing.SwingWorker 7 | 8 | class OnlineCheckWorker(private val url: String, private val doneListener: () -> Unit) : SwingWorker() { 9 | private val log = KotlinLogging.logger {} 10 | 11 | @Throws(Exception::class) 12 | override fun doInBackground(): Boolean? { 13 | log.debug("check connectivity") 14 | ConnectionManager.checkConnectivity(url) 15 | UiManager.refreshComponents() 16 | log.debug("check build") 17 | doneListener.invoke() 18 | return null 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/workers/ProjectCreateWorker.kt: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import config.WurstProjectConfigData 4 | import java.nio.file.Path 5 | import javax.swing.SwingWorker 6 | 7 | /** Handles creating a new project */ 8 | class ProjectCreateWorker(private val projectRoot: Path, private val gameRoot: Path?, private val configData: WurstProjectConfigData) : SwingWorker() { 9 | 10 | @Throws(Exception::class) 11 | override fun doInBackground(): Boolean? { 12 | config.WurstProjectConfig.handleCreate(projectRoot, gameRoot, configData) 13 | return null 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/workers/ProjectUpdateWorker.kt: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import config.WurstProjectConfigData 4 | import java.nio.file.Path 5 | import javax.swing.SwingWorker 6 | 7 | /** Handles updating an existing project */ 8 | class ProjectUpdateWorker(private val projectRoot: Path, private val gamePath: Path, private val configData: WurstProjectConfigData) : SwingWorker() { 9 | 10 | @Throws(Exception::class) 11 | override fun doInBackground(): Boolean? { 12 | config.WurstProjectConfig.handleUpdate(projectRoot, gamePath, configData) 13 | return null 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/workers/RemoveWurstWorker.kt: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import global.InstallationManager 4 | import javax.swing.SwingWorker 5 | 6 | class RemoveWurstWorker : SwingWorker() { 7 | 8 | @Throws(Exception::class) 9 | override fun doInBackground(): Boolean? { 10 | InstallationManager.handleRemove() 11 | return null 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/workers/WurstBuildCheckWorker.kt: -------------------------------------------------------------------------------- 1 | package workers 2 | 3 | import file.CompileTimeInfo 4 | import file.SetupApp 5 | import global.InstallationManager 6 | import mu.KotlinLogging 7 | import net.ConnectionManager 8 | import net.NetStatus 9 | import ui.SetupUpdateDialog 10 | import ui.UiManager 11 | import javax.swing.SwingWorker 12 | 13 | class WurstBuildCheckWorker : SwingWorker() { 14 | 15 | private val log = KotlinLogging.logger {} 16 | 17 | @Throws(Exception::class) 18 | override fun doInBackground(): Boolean? { 19 | ConnectionManager.checkWurstBuild() 20 | InstallationManager.verifyInstallation() 21 | if (ConnectionManager.netStatus == NetStatus.ONLINE) { 22 | val latestSetupBuild = ConnectionManager.getLatestSetupBuild() 23 | val jenkinsBuildVer = InstallationManager.getJenkinsBuildVer(CompileTimeInfo.version) 24 | log.debug("current setup ver: $jenkinsBuildVer latest Setup: $latestSetupBuild") 25 | if (latestSetupBuild > jenkinsBuildVer) { 26 | SetupUpdateDialog("There is a more recent version of the setup tool available. It is highly recommended" + 27 | " to update before making any further changes.") 28 | } 29 | } 30 | UiManager.refreshComponents() 31 | return null 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/exitdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wurstscript/WurstSetup/dc99e0c7805e80fa21c60034896e871475398355/src/main/resources/exitdown.png -------------------------------------------------------------------------------- /src/main/resources/exithover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wurstscript/WurstSetup/dc99e0c7805e80fa21c60034896e871475398355/src/main/resources/exithover.png -------------------------------------------------------------------------------- /src/main/resources/exitup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wurstscript/WurstSetup/dc99e0c7805e80fa21c60034896e871475398355/src/main/resources/exitup.png -------------------------------------------------------------------------------- /src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wurstscript/WurstSetup/dc99e0c7805e80fa21c60034896e871475398355/src/main/resources/icon.png -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DEBUG 6 | DENY 7 | 8 | 9 | TRACE 10 | DENY 11 | 12 | 13 | %msg%n 14 | 15 | 16 | 17 | false 18 | ${user.home}/.wurst/logs/setup.log 19 | 20 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{35} - %msg %n 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/minimizedown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wurstscript/WurstSetup/dc99e0c7805e80fa21c60034896e871475398355/src/main/resources/minimizedown.png -------------------------------------------------------------------------------- /src/main/resources/minimizehover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wurstscript/WurstSetup/dc99e0c7805e80fa21c60034896e871475398355/src/main/resources/minimizehover.png -------------------------------------------------------------------------------- /src/main/resources/minimizeup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wurstscript/WurstSetup/dc99e0c7805e80fa21c60034896e871475398355/src/main/resources/minimizeup.png -------------------------------------------------------------------------------- /src/main/resources/wbschema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "http://example.com/product.schema.json", 4 | "title": "Wurst Build File", 5 | "description": "Exposes dependency and map configuration", 6 | "type": "object", 7 | "properties": { 8 | "projectName": { 9 | "description": "The name of this wurst project", 10 | "type": "string" 11 | }, 12 | "dependencies": { 13 | "description": "Git repository urls of this project's dependencies", 14 | "type": "array", 15 | "items": { 16 | "type": "string" 17 | }, 18 | "uniqueItems": true 19 | }, 20 | "buildMapData": { 21 | "description": "Map configuration for the build map command", 22 | "type": "object", 23 | "properties": { 24 | "name": { 25 | "description": "The name that will be displayed ingame", 26 | "type": "string" 27 | }, 28 | "fileName": { 29 | "description": "The name of the output file", 30 | "type": "string" 31 | }, 32 | "author": { 33 | "description": "The name of the map's author", 34 | "type": "string" 35 | }, 36 | "scenarioData": { 37 | "description": "The scenario options", 38 | "type": "object", 39 | "properties": { 40 | "description": { 41 | "description": "The map's description", 42 | "type": "string" 43 | }, 44 | "suggestedPlayers": { 45 | "description": "The map's suggested player amount", 46 | "type": "string" 47 | }, 48 | "loadingScreen": { 49 | "description": "The loading screen options", 50 | "type": "object", 51 | "properties": { 52 | "model": { 53 | "description": "The path to a custom loading screen model", 54 | "type": "string" 55 | }, 56 | "background": { 57 | "description": "The name of an existing loading screen, e.g. Generic", 58 | "type": "string" 59 | }, 60 | "title": { 61 | "description": "The loading screen title", 62 | "type": "string" 63 | }, 64 | "subTitle": { 65 | "description": "The loading screen subtitle", 66 | "type": "string" 67 | }, 68 | "text": { 69 | "description": "The loading screen text", 70 | "type": "string" 71 | } 72 | } 73 | } 74 | } 75 | }, 76 | "optionsFlags": { 77 | "description": "The map option flags", 78 | "type": "object", 79 | "properties": { 80 | "hideMinimapPreview": { 81 | "description": "Whether or not to hide the minimap preview", 82 | "type": "boolean" 83 | }, 84 | "forcesFixed": { 85 | "description": "Whether or not the force assignments are fixed", 86 | "type": "boolean" 87 | }, 88 | "maskedAreasPartiallyVisible": { 89 | "description": "Whether or not to makes areas with black mask partially visible", 90 | "type": "boolean" 91 | }, 92 | "showWavesOnCliffShores": { 93 | "description": "Whether or not to show water waves on cliff shores", 94 | "type": "boolean" 95 | }, 96 | "showWavesOnRollingShores": { 97 | "description": "Whether or not to show water waves on rolling shores", 98 | "type": "boolean" 99 | }, 100 | "useItemClassificationSystem": { 101 | "description": "Whether or not to use the item classification system", 102 | "type": "boolean" 103 | } 104 | } 105 | }, 106 | "players": { 107 | "description": "The available player slots", 108 | "type": "array", 109 | "items": { 110 | "$ref": "#/definitions/player" 111 | } 112 | }, 113 | "forces": { 114 | "description": "The available player forces", 115 | "type": "array", 116 | "items": { 117 | "$ref": "#/definitions/force" 118 | } 119 | } 120 | } 121 | } 122 | }, 123 | "definitions": { 124 | "player": { 125 | "type": "object", 126 | "required": [ 127 | "id" 128 | ], 129 | "properties": { 130 | "id": { 131 | "type": "integer", 132 | "description": "The ID of the player" 133 | }, 134 | "name": { 135 | "type": "string", 136 | "description": "The name of the player" 137 | }, 138 | "race": { 139 | "type": "string", 140 | "description": "The race of this player", 141 | "enum": ["NIGHT_ELF", "HUMAN", "ORC", "SELECTABLE", "UNDEAD"] 142 | }, 143 | "controller": { 144 | "type": "string", 145 | "description": "The controller of this player", 146 | "enum": ["USER", "RESCUABLE", "NEUTRAL", "COMPUTER"] 147 | }, 148 | "fixedStartLoc": { 149 | "type": "boolean", 150 | "description": "Whether or not the startposition is fixed" 151 | } 152 | } 153 | }, 154 | "force": { 155 | "type": "object", 156 | "required": [ 157 | "name", 158 | "playerIds" 159 | ], 160 | "properties": { 161 | "name": { 162 | "type": "string", 163 | "description": "The name of the player" 164 | }, 165 | "playerIds": { 166 | "type": "array", 167 | "description": "The player ids in this force", 168 | "items": { 169 | "type": "integer" 170 | } 171 | }, 172 | "flags": { 173 | "type": "object", 174 | "description": "The controller of this player", 175 | "properties": { 176 | "allied": { 177 | "type": "boolean", 178 | "description": "Whether or not players in this force are allied" 179 | }, 180 | "alliedVictory": { 181 | "type": "boolean", 182 | "description": "Whether or not players in this force will win together" 183 | }, 184 | "sharedVision": { 185 | "type": "boolean", 186 | "description": "Whether or not players in this force share map vision" 187 | }, 188 | "sharedControl": { 189 | "type": "boolean", 190 | "description": "Whether or not players in this force share unit control" 191 | }, 192 | "sharedControlAdvanced": { 193 | "type": "boolean", 194 | "description": "Whether or not players in this force share advanced unit control (building, hero skilling, etc.)" 195 | } 196 | } 197 | } 198 | } 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/test/kotlin/CMDTests.kt: -------------------------------------------------------------------------------- 1 | import com.github.stefanbirkner.systemlambda.SystemLambda 2 | import file.CLICommand 3 | import file.DependencyManager 4 | import file.SetupApp 5 | import file.SetupMain 6 | import global.InstallationManager 7 | import net.ConnectionManager 8 | import org.eclipse.jgit.internal.storage.file.FileRepository 9 | import org.testng.Assert 10 | import org.testng.annotations.Test 11 | import java.nio.file.Files 12 | 13 | 14 | class CMDTests { 15 | 16 | companion object { 17 | private const val INSTALL = "install" 18 | private const val REMOVE = "remove" 19 | private const val GENERATE = "generate" 20 | private const val HELP = "help" 21 | private const val TEST = "test" 22 | private const val BUILD = "build" 23 | private const val WURSTSCRIPT = "wurstscript" 24 | } 25 | 26 | 27 | @Test(priority = 1) 28 | fun testUnInstallCmd() { 29 | SetupMain.main(listOf(INSTALL, WURSTSCRIPT).toTypedArray()) 30 | ConnectionManager.checkConnectivity("http://google.com") 31 | ConnectionManager.checkWurstBuild() 32 | InstallationManager.verifyInstallation() 33 | Assert.assertEquals(InstallationManager.status, InstallationManager.InstallationStatus.INSTALLED_UPTODATE) 34 | 35 | SetupMain.main(listOf(REMOVE, WURSTSCRIPT).toTypedArray()) 36 | InstallationManager.verifyInstallation() 37 | Assert.assertEquals(InstallationManager.status, InstallationManager.InstallationStatus.NOT_INSTALLED) 38 | 39 | SetupMain.main(listOf(INSTALL, WURSTSCRIPT).toTypedArray()) 40 | InstallationManager.verifyInstallation() 41 | Assert.assertEquals(InstallationManager.status, InstallationManager.InstallationStatus.INSTALLED_UPTODATE) 42 | } 43 | 44 | @Test(priority = 2) 45 | fun testCreateHelpCmd() { 46 | Assert.assertEquals(InstallationManager.status, InstallationManager.InstallationStatus.INSTALLED_UPTODATE) 47 | val setupMain = SetupMain() 48 | setupMain.doMain(listOf(HELP).toTypedArray()) 49 | 50 | Assert.assertEquals(setupMain.command, CLICommand.HELP) 51 | } 52 | 53 | @Test(priority = 2) 54 | fun testCreateProjectCmd() { 55 | Assert.assertEquals(InstallationManager.status, InstallationManager.InstallationStatus.INSTALLED_UPTODATE) 56 | SetupMain.main(listOf(GENERATE, "myname").toTypedArray()) 57 | 58 | Assert.assertTrue(Files.exists(SetupApp.DEFAULT_DIR.resolve("myname"))) 59 | 60 | SetupMain.main(listOf(INSTALL, "-projectDir", "./myname/").toTypedArray()) 61 | } 62 | 63 | @Test(priority = 3) 64 | fun testAddDependency() { 65 | Assert.assertTrue(Files.exists(SetupApp.DEFAULT_DIR.resolve("myname/wurst.build"))) 66 | 67 | SetupMain.main(listOf(INSTALL, "https://github.com/Frotty/Frentity", "-projectDir", "./myname/").toTypedArray()) 68 | 69 | val buildfile = String(Files.readAllBytes(SetupApp.DEFAULT_DIR.resolve("./myname/wurst.build"))) 70 | Assert.assertTrue(buildfile.contains("https://github.com/Frotty/Frentity")) 71 | } 72 | 73 | @Test(priority = 3) 74 | fun testAddDependencyBranched() { 75 | Assert.assertTrue(Files.exists(SetupApp.DEFAULT_DIR.resolve("myname/wurst.build"))) 76 | 77 | SetupMain.main(listOf(INSTALL, "https://github.com/Frotty/wurst-item-recipes:main", "-projectDir", "./myname/").toTypedArray()) 78 | 79 | val buildfile = String(Files.readAllBytes(SetupApp.DEFAULT_DIR.resolve("./myname/wurst.build"))) 80 | Assert.assertTrue(buildfile.contains("https://github.com/Frotty/wurst-item-recipes:main")) 81 | } 82 | 83 | 84 | @Test(priority = 3) 85 | fun testProjectTest() { 86 | 87 | val testproject = SetupApp.DEFAULT_DIR.resolve("testproject") 88 | DependencyManager.cloneRepo("https://github.com/wurstscript/WurstStdlib2.git", testproject) 89 | Assert.assertTrue(Files.exists(testproject.resolve("wurst.build"))) 90 | 91 | SetupMain.main(listOf(INSTALL, "-projectDir", "./testproject/").toTypedArray()) 92 | 93 | val setupMain = SetupMain() 94 | setupMain.projectRoot = testproject 95 | setupMain.doMain(arrayOf(TEST)) 96 | } 97 | 98 | @Test(priority = 3) 99 | fun testBranchPull() { 100 | val testproject = SetupApp.DEFAULT_DIR.resolve("ptrtestproject") 101 | DependencyManager.cloneRepo("https://github.com/wurstscript/WurstStdlib2:ptr", testproject) 102 | 103 | Assert.assertTrue(Files.exists(testproject)) 104 | FileRepository(testproject.resolve(".git").toFile()).use { repository -> 105 | Assert.assertEquals(repository.branch, "ptr") 106 | 107 | } 108 | } 109 | 110 | @Test(priority = 3) 111 | fun testProjectBuild() { 112 | 113 | val testproject = SetupApp.DEFAULT_DIR.resolve("buildproject") 114 | DependencyManager.cloneRepo("https://github.com/Frotty/ConflagrationSpell.git", testproject) 115 | Assert.assertTrue(Files.exists(testproject.resolve("wurst.build"))) 116 | 117 | SetupMain.main(listOf(INSTALL, "-projectDir", "./buildproject/").toTypedArray()) 118 | 119 | val setupMain = SetupMain() 120 | setupMain.projectRoot = testproject 121 | setupMain.doMain(arrayOf(BUILD, "ExampleMap.w3x")) 122 | } 123 | 124 | 125 | @Test(priority = 4) 126 | fun testInvalid() { 127 | val status = SystemLambda.catchSystemExit { 128 | SetupMain.main(listOf("-someInvalidCommand").toTypedArray()) 129 | } 130 | Assert.assertEquals(status, 1) 131 | } 132 | 133 | @Test(priority = 5) 134 | fun testInvalidInstall() { 135 | val invalid = SetupApp.DEFAULT_DIR.resolve("invalidbuild") 136 | DependencyManager.cloneRepo("https://github.com/Frotty/ConflagrationSpell.git", invalid) 137 | Assert.assertTrue(Files.exists(invalid.resolve("wurst.build"))) 138 | 139 | val status = SystemLambda.catchSystemExit { 140 | SetupMain.main(listOf(INSTALL, "someInvalid", "-projectDir", "./invalidbuild/").toTypedArray()) 141 | } 142 | Assert.assertEquals(status, 1) 143 | 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/test/kotlin/ConnectivityTests.kt: -------------------------------------------------------------------------------- 1 | import net.ConnectionManager 2 | import net.NetStatus 3 | import org.testng.annotations.Test 4 | 5 | class ConnectivityTests { 6 | 7 | @Test fun testConnectionManager() { 8 | assert(ConnectionManager.checkConnectivity("http://google.com") == NetStatus.SERVER_CONTACT) 9 | 10 | assert(ConnectionManager.checkWurstBuild() == NetStatus.ONLINE) 11 | 12 | assert(ConnectionManager.getLatestCompilerBuild() > 1000) 13 | 14 | assert(ConnectionManager.getLatestSetupBuild() > 50) 15 | } 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/test/kotlin/InstallationTests.kt: -------------------------------------------------------------------------------- 1 | import global.CLIParser 2 | import global.InstallationManager 3 | import org.testng.annotations.Test 4 | 5 | class InstallationTests { 6 | @Test(priority = 8) 7 | fun testVersionPattern() { 8 | assert(InstallationManager.isJenkinsBuilt("1.7.0.0-jenkins-Wurst-531")) 9 | 10 | } 11 | 12 | @Test(priority = 8) 13 | fun testInstall() { 14 | CLIParser.getVersionFomJar() 15 | 16 | assert(InstallationManager.currentCompilerVersion > 1000) 17 | } 18 | } 19 | 20 | --------------------------------------------------------------------------------