diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2844e88 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Project exclude paths +.gradle/ +build/ +.idea \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..04e7e9e --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Environment Variables Gui \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml new file mode 100644 index 0000000..fabd64c --- /dev/null +++ b/.idea/dbnavigator.xml @@ -0,0 +1,466 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/google-java-format.xml b/.idea/google-java-format.xml new file mode 100644 index 0000000..2aa056d --- /dev/null +++ b/.idea/google-java-format.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..77addae --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..6560a98 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..fdc392f --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries-with-intellij-classes.xml b/.idea/libraries-with-intellij-classes.xml new file mode 100644 index 0000000..9fa3156 --- /dev/null +++ b/.idea/libraries-with-intellij-classes.xml @@ -0,0 +1,65 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d4e94e6 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..aae95a3 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8da0b45 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Environment Variables Gui +Set environment variables with GUI + +Application work on UNIX / Linux / macOS + +Create application with this command `./gradlew jpackage` +build are in `build/jpackage` + +[Need JDK 17](https://www.oracle.com/java/technologies/downloads/) + +# Screenshot +![Alt text](screenshot_1.png "Main menu") + +![Alt text](screenshot_2.png "Environment variable editor") \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c947ac1 --- /dev/null +++ b/build.gradle @@ -0,0 +1,57 @@ +plugins { + id 'java' + id 'application' + id 'org.openjfx.javafxplugin' version '0.0.9' + id "org.beryx.jlink" version "2.22.0" +} + +sourceCompatibility = 17 +targetCompatibility = 17 + +mainClassName = 'fr.fenrur.Main' + +group 'fr.fenrur' +version '1.0' + +jar { + manifest { + attributes 'Main-Class': 'fr.fenrur.Main' + } +} + +repositories { + mavenCentral() +} + +dependencies { +// testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' +// testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' +} + +test { +// useJUnitPlatform() +} + +javafx { + version = "17" + modules = [ 'javafx.controls', 'javafx.fxml' ] +} + +jlink { + application.mainModule = "fr.fenrur" + options = ['--bind-services', '--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages'] + + launcher { + name = 'Environment Variables' + noConsole = true + } + jpackage { + def currentOs = org.gradle.internal.os.OperatingSystem.current() + def imgType = currentOs.macOsX ? 'icns' : 'png' + icon = "icons/favicon.$imgType" + installerOptions += [ + '--vendor', 'Fenrur', + '--app-version', version + ] + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e750102 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/icons/favicon.icns b/icons/favicon.icns new file mode 100644 index 0000000..0109825 Binary files /dev/null and b/icons/favicon.icns differ diff --git a/icons/favicon.png b/icons/favicon.png new file mode 100644 index 0000000..01a16cc Binary files /dev/null and b/icons/favicon.png differ diff --git a/screenshot_1.png b/screenshot_1.png new file mode 100644 index 0000000..4305737 Binary files /dev/null and b/screenshot_1.png differ diff --git a/screenshot_2.png b/screenshot_2.png new file mode 100644 index 0000000..8254e2b Binary files /dev/null and b/screenshot_2.png differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..a0767cc --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'Environment Variables Gui' + diff --git a/src/main/java/fr/fenrur/Controller.java b/src/main/java/fr/fenrur/Controller.java new file mode 100644 index 0000000..dc2158f --- /dev/null +++ b/src/main/java/fr/fenrur/Controller.java @@ -0,0 +1,141 @@ +package fr.fenrur; + +import javafx.application.Platform; +import javafx.beans.property.SimpleStringProperty; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.RowConstraints; +import javafx.stage.Stage; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.Set; + +public class Controller implements Initializable { + + public static int ROW = 50; + + private final Set sourcedFilePaths; + + public GridPane pane; + public TableView tableView; + public Label label; + public Button newButton; + public Button modifyButton; + public Button deleteButton; + public TableColumn variableColumn; + public TableColumn valueColumn; + public TableColumn fileColumn; + public RowConstraints row1; + public RowConstraints row2; + public RowConstraints row3; + + public Controller(Set sourcedFilePaths) { + this.sourcedFilePaths = sourcedFilePaths; + } + + public static void selectItem0(TableView tableView) { + if (!tableView.getItems().isEmpty()) { + tableView.getSelectionModel().select(0); + } + } + + public static void setTableColumnFactory(TableColumn columnVariable, TableColumn columnValue, TableColumn columnFile) { + columnVariable.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().key())); + columnValue.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().toValueLine())); + columnFile.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().sourceFilePath().toAbsolutePath().toString())); + } + + public static void addSelectItemListener(TableView tableView, Button deleteButton, Button modifyButton, Button newButton) { + tableView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + final boolean disable = !Files.isWritable(newValue.sourceFilePath()); + deleteButton.setDisable(disable); + modifyButton.setDisable(disable); + newButton.setDisable(!Files.isWritable(tableView.getItems().get(0).sourceFilePath())); + System.out.println(newValue); + }); + } + + public static void addResizeListener(TableView tableView, TableColumn variableColumn, TableColumn valueColumn, TableColumn fileColumn) { + Platform.runLater(() -> tableView.widthProperty().addListener((observableValue, oldSceneWidth, newSceneWidth) -> { + final double factor = newSceneWidth.doubleValue() / oldSceneWidth.doubleValue(); + final double newSizeWidthKeyColumn = variableColumn.getWidth() * factor; + final double newSizeWidthValueColumn = valueColumn.getWidth() * factor; + variableColumn.setPrefWidth(newSizeWidthKeyColumn); + valueColumn.setPrefWidth(newSizeWidthValueColumn); + fileColumn.setPrefWidth((newSceneWidth.doubleValue() - (newSizeWidthKeyColumn + newSizeWidthValueColumn)) - 5); + })); + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + addResizeListener(tableView, variableColumn, valueColumn, fileColumn); + tableView.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY); + addSelectItemListener(tableView, deleteButton, modifyButton, newButton); + sourcedFilePaths.forEach(path -> tableView.getItems().addAll(EnvironmentVariableManager.findVariablesFrom(path))); + setTableColumnFactory(variableColumn, valueColumn, fileColumn); + selectItem0(tableView); + pane.heightProperty().addListener((observable, oldValue, newValue) -> resizeRows()); + label.setText(label.getText() + " " + (System.getenv("SUDO_USER") != null ? System.getenv("SUDO_USER") : System.getenv("USER"))); + } + + public void onDeleteButtonClicked(MouseEvent mouseEvent) { + final EnvironmentVariable environmentVariable = tableView.getSelectionModel().getSelectedItem(); + final boolean isDeleted = EnvironmentVariableManager.deleteVariable(environmentVariable); + if (isDeleted) { + tableView.getItems().remove(environmentVariable); + } + } + + public void onNewButtonClicked(MouseEvent mouseEvent) throws IOException { + final FXMLLoader loader = new FXMLLoader(Main.getResource("editorVariableController.fxml", getClass())); + + final EditorVariableController editorVariableController = new EditorVariableController( + tableView, + sourcedFilePaths, + Optional.empty()); + loader.setControllerFactory(param -> editorVariableController); + final Scene scene = new Scene(loader.load()); + final Stage stage = new Stage(); + stage.setScene(scene); + stage.setTitle("New variable"); + Main.stage.hide(); + stage.show(); + Main.setWindowSizing(stage, editorVariableController.pane); + } + + public void onModifyButtonClicked(MouseEvent mouseEvent) throws IOException { + final FXMLLoader loader = new FXMLLoader(Main.getResource("editorVariableController.fxml", getClass())); + + final EditorVariableController editorVariableController = new EditorVariableController( + tableView, + sourcedFilePaths, + Optional.ofNullable(tableView.getSelectionModel().getSelectedItem()) + ); + loader.setControllerFactory(param -> editorVariableController); + final Scene scene = new Scene(loader.load()); + final Stage stage = new Stage(); + stage.setScene(scene); + stage.setTitle("Modify variable"); + Main.stage.hide(); + stage.show(); + Main.setWindowSizing(stage, editorVariableController.pane); + } + + public void resizeRows() { + row2.setPrefHeight(pane.getHeight() - ROW - ROW); + row1.setPrefHeight(ROW); + row3.setPrefHeight(ROW); + } +} diff --git a/src/main/java/fr/fenrur/EditorVariableController.java b/src/main/java/fr/fenrur/EditorVariableController.java new file mode 100644 index 0000000..a37c569 --- /dev/null +++ b/src/main/java/fr/fenrur/EditorVariableController.java @@ -0,0 +1,233 @@ +package fr.fenrur; + +import javafx.application.Platform; +import javafx.beans.value.ObservableValueBase; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.RowConstraints; +import javafx.stage.DirectoryChooser; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +import java.io.File; +import java.net.URL; +import java.nio.file.Path; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.Set; + +public class EditorVariableController implements Initializable { + + public static int COLUMN = 145; + public static int ROW = 60; + private final TableView mainTableView; + private final Set sourcedFilePaths; + private final Optional from; + + public GridPane pane; + public Button browseDirectoryButton; + public Button browseFileButton; + public Button okButton; + public Button cancelButton; + public ChoiceBox fileChoiceBox; + public RowConstraints row1; + public RowConstraints row2; + public RowConstraints row3; + public RowConstraints row4; + public ColumnConstraints column1; + public ColumnConstraints column2; + public TableColumn tableColumn; + public TableView tableView; + public Button removeButton; + public TextField keyTextField; + public Button addButton; + + private StringBuilder selected = null; + private DirectoryChooser directoryChooser; + private FileChooser fileChooser; + + public EditorVariableController(TableView mainTableView, Set sourcedFilePaths, Optional from) { + this.mainTableView = mainTableView; + this.sourcedFilePaths = sourcedFilePaths; + this.from = from; + } + + public void onKeyPressed(KeyEvent keyEvent) { + if (keyEvent.getCode() == KeyCode.ESCAPE) { + onMouseClickedCancelButton(null); + } else if (keyEvent.getCode() == KeyCode.ENTER && !okButton.isDisable()) { + onMouseClickedOkButton(null); + } + } + + public void onMouseClickedOkButton(MouseEvent mouseEvent) { + if (verifyCharsAndShowAlertIfNotGood(keyTextField.getText())) return; + + for (StringBuilder item : tableView.getItems()) { + if (verifyCharsAndShowAlertIfNotGood(item.toString())) return; + } + + final Stage stage = (Stage) okButton.getScene().getWindow(); + stage.close(); + + if (from.isPresent()) { + final boolean deleted = EnvironmentVariableManager.deleteVariable(from.get()); + if (!deleted) { + Main.stage.show(); + } else { + mainTableView.getItems().remove(from.get()); + } + } + + final EnvironmentVariable variable = new EnvironmentVariable(fileChoiceBox.getSelectionModel().getSelectedItem(), keyTextField.getText(), tableView.getItems().stream().map(StringBuilder::toString).filter(s -> !s.isBlank()).toList()); + if (EnvironmentVariableManager.writeVariable(variable)) { + mainTableView.getItems().add(variable); + mainTableView.refresh(); + } + + Main.stage.show(); + } + + private boolean verifyCharsAndShowAlertIfNotGood(String value) { + final char[] chars = value.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '"' && (i == 0 || chars[i - 1] != '\\')) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Environment Variables Gui"); + alert.setContentText("The value: " + value + " need to escape (\") with (\\) before"); + alert.getButtonTypes().add(ButtonType.CLOSE); + alert.showAndWait(); + return true; + } + } + return false; + } + + public void onMouseClickedCancelButton(MouseEvent mouseEvent) { + Main.stage.show(); + final Stage stage = (Stage) okButton.getScene().getWindow(); + stage.close(); + } + + public void onMouseClickedBrowseDirectoryButton(MouseEvent mouseEvent) { + if (directoryChooser == null) { + directoryChooser = new DirectoryChooser(); + } + final File file = directoryChooser.showDialog(getStage()); + if (file == null) return; + tableColumn.getCellObservableValue(selected).getValue().setText(file.getAbsolutePath()); + tableView.refresh(); + } + + public void onMouseClickedBrowseFileButton(MouseEvent mouseEvent) { + if (fileChooser == null) { + fileChooser = new FileChooser(); + } + final File file = fileChooser.showOpenDialog(getStage()); + if (file == null) return; + tableColumn.getCellObservableValue(selected).getValue().setText(file.getAbsolutePath()); + tableView.refresh(); + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + keyTextField.textProperty().addListener((observable, oldValue, newValue) -> refreshDisableButtons()); + + tableColumn.setCellValueFactory(param -> new ObservableValueBase<>() { + @Override + public TextField getValue() { + final TextField textField = new TextField(param.getValue().toString()); + textField.textProperty().addListener((observable, oldValue, newValue) -> { + param.getValue() + .delete(0, param.getValue().length()) + .append(newValue); + }); + textField.focusedProperty().addListener((observable, oldValue, newValue) -> { + selected = param.getValue(); + refreshDisableButtons(); + }); + textField.onMouseClickedProperty().addListener((observable, oldValue, newValue) -> { + selected = param.getValue(); + refreshDisableButtons(); + }); + return textField; + } + }); + resizeRows(); + resizeColumns(); + pane.widthProperty().addListener((observable, oldValue, newValue) -> { + resizeColumns(); + resizeTableColumn(); + }); + pane.heightProperty().addListener((observable, oldValue, newValue) -> resizeRows()); + Platform.runLater(this::resizeTableColumn); + + fileChoiceBox.getItems().addAll(sourcedFilePaths); + fileChoiceBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> refreshDisableButtons()); + + if (from.isPresent()) { + final EnvironmentVariable environmentVariable = from.get(); + keyTextField.setText(environmentVariable.key()); + tableView.getItems().addAll(environmentVariable.value().stream().map(StringBuilder::new).toList()); + fileChoiceBox.setValue(environmentVariable.sourceFilePath()); + } + + refreshDisableButtons(); + } + + private void refreshDisableButtons() { + okButton.setDisable(tableView.getItems().isEmpty() || keyTextField.getText() == null || keyTextField.getText().isBlank() || selected == null || fileChoiceBox.getValue() == null); + + if (tableView.getItems().isEmpty()) { + selected = null; + } + + if (selected == null) { + browseDirectoryButton.setDisable(true); + browseFileButton.setDisable(true); + removeButton.setDisable(true); + } else { + browseDirectoryButton.setDisable(false); + browseFileButton.setDisable(false); + removeButton.setDisable(false); + } + } + + private void resizeTableColumn() { + tableColumn.setPrefWidth(tableColumn.getTableView().getWidth() - 3); + } + + private void resizeColumns() { + column1.setPrefWidth(COLUMN); + column2.setPrefWidth(pane.getWidth() - COLUMN); + } + + public void resizeRows() { + row2.setPrefHeight(pane.getHeight() - ROW - ROW - ROW); + row1.setPrefHeight(ROW); + row3.setPrefHeight(ROW); + row4.setPrefHeight(ROW); + } + + public void onMouseClickedAddButton(MouseEvent mouseEvent) { + tableView.getItems().add(new StringBuilder()); + Platform.runLater(this::refreshDisableButtons); + } + + public void onMouseClickedRemoveButton(MouseEvent mouseEvent) { + if (selected != null) { + tableView.getItems().remove(selected); + } + refreshDisableButtons(); + tableView.refresh(); + } + + public Stage getStage() { + return (Stage) okButton.getScene().getWindow(); + } +} diff --git a/src/main/java/fr/fenrur/EnvironmentVariable.java b/src/main/java/fr/fenrur/EnvironmentVariable.java new file mode 100644 index 0000000..1066d6e --- /dev/null +++ b/src/main/java/fr/fenrur/EnvironmentVariable.java @@ -0,0 +1,22 @@ +package fr.fenrur; + +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +public record EnvironmentVariable(Path sourceFilePath, String key, List value) { + + public EnvironmentVariable { + Objects.requireNonNull(sourceFilePath); + Objects.requireNonNull(key); + Objects.requireNonNull(value); + } + + public String toLineFile() { + return '"' + key + '"' + "=\"" + toValueLine() + "\""; + } + + public String toValueLine() { + return String.join(":", value); + } +} diff --git a/src/main/java/fr/fenrur/EnvironmentVariableManager.java b/src/main/java/fr/fenrur/EnvironmentVariableManager.java new file mode 100644 index 0000000..b3d18e8 --- /dev/null +++ b/src/main/java/fr/fenrur/EnvironmentVariableManager.java @@ -0,0 +1,73 @@ +package fr.fenrur; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class EnvironmentVariableManager { + + public static final List PATTERNS = List.of( + Pattern.compile("export \"(.*?)\"=\"(.*?)\""), + Pattern.compile("export '(.*?)'='(.*?)'"), + Pattern.compile("export (.*?)=\"(.*?)\""), + Pattern.compile("export (.*?)='(.*?)'"), + Pattern.compile("export '(.*?)'=(.*?)"), + Pattern.compile("export \"(.*?)\"=(.*?)"), + Pattern.compile("export \"(.*?)\"='(.*?)'"), + Pattern.compile("export '(.*?)'=\"(.*?)\""), + Pattern.compile("export (.*?)=(.*?)") + ); + + public static void main(String[] args) { + } + + public static Optional isExportLine(String line) { + return PATTERNS.stream() + .map(pattern -> pattern.matcher(line)) + .filter(Matcher::matches) + .findFirst(); + } + + public static Set findVariablesFrom(Path path) { + if (!Files.isRegularFile(path) || !Files.isReadable(path)) return Set.of(); + try { + return Files.lines(path) + .map(EnvironmentVariableManager::isExportLine) + .flatMap(Optional::stream) + .map(matcher -> new EnvironmentVariable(path, matcher.group(1), List.of(matcher.group(2).split(":")))) + .collect(Collectors.toSet()); + } catch (IOException e) { + e.printStackTrace(); + } + return Set.of(); + } + + public static boolean deleteVariable(EnvironmentVariable variable) { + try { + final List linesFrom = Files.readAllLines(variable.sourceFilePath()); + linesFrom.removeIf(line -> line.startsWith("export " + variable.toLineFile())); + Files.write(variable.sourceFilePath(), linesFrom); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + public static boolean writeVariable(EnvironmentVariable variable) { + try { + Files.writeString(variable.sourceFilePath(), "\nexport " + variable.toLineFile(), StandardOpenOption.APPEND); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } +} diff --git a/src/main/java/fr/fenrur/Main.java b/src/main/java/fr/fenrur/Main.java new file mode 100644 index 0000000..0e321e8 --- /dev/null +++ b/src/main/java/fr/fenrur/Main.java @@ -0,0 +1,123 @@ +package fr.fenrur; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Main extends Application { + + private final static List DEFAULT_SOURCED_FILE = List.of( + "~/.zshrc", + "~/.zprofile", + "~/.profile", + "~/.bashrc", + "~/.bash_profile", + "~/.zsh_profile", + "~/.config/fish/config.fish", + "/etc/profile", + "/etc/bash.bashrc", + "~/.bash_logout" + ); + + private final static List PATTERNS = List.of( + Pattern.compile("source \"(.*?)\""), + Pattern.compile("source '(.*?)'"), + Pattern.compile("source (.*?)") + ); + + public static boolean runByIntellij = false; + public static Stage stage; + + public static void main(String[] args) { + runByIntellij = Arrays.stream(args).anyMatch(arg -> arg.equalsIgnoreCase("--runByIntellij")); + Application.launch(args); + } + + public static Optional isSourceLine(String line) { + return PATTERNS.stream() + .map(pattern -> pattern.matcher(line)) + .filter(Matcher::matches) + .findFirst(); + } + + public static URL getResource(String resource, Class clazz) { + if (runByIntellij) { + try { + return Paths.get("src/main/resources/", clazz.getPackageName().replace(".", "/"), resource).toUri().toURL(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } else { + return clazz.getResource(resource); + } + } + + private static void appendAllSourcedFiles(Set result, List toCheck) { + for (Path check : toCheck) { + if (Files.isRegularFile(check) && Files.isReadable(check)) { + result.add(check); + try { + Files.lines(check) + .map(Main::isSourceLine) + .flatMap(Optional::stream) + .map(matcher -> Path.of(matcher.group(1))) + .forEach(path -> appendAllSourcedFiles(result, List.of(path))); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void start(Stage primaryStage) throws IOException { + if (System.getProperty("os.name").toLowerCase().contains("win")) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Environment Variables Gui"); + alert.setContentText("App can't execute on Windows"); + alert.getButtonTypes().add(ButtonType.CLOSE); + alert.showAndWait(); + return; + } + + Set sourcedFilePaths = new HashSet<>(); + List defaultSourceFilesPaths = DEFAULT_SOURCED_FILE.stream() + .map(filePath -> filePath.replace("~", System.getenv("HOME"))) + .map(Path::of) + .toList(); + + appendAllSourcedFiles(sourcedFilePaths, defaultSourceFilesPaths); + + stage = primaryStage; + primaryStage.setTitle("Environment Variables"); + FXMLLoader loader = new FXMLLoader(getResource("controller.fxml", getClass())); + final Controller controller = new Controller(Collections.unmodifiableSet(sourcedFilePaths)); + loader.setControllerFactory(param -> controller); + primaryStage.setScene(new Scene(loader.load())); + + primaryStage.show(); + setWindowSizing(stage, controller.pane); + } + + public static void setWindowSizing(Stage stage, Pane pane) { + stage.setHeight(pane.getPrefHeight()); + stage.setWidth(pane.getPrefWidth()); + stage.setMinHeight(pane.getMinHeight()); + stage.setMinWidth(pane.getMinWidth()); + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..396b095 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,7 @@ +module fr.fenrur { + exports fr.fenrur; + requires javafx.base; + requires javafx.controls; + requires javafx.fxml; + requires javafx.graphics; +} \ No newline at end of file diff --git a/src/main/resources/fr/fenrur/controller.fxml b/src/main/resources/fr/fenrur/controller.fxml new file mode 100644 index 0000000..715ab6b --- /dev/null +++ b/src/main/resources/fr/fenrur/controller.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +