Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 8 additions & 20 deletions src/main/java/me/itzg/helpers/sync/Sync.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package me.itzg.helpers.sync;

import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.Callable;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import me.itzg.helpers.McImageHelper;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.Callable;

@Command(name = "sync",
description = "Synchronizes the contents of one directory to another.")
@ToString
Expand All @@ -34,24 +32,14 @@ public class Sync implements Callable<Integer> {
@ToString.Exclude
List<String> extra;

@Parameters(index = "0", description = "source directory")
Path src;

@Parameters(index = "1", description = "destination directory")
Path dest;
@Parameters(arity = "2..*", description = "src... dest directories",
split = McImageHelper.SPLIT_COMMA_NL, splitSynopsisLabel = McImageHelper.SPLIT_SYNOPSIS_COMMA_NL)
List<Path> srcDest;

@Override
public Integer call() throws Exception {
log.debug("Configured with {}", this);

try {
Files.walkFileTree(src, new SynchronizingFileVisitor(src, dest, skipNewerInDestination, new CopyingFileProcessor()));
} catch (IOException e) {
log.error("Failed to sync {} into {} : {}", src, dest, e.getMessage());
log.debug("Details", e);
return 1;
}

return 0;
return SynchronizingFileVisitor.walkDirectories(srcDest, skipNewerInDestination, new CopyingFileProcessor());
}
}
40 changes: 13 additions & 27 deletions src/main/java/me/itzg/helpers/sync/SyncAndInterpolate.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package me.itzg.helpers.sync;

import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.Callable;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import me.itzg.helpers.McImageHelper;
import me.itzg.helpers.env.Interpolator;
import me.itzg.helpers.env.StandardEnvironmentVariablesProvider;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.Callable;

@Command(name = "sync-and-interpolate",
description = "Synchronizes the contents of one directory to another with conditional variable interpolation.")
@ToString
Expand All @@ -31,34 +30,21 @@ public class SyncAndInterpolate implements Callable<Integer> {
@ArgGroup(multiplicity = "1", exclusive = false)
ReplaceEnvOptions replaceEnv = new ReplaceEnvOptions();

@Parameters(index = "0", description = "source directory")
Path src;

@Parameters(index = "1", description = "destination directory")
Path dest;
@Parameters(arity = "2..*", description = "src... dest directories",
split = McImageHelper.SPLIT_COMMA_NL, splitSynopsisLabel = McImageHelper.SPLIT_SYNOPSIS_COMMA_NL)
List<Path> srcDest;

@Override
public Integer call() throws Exception {
log.debug("Configured with {}", this);

try {
Files.walkFileTree(src,
new SynchronizingFileVisitor(src, dest, skipNewerInDestination,
new InterpolatingFileProcessor(
replaceEnv,
new Interpolator(new StandardEnvironmentVariablesProvider(), replaceEnv.prefix),
new CopyingFileProcessor()
)

)
);
} catch (IOException e) {
log.error("Failed to sync and interpolate {} into {} : {}", src, dest, e.getMessage());
log.debug("Details", e);
return 1;
}
final InterpolatingFileProcessor fileProcessor = new InterpolatingFileProcessor(
replaceEnv,
new Interpolator(new StandardEnvironmentVariablesProvider(), replaceEnv.prefix),
new CopyingFileProcessor()
);

return 0;
return SynchronizingFileVisitor.walkDirectories(srcDest, skipNewerInDestination, fileProcessor);
}

}
28 changes: 23 additions & 5 deletions src/main/java/me/itzg/helpers/sync/SynchronizingFileVisitor.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package me.itzg.helpers.sync;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.List;
import lombok.extern.slf4j.Slf4j;

@Slf4j
class SynchronizingFileVisitor implements FileVisitor<Path> {
Expand All @@ -24,6 +24,24 @@ public SynchronizingFileVisitor(Path src, Path dest, boolean skipNewerInDestinat
this.fileProcessor = fileProcessor;
}

static int walkDirectories(List<Path> srcDest, boolean skipNewerInDestination,
FileProcessor fileProcessor
) {
final Path dest = srcDest.getLast();

for (final Path src : srcDest.subList(0, srcDest.size() - 1)) {
try {
Files.walkFileTree(src, new SynchronizingFileVisitor(src, dest, skipNewerInDestination, fileProcessor));
} catch (IOException e) {
log.error("Failed to sync and interpolate {} into {} : {}", src, dest, e.getMessage());
log.debug("Details", e);
return 1;
}
}

return 0;
}

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
log.trace("pre visit dir={}", dir);
Expand Down Expand Up @@ -79,9 +97,9 @@ private boolean shouldProcessFile(Path srcFile, Path destFile) throws IOExceptio
}

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
log.warn("Failed to access {} due to {}", file, exc.getMessage());
log.debug("Details", exc);
public FileVisitResult visitFileFailed(Path file, IOException e) {
log.warn("Failed to visit file {} due to {}:{}", file, e.getClass(), e.getMessage());
log.debug("Details", e);
return FileVisitResult.CONTINUE;
}

