Compare commits

..

No commits in common. "1a41afc408c408ec08acb76a2921da02914884ce" and "e31a82f1c5ee515cee6c3bb1f17a2b66f8eac297" have entirely different histories.

13 changed files with 170 additions and 150 deletions

View file

@ -1,18 +1,6 @@
# waituntil
A basic tool for delaying work in the terminal to a specific time. This software was originally
written in Kotlin and then recently ported to Java.
Usage:
```sh
java -jar ./waituntil.jar 10:30 ; reboot # Replace reboot with your command
```
The example above would make the program wait until half past ten AM before exiting and allowing
the next command to run. The timestamp can be passed in the formats `HH:MM` or `HH:MM:SS` and must
be in the 24-hour system. Passing dates is not supported but entering a time that has already passed
will make the software wait until that time on the following day.
This simple tool doubles as a programming exercise for me, as I attempt to learn Kotlin from my perspective as a Java developer and a (hopefully) useful tool for personal use. I will expand this README with details as the project evolves.
## Copyright / Licensing

View file

@ -1,4 +1,9 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
kotlin("jvm") version "latest.release"
// Apply the application plugin to add support for building a CLI application in Java.
application
@ -14,22 +19,44 @@ repositories {
mavenCentral()
}
dependencies {
// Align versions of all Kotlin components
implementation(platform(kotlin("bom", "latest.release")))
// Use the Kotlin standard library.
implementation(kotlin("stdlib", "latest.release"))
// Use the Kotlin test library.
testImplementation(kotlin("test", "latest.release"))
// Use the Kotlin JUnit integration.
testImplementation(kotlin("test-junit", "latest.release"))
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.test {
useJUnitPlatform()
}
tasks.jar {
manifest {
attributes(
"Implementation-Title" to "de.jotoho.waituntil",
"Implementation-Title" to "waituntil",
"Implementation-Version" to "${project.version}",
"Main-Class" to "de.jotoho.waituntil.Main"
"Main-Class" to "de.jotoho.waituntil.StartKt"
)
}
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "17"
}
application {
// Define the main class for the application.
mainClass.set("de.jotoho.waituntil.Main")
mainClass.set("de.jotoho.waituntil.StartKt")
}

5
compile.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
# shellcheck disable=SC2046
# Word splitting in find results is intentional!
kotlinc $(find src/main -type f -iname '*.kt') -jvm-target 17 -include-runtime -d waituntil.jar

View file

@ -1 +1,2 @@
org.gradle.daemon=false
kotlin.code.style=official

3
run.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
java -jar waituntil.jar $*

View file

@ -7,4 +7,4 @@
* in the user manual at https://docs.gradle.org/7.2/userguide/multi_project_builds.html
*/
rootProject.name = "de.jotoho.waituntil"
rootProject.name = "waituntil"

View file

@ -1,10 +0,0 @@
package de.jotoho.waituntil;
import java.util.Locale;
public record GlobalConf() {
public static final String langGerman = "de";
public static final String applicationOutputLanguage = (Locale.getDefault().getLanguage().equals(Locale.GERMAN.getLanguage()))
? Locale.GERMAN.getLanguage()
: Locale.ENGLISH.getLanguage();
}

View file

@ -1,51 +0,0 @@
package de.jotoho.waituntil;
import java.util.*;
import static de.jotoho.waituntil.GlobalConf.applicationOutputLanguage;
// This file contains the main function and other utility function necessary for interpreting the terminal arguments.
// See README.md and LICENSE.md for license information
// Author: Jonas Tobias Hopusch (@jotoho)
public final class Main {
public static void main(final String[] args) {
final var optionDictionary = Map.of("-h", "--help", "-v", "--version");
final var options = new HashSet<String>();
final var words = new HashSet<String>();
for (final String arg : args) {
if (arg.startsWith("--")) {
options.add(arg.substring(2));
} else if (arg.startsWith("-")) {
if (optionDictionary.containsKey(arg))
options.add(optionDictionary.get(arg).substring(2));
else
System.err.println("Short-hand '$arg' does not exist. Ignoring!");
} else {
words.add(arg);
}
}
if (options.contains("help")) {
switch (applicationOutputLanguage) {
case GlobalConf.langGerman -> System.out.println("Hilfe kommt noch. (Nicht implementiert)");
default -> System.out.println("Help is yet to come. (Not implemented)");
}
} else if (options.contains("version")) {
final var thisPackage = Main.class.getPackage();
final var appVersion = thisPackage.getImplementationVersion() != null ? thisPackage.getImplementationVersion() :"UNKNOWN";
System.out.println("de.jotoho.waituntil version $appVersion");
} else if (words.size() == 1) {
final var target = TimeCalculator.calculateAndAnnounceTargetTime(words.iterator().next());
Sleep.waitUntilTimeStamp(target);
} else {
switch (applicationOutputLanguage) {
case GlobalConf.langGerman -> System.err.println("FATAL: Es wurde exact ein nicht-flag Argument erwartet. (" + words.size() + " erhalten)");
default -> System.err.println("FATAL: Expected one non-flag argument. (Got " + words.size() + ")");
}
System.exit(1);
}
}
}

View file

@ -1,32 +0,0 @@
package de.jotoho.waituntil;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.lang.Math;
public final class Sleep {
public static void waitUntilTimeStamp(ZonedDateTime timestamp) {
try {
Thread.sleep(Math.max(0, Instant.now().until(timestamp, ChronoUnit.MILLIS)));
} catch (final InterruptedException ignored) {
}
final String formattedTimeStamp =
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
.withZone(TimeZone.getDefault().toZoneId())
.format(Instant.now());
final String msg = switch (GlobalConf.applicationOutputLanguage) {
case GlobalConf.langGerman -> "Erfolgreich bis %s gewartet!";
default -> "Successfully waited until %s";
};
final String msgWithData = msg.formatted(formattedTimeStamp);
System.err.println(msgWithData);
}
}

View file

@ -1,40 +0,0 @@
package de.jotoho.waituntil;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.*;
public final class TimeCalculator {
public static ZonedDateTime calculateAndAnnounceTargetTime(final String userTimeInputRaw) {
final var userTimeInputRelative = LocalTime.parse(userTimeInputRaw);
final var userTimeInputAbsolute =
ZonedDateTime.of(
LocalDate.now(),
userTimeInputRelative,
TimeZone.getDefault().toZoneId()
);
final var userTimeInputFinal = (Instant.now().isBefore(userTimeInputAbsolute.toInstant()))
? userTimeInputAbsolute
: userTimeInputAbsolute.plusDays(1);
final var formattedTimeStamp =
userTimeInputFinal.format(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
);
final String msg = switch (GlobalConf.applicationOutputLanguage) {
case GlobalConf.langGerman -> "Dieses Program wird bis zum %s warten."
.formatted(formattedTimeStamp);
default -> "WaitUntil will suspend until %s".formatted(formattedTimeStamp);
};
System.out.println(msg);
return userTimeInputFinal;
}
}

View file

@ -0,0 +1,64 @@
package de.jotoho.waituntil
import java.util.*
import kotlin.system.exitProcess
// This file contains the main function and other utility function necessary for interpreting the terminal arguments.
// See README.md and LICENSE.md for license information
// Author: Jonas Tobias Hopusch (@jotoho)
val langGerman: String = Locale.GERMAN.language
val applicationOutputLanguage: String = if (Locale.getDefault().language.equals(Locale.GERMAN.language))
Locale.GERMAN.language
else Locale.ENGLISH.language
// For accessing package information
object DummyClass
fun main(args: Array<String>) {
val optionDictionary = mapOf(Pair("-h", "--help"), Pair("-v", "--version"))
val options = HashSet<String>()
val words = HashSet<String>()
for (arg in args) {
if (arg.startsWith("--")) {
options.add(arg.substring(startIndex = 2))
} else if (arg.startsWith('-')) {
if (optionDictionary.containsKey(arg))
options.add(optionDictionary[arg]!!.substring(startIndex = 2))
else
System.err.println("Short-hand '$arg' does not exist. Ignoring!")
} else
words.add(arg)
}
if (options.contains("help")) {
when (applicationOutputLanguage) {
langGerman -> println("Hilfe kommt noch. (Nicht implementiert)")
else -> {
println("Help is yet to come. (Not implemented)")
}
}
} else if (options.contains("version")) {
when (applicationOutputLanguage) {
langGerman -> {
val thisPackage = DummyClass.javaClass.`package`
val appVersion = thisPackage.implementationVersion ?: "UNKNOWN"
println("waituntil version $appVersion")
}
}
} else if (words.size == 1) {
val target = calculateAndAnnounceTargetTime(words.iterator().next())
waitUntilTimeStamp(target)
} else {
when (applicationOutputLanguage) {
langGerman -> System.err.println("FATAL: Es wurde exact ein nicht-flag Argument erwartet. (${words.size} erhalten)")
else -> {
System.err.println("FATAL: Expected one non-flag argument. (Got ${words.size})")
}
}
exitProcess(1)
}
}

View file

@ -0,0 +1,24 @@
package de.jotoho.waituntil
import java.time.Instant
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.time.temporal.ChronoUnit
import java.util.*
fun waitUntilTimeStamp(timestamp: ZonedDateTime) {
Thread.sleep(Instant.now().until(timestamp, ChronoUnit.MILLIS).coerceAtLeast(0))
val formattedTimeStamp: String =
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
.withZone(TimeZone.getDefault().toZoneId())
.format(Instant.now())
when (applicationOutputLanguage) {
langGerman -> System.err.println("Erfolgreich bis $formattedTimeStamp gewartet!")
else -> {
System.err.println("Successfully waited until $formattedTimeStamp")
}
}
}

View file

@ -0,0 +1,41 @@
package de.jotoho.waituntil
import java.time.Instant
import java.time.LocalDate
import java.time.LocalTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.*
fun calculateAndAnnounceTargetTime(userTimeInputRaw: String): ZonedDateTime {
val userTimeInputRelative = LocalTime.parse(userTimeInputRaw)
val userTimeInputAbsolute =
ZonedDateTime.of(
LocalDate.now(),
userTimeInputRelative,
TimeZone.getDefault().toZoneId()
)
val userTimeInputFinal =
if (Instant.now().isBefore(userTimeInputAbsolute.toInstant()))
userTimeInputAbsolute
else userTimeInputAbsolute.plusDays(1)
val formattedTimeStamp =
userTimeInputFinal.format(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
)
when (applicationOutputLanguage) {
langGerman ->
System.err.println(
"Dieses Program wird bis zum $formattedTimeStamp warten."
)
else -> {
println("WaitUntil will suspend until $formattedTimeStamp")
}
}
return userTimeInputFinal
}