package com.github.approval.reporters;
/*
* #%L
* approval
* %%
* Copyright (C) 2014 Nikolavp
* %%
* 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
*
* http://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.
* #L%
*/
import com.github.approval.Reporter;
import com.github.approval.utils.CrossPlatformCommand;
import com.github.approval.utils.ExecutableExistsOnPath;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
/**
* Created with IntelliJ IDEA.
* User: github
* Date: 10/02/14
* Time: 15:10
* To change this template use File | Settings | File Templates.
*/
public final class Reporters {
private Reporters() {
}
private static final Reporter VIM_INSTANCE = new ExecutableDifferenceReporter("gvimdiff -f", "gvimdiff -f", "gvimdiff");
private static final Reporter GEDIT = SwingInteractiveReporter.wrap(new ExecutableDifferenceReporter("gedit -w", "gedit -w", "gedit") {
@Override
protected String[] buildApproveNewCommand(File approvalDestination, File fileForVerification) {
return new String[] {getApprovalCommand(), approvalDestination.getAbsolutePath()};
}
});
private static final Reporter CONSOLE_REPORTER_INSTANCE = SwingInteractiveReporter.wrap(new ExecutableDifferenceReporter("cat", "diff -u", "cat") {
@Override
protected String[] buildApproveNewCommand(File approvalDestination, File fileForVerification) {
return new String[] {getApprovalCommand(), approvalDestination.getAbsolutePath()};
}
@Override
public boolean canApprove(File fileForApproval) {
return super.canApprove(fileForApproval) && new ExecutableExistsOnPath("diff").execute();
}
});
/**
* Creates a convenient gvim reporter.
* <p>
* This one will use gvimdiff for difference detection and gvim for approving new files.
* The proper way to exit vim and not approve the new changes is with ":cq" - just have that in mind!
* </p>
*
* @return a reporter that uses vim under the hood
*/
public static Reporter gvim() {
return VIM_INSTANCE;
}
/**
* Creates a simple reporter that will print/report approvals to the console.
* <p>
* This reporter will use convenient command line tools under the hood to only report the changes in finds.
* This is perfect for batch modes or when you run your build in a CI server
* </p>
*
* @return a reporter that uses console unix tools under the hood
*/
public static Reporter console() {
return CONSOLE_REPORTER_INSTANCE;
}
/**
* Creates a reporter that uses gedit under the hood for approving.
*
* @return a reporter that uses gedit under the hood
*/
public static Reporter gedit() {
return GEDIT;
}
/**
* A reporter that launches the file under test. This is useful if you for example are generating an spreadsheet and want to verify it.
*
* @return a reporter that launches the file
*/
public static Reporter fileLauncher() {
return SwingInteractiveReporter.wrap(fileLauncherWithoutInteraction());
}
/**
* Same as {@link #fileLauncher} but doesn't do the swing interaction after it opens the file. This is mostly used to reuse the logic
* of file launching without adding the burden for prompting the user after that.
*
* @return a reporter that launches the file
*/
public static Reporter fileLauncherWithoutInteraction() {
final String cmd = new CrossPlatformCommand<String>() {
@Override
protected String onWindows() {
return "cmd /C start";
}
@Override
protected String onMac() {
return "open";
}
@Override
protected String onUnix() {
return "xdg-open";
}
}.execute();
return new ExecutableDifferenceReporter(cmd, cmd, null) {
@Override
protected String[] buildApproveNewCommand(File approvalDestination, File fileForVerification) {
return new String[]{getApprovalCommand(), approvalDestination.getAbsolutePath()};
}
@Override
protected String[] buildNotTheSameCommand(File fileForVerification, File fileForApproval) {
return new String[]{getDiffCommand(), fileForApproval.getAbsolutePath()};
}
};
}
/**
* A reporter that compares images. Currently this uses <a href="http://www.imagemagick.org/script/binary-releases.php">imagemagick</a>
* for comparison. If you only want to view the new image on first approval and when there is a difference, then you better use the {@link #fileLauncher()}
* reporter which will do this for you.
*
* @return the reporter that uses ImagemMagick for comparison
*/
public static Reporter imageMagick() {
return SwingInteractiveReporter.wrap(new Reporter() {
private final Reporter other = fileLauncher();
@Override
public void notTheSame(byte[] oldValue, File fileForVerification, byte[] newValue, File fileForApproval) {
try {
final File compareResult = File.createTempFile("compareResult", fileForVerification.getName());
final Process compare = ExecutableDifferenceReporter.runProcess("compare", fileForApproval.getCanonicalPath(), fileForVerification.getAbsolutePath(), compareResult.getAbsolutePath());
final int result = compare.waitFor();
if (result != 0) {
throw new IllegalStateException("Couldn't execute compare!");
}
other.approveNew(/* unused */newValue, /* only used value*/compareResult, /* unused */fileForVerification);
} catch (IOException | InterruptedException e) {
throw new AssertionError("Couldn't create file!", e);
}
}
@Override
public void approveNew(byte[] value, File fileForApproval, File fileForVerification) {
other.approveNew(value, fileForApproval, fileForVerification);
}
@Override
public boolean canApprove(File fileForApproval) {
return other.canApprove(fileForApproval);
}
});
}
/**
* Get a reporter that will use the first working reporter as per {@link com.github.approval.Reporter#canApprove}
* for the reporting.
*
* @param others an array/list of reporters that will be used
* @return the newly created composite reporter
*/
public static Reporter firstWorking(Reporter... others) {
return new FirstWorkingReporter(others);
}
private static final Reporter NOOP_REPORTER = new Reporter() {
@Override
public void notTheSame(byte[] oldValue, File fileForVerification, byte[] newValue, File fileForApproval) {
throw new UnsupportedOperationException("Shouldn't be called");
}
@Override
public void approveNew(byte[] value, File fileForApproval, File fileForVerification) {
throw new UnsupportedOperationException("Shouldn't be called");
}
@Override
public boolean canApprove(File fileForApproval) {
return false;
}
};
/**
* A reporter that always throws an exception and shouldn't be called. This can be used as a safe fallback
* for composing reporters. Note that it will return false in Reporter#canApprove so it shouldn't be called
* by the approval infrastructure.
*
* @return a reporter that does nothing and return false in can approve
*/
public static Reporter noop() {
return NOOP_REPORTER;
}
/**
* A reporter that auto approves every value passed to it.
* <p>
* This is especially useful when you are making a big change and want to
* auto approve a lot of things. You can then use your version control
* system(you are using one of those right?) to see a diff
* </p>
*
* @return the auto approving reporter
*/
public Reporter autoApprove() {
return new Reporter() {
@Override
public void notTheSame(byte[] oldValue, File fileForVerification, byte[] newValue, File fileForApproval) {
autoApprove(fileForVerification, fileForApproval);
}
@Override
public void approveNew(byte[] value, File fileForApproval, File fileForVerification) {
autoApprove(fileForVerification, fileForApproval);
}
private void autoApprove(File fileForVerification, File fileForApproval) {
try {
Files.move(fileForApproval.toPath(), fileForVerification.toPath());
} catch (IOException e) {
throw new IllegalStateException("Couldn't auto approve the value in " + fileForApproval.getAbsolutePath(), e);
}
}
@Override
public boolean canApprove(File fileForApproval) {
return true;
}
};
}
}