From 4961a5bf80dce6a6e46b45c4be46ae58fbc1ee14 Mon Sep 17 00:00:00 2001 From: Greg Gibeling Date: Wed, 10 Apr 2024 15:54:54 -0700 Subject: [PATCH] LoggingRunner & CommnadLogger simplify debug/trace logging of commands being run --- .../converter/dumb/DumbCommandConverter.java | 43 ++++++---- .../gearbox/command/log/CommandLogger.java | 44 +++++++++++ .../gearbox/command/log/LoggingRunner.java | 79 +++++++++++++++++++ 3 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 gb-command/src/main/java/com/g2forge/gearbox/command/log/CommandLogger.java create mode 100644 gb-command/src/main/java/com/g2forge/gearbox/command/log/LoggingRunner.java diff --git a/gb-command/src/main/java/com/g2forge/gearbox/command/converter/dumb/DumbCommandConverter.java b/gb-command/src/main/java/com/g2forge/gearbox/command/converter/dumb/DumbCommandConverter.java index 6cc09b4..e3db218 100644 --- a/gb-command/src/main/java/com/g2forge/gearbox/command/converter/dumb/DumbCommandConverter.java +++ b/gb-command/src/main/java/com/g2forge/gearbox/command/converter/dumb/DumbCommandConverter.java @@ -13,6 +13,7 @@ import com.g2forge.alexandria.annotations.note.NoteType; import com.g2forge.alexandria.command.invocation.CommandInvocation; import com.g2forge.alexandria.command.invocation.environment.SystemEnvironment; +import com.g2forge.alexandria.command.invocation.environment.modified.IEnvironmentModifier; import com.g2forge.alexandria.command.invocation.environment.modified.ModifiedEnvironment; import com.g2forge.alexandria.command.invocation.format.ICommandFormat; import com.g2forge.alexandria.command.stdio.StandardIO; @@ -48,6 +49,7 @@ import lombok.Builder; import lombok.Data; +import lombok.RequiredArgsConstructor; public class DumbCommandConverter implements ICommandConverterR_, ISingleton { @Data @@ -62,6 +64,31 @@ protected static class ArgumentContext { protected static final DumbCommandConverter instance = new DumbCommandConverter(); + @Data + @Builder(toBuilder = true) + @RequiredArgsConstructor + protected static class EnvPathModifier implements IEnvironmentModifier { + protected final EnvPath.Usage usage; + + protected final Path value; + + @Override + public String modify(String parent) { + final String pathSeparator = HPlatform.getPlatform().getPathSpec().getPathSeparator(); + switch (getUsage()) { + case AddFirst: + return getValue().toString() + pathSeparator + parent; + case Replace: + return getValue().toString(); + case AddLast: + return parent + pathSeparator + getValue().toString(); + default: + throw new EnumException(EnvPath.Usage.class, getUsage()); + } + } + + } + protected static final IConsumer2 ARGUMENT_BUILDER = new TypeSwitch2.ConsumerBuilder().with(builder -> { builder.add(ArgumentContext.class, String[].class, (c, v) -> { final ISubject metadata = c.getArgument().getMetadata(); @@ -84,25 +111,13 @@ protected static class ArgumentContext { final Working working = c.getArgument().getMetadata().get(Working.class); if (working != null) { - c.getCommand().working(v); + if (v != null) c.getCommand().working(v); isNormal = false; } final EnvPath envPath = c.getArgument().getMetadata().get(EnvPath.class); if (envPath != null) { - c.getEnvironment().modifier(HPlatform.PATH, parent -> { - final String pathSeparator = HPlatform.getPlatform().getPathSpec().getPathSeparator(); - switch (envPath.usage()) { - case AddFirst: - return v.toString() + pathSeparator + parent; - case Replace: - return v.toString(); - case AddLast: - return parent + pathSeparator + v.toString(); - default: - throw new EnumException(EnvPath.Usage.class, envPath.usage()); - } - }); + c.getEnvironment().modifier(HPlatform.PATH, new EnvPathModifier(envPath.usage(), v)); isNormal = false; } diff --git a/gb-command/src/main/java/com/g2forge/gearbox/command/log/CommandLogger.java b/gb-command/src/main/java/com/g2forge/gearbox/command/log/CommandLogger.java new file mode 100644 index 0000000..991eaa4 --- /dev/null +++ b/gb-command/src/main/java/com/g2forge/gearbox/command/log/CommandLogger.java @@ -0,0 +1,44 @@ +package com.g2forge.gearbox.command.log; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.g2forge.alexandria.java.close.ICloseable; +import com.g2forge.alexandria.java.function.IConsumer1; +import com.g2forge.alexandria.java.io.RuntimeIOException; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class CommandLogger implements ICloseable, IConsumer1 { + protected final Path logFile; + + protected final IConsumer1 tee; + + protected PrintStream stream = null; + + @Override + public void accept(String t) { + if (stream == null) try { + Files.createDirectories(logFile.getParent()); + stream = new PrintStream(Files.newOutputStream(logFile)); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + stream.println(t); + if (tee != null) tee.accept(t); + } + + @Override + public void close() { + if (stream == null) { + try { + Files.deleteIfExists(logFile); + } catch (IOException e) { + throw new RuntimeIOException(e); + } + } else stream.close(); + } +} \ No newline at end of file diff --git a/gb-command/src/main/java/com/g2forge/gearbox/command/log/LoggingRunner.java b/gb-command/src/main/java/com/g2forge/gearbox/command/log/LoggingRunner.java new file mode 100644 index 0000000..42e1b63 --- /dev/null +++ b/gb-command/src/main/java/com/g2forge/gearbox/command/log/LoggingRunner.java @@ -0,0 +1,79 @@ +package com.g2forge.gearbox.command.log; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import com.g2forge.alexandria.command.invocation.CommandInvocation; +import com.g2forge.alexandria.command.invocation.environment.IEnvironment; +import com.g2forge.alexandria.command.invocation.environment.MapEnvironment; +import com.g2forge.alexandria.command.invocation.environment.SystemEnvironment; +import com.g2forge.alexandria.command.invocation.environment.modified.IEnvironmentModifier; +import com.g2forge.alexandria.command.invocation.environment.modified.ModifiedEnvironment; +import com.g2forge.alexandria.java.core.error.NotYetImplementedError; +import com.g2forge.alexandria.java.core.helpers.HCollection; +import com.g2forge.alexandria.java.function.IConsumer1; +import com.g2forge.gearbox.command.process.IProcess; +import com.g2forge.gearbox.command.process.IRunner; +import com.g2forge.gearbox.command.process.redirect.IRedirect; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter(AccessLevel.PROTECTED) +@RequiredArgsConstructor +public class LoggingRunner implements IRunner { + protected static String toString(IEnvironment environment, String prefix) { + final StringBuilder retVal = new StringBuilder(); + if (environment instanceof MapEnvironment) { + for (Map.Entry entry : ((MapEnvironment) environment).getVariables().entrySet()) { + retVal.append(prefix).append(entry.getKey()).append('=').append(entry.getValue()).append("\n"); + } + } else if (environment instanceof ModifiedEnvironment) { + final ModifiedEnvironment modified = ((ModifiedEnvironment) environment); + + final String base = toString(modified.getBase(), prefix + "\t"); + final boolean multiLineBase = base.contains("\n"); + final int modifierCount = modified.getModifiers().size(); + + if (multiLineBase || modifierCount > 1) { + retVal.append(prefix).append("modified:").append(multiLineBase ? '\n' : ' ').append(multiLineBase ? base : base.strip()).append('\n'); + retVal.append(prefix).append("modifiers:\n"); + for (Map.Entry entry : modified.getModifiers().entrySet()) { + retVal.append(prefix).append('\t').append(entry.getKey()).append(" <- ").append(entry.getValue()).append('\n'); + } + } else if (modifierCount < 1) retVal.append(base.strip()); + else { + final Entry entry = HCollection.getOne(modified.getModifiers().entrySet()); + retVal.append(prefix).append("modified ").append(base.strip()).append(" with ").append(entry.getKey()).append(" <- ").append(entry.getValue()); + } + } else if (environment instanceof SystemEnvironment) { + retVal.append(prefix).append("system environment\n"); + } else throw new NotYetImplementedError("Environments of type " + environment.getClass() + " are not yet supported!"); + + return retVal.toString().stripTrailing(); + } + + protected final IConsumer1 log; + + protected final IRunner runner; + + @Override + public IProcess apply(CommandInvocation commandInvocation) { + final IConsumer1 log = getLog(); + log.accept("Running: " + commandInvocation.getArguments().stream().collect(Collectors.joining(" "))); + if (commandInvocation.getWorking() != null) log.accept("\tin " + commandInvocation.getWorking()); + final IEnvironment environment = commandInvocation.getEnvironment(); + if ((environment != null) && !(commandInvocation.getEnvironment() instanceof SystemEnvironment)) { + final String string = toString(environment, "\t\t"); + if (string.contains("\n")) { + log.accept("\tenvironment:"); + for (String line : string.split("\n")) { + log.accept(line); + } + } else log.accept("\tenvironment: " + string.substring(2 /* Remove the tabs */)); + } + return runner.apply(commandInvocation); + } +} \ No newline at end of file