Expand Down
106 changes: 106 additions & 0 deletions src/test/java/me/itzg/helpers/sync/SyncAndInterpolateTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package me.itzg.helpers.sync;

import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemErr;
import static org.assertj.core.api.Assertions.assertThat;

import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import picocli.CommandLine;

class SyncAndInterpolateTest {

@Nested
class NonInterpolatedScenarios {
@ParameterizedTest
@ValueSource(classes = {Sync.class, SyncAndInterpolate.class})
void copiesFromOneSrc(Class<?> commandClass, @TempDir Path tempDir) throws Exception {
final Path srcDir = Files.createDirectory(tempDir.resolve("src"));
Files.createFile(srcDir.resolve("test1.txt"));
Files.createFile(srcDir.resolve("test2.txt"));
final Path destDir = Files.createDirectory(tempDir.resolve("dest"));

final String stderr = tapSystemErr(() -> {
final int exitCode = new CommandLine(commandClass)
.execute(
"--replace-env-file-suffixes=json",
srcDir.toString(),
destDir.toString()
);

assertThat(exitCode).isEqualTo(0);
});
assertThat(stderr).isBlank();

assertThat(destDir).isNotEmptyDirectory();
assertThat(destDir.resolve("test1.txt")).exists();
assertThat(destDir.resolve("test2.txt")).exists();
}

@ParameterizedTest
@ValueSource(classes = {Sync.class, SyncAndInterpolate.class})
void copiesFromTwoSrc(Class<?> commandClass, @TempDir Path tempDir) throws Exception {
final Path srcDir1 = Files.createDirectory(tempDir.resolve("src1"));
Files.createFile(srcDir1.resolve("test1.txt"));
Files.createFile(srcDir1.resolve("test2.txt"));
final Path srcDir2 = Files.createDirectory(tempDir.resolve("src2"));
Files.createFile(srcDir2.resolve("test3.txt"));
Files.createFile(srcDir2.resolve("test4.txt"));
final Path destDir = Files.createDirectory(tempDir.resolve("dest"));

final String stderr = tapSystemErr(() -> {
final int exitCode = new CommandLine(commandClass)
.execute(
"--replace-env-file-suffixes=json",
srcDir1.toString(),
srcDir2.toString(),
destDir.toString()
);

assertThat(exitCode).isEqualTo(0);
});
assertThat(stderr).isBlank();

assertThat(destDir).isNotEmptyDirectory();
assertThat(destDir.resolve("test1.txt")).exists();
assertThat(destDir.resolve("test2.txt")).exists();
assertThat(destDir.resolve("test3.txt")).exists();
assertThat(destDir.resolve("test4.txt")).exists();
}

@ParameterizedTest
@ValueSource(classes = {Sync.class, SyncAndInterpolate.class})
void copiesFromTwoSrcCommaDelim(Class<?> commandClass, @TempDir Path tempDir) throws Exception {
final Path srcDir1 = Files.createDirectory(tempDir.resolve("src1"));
Files.createFile(srcDir1.resolve("test1.txt"));
Files.createFile(srcDir1.resolve("test2.txt"));
final Path srcDir2 = Files.createDirectory(tempDir.resolve("src2"));
Files.createFile(srcDir2.resolve("test3.txt"));
Files.createFile(srcDir2.resolve("test4.txt"));
final Path destDir = Files.createDirectory(tempDir.resolve("dest"));

final String stderr = tapSystemErr(() -> {
final int exitCode = new CommandLine(commandClass)
.execute(
"--replace-env-file-suffixes=json",
String.join(",", srcDir1.toString(), srcDir2.toString()),
destDir.toString()
);

assertThat(exitCode).isEqualTo(0);
});
assertThat(stderr).isBlank();

assertThat(destDir).isNotEmptyDirectory();
assertThat(destDir.resolve("test1.txt")).exists();
assertThat(destDir.resolve("test2.txt")).exists();
assertThat(destDir.resolve("test3.txt")).exists();
assertThat(destDir.resolve("test4.txt")).exists();
}

}

}