Skip to content

Commit 1ca23c8

Browse files
committed
add optional executable to GraphvizCmdLineEngine (see #154)
Signed-off-by: Stefan Niederhauser <[email protected]>
1 parent fb5ad0f commit 1ca23c8

File tree

4 files changed

+57
-36
lines changed

4 files changed

+57
-36
lines changed

graphviz-java/src/main/java/guru/nidi/graphviz/engine/GraphvizCmdLineEngine.java

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
import java.util.concurrent.TimeUnit;
3030

3131
import static guru.nidi.graphviz.engine.GraphvizLoader.isOnClasspath;
32+
import static guru.nidi.graphviz.service.CommandRunner.isExecutableFile;
33+
import static guru.nidi.graphviz.service.CommandRunner.isExecutableFound;
34+
import static guru.nidi.graphviz.service.SystemUtils.pathOf;
3235
import static java.util.Locale.ENGLISH;
3336

3437
/**
@@ -40,6 +43,8 @@ public class GraphvizCmdLineEngine extends AbstractGraphvizEngine {
4043
private static final Logger LOG = LoggerFactory.getLogger(AbstractGraphvizEngine.class);
4144
static final boolean AVAILABLE = isOnClasspath("org/apache/commons/exec/CommandLine.class");
4245

46+
@Nullable
47+
private final String executable;
4348
private final String envPath;
4449
private final CommandRunner cmdRunner;
4550

@@ -49,13 +54,30 @@ public class GraphvizCmdLineEngine extends AbstractGraphvizEngine {
4954
private String outputFileName;
5055

5156
public GraphvizCmdLineEngine() {
52-
this(Optional.ofNullable(System.getenv("PATH")).orElse(""), defaultExecutor());
57+
this(null, Optional.ofNullable(System.getenv("PATH")).orElse(""), runner(defaultExecutor()));
58+
}
59+
60+
public GraphvizCmdLineEngine(String executable) {
61+
this(executable, Optional.ofNullable(System.getenv("PATH")).orElse(""), runner(defaultExecutor()));
5362
}
5463

55-
public GraphvizCmdLineEngine(String envPath, CommandLineExecutor executor) {
64+
private GraphvizCmdLineEngine(@Nullable String executable, String envPath, CommandRunner cmdRunner) {
5665
super(true);
66+
this.executable = executable;
5767
this.envPath = envPath;
58-
cmdRunner = new CommandBuilder()
68+
this.cmdRunner = cmdRunner;
69+
}
70+
71+
public GraphvizCmdLineEngine searchPath(String path) {
72+
return new GraphvizCmdLineEngine(executable, path, cmdRunner);
73+
}
74+
75+
public GraphvizCmdLineEngine executor(CommandLineExecutor executor) {
76+
return new GraphvizCmdLineEngine(executable, envPath, runner(executor));
77+
}
78+
79+
private static CommandRunner runner(CommandLineExecutor executor) {
80+
return new CommandBuilder()
5981
.withShellWrapper(true)
6082
.withCommandExecutor(executor)
6183
.build();
@@ -76,7 +98,7 @@ public GraphvizCmdLineEngine timeout(int amount, TimeUnit unit) {
7698

7799
@Override
78100
protected void doInit() {
79-
getEngineExecutable(Engine.DOT);
101+
getEngineExecutable();
80102
}
81103

82104
@Override
@@ -96,10 +118,10 @@ public EngineResult execute(String src, Options options, Rasterizer rasterizer)
96118

97119
private EngineResult doExecute(Path path, File dotFile, Options options, Rasterizer rasterizer)
98120
throws IOException, InterruptedException {
99-
final String engine = getEngineExecutable(options.engine);
100121
final String format = getFormatName(options.format, rasterizer);
101-
final String command = engine
122+
final String command = getEngineExecutable()
102123
+ (options.yInvert != null && options.yInvert ? " -y" : "")
124+
+ " -K" + options.engine.toString().toLowerCase(ENGLISH)
103125
+ " -T" + format
104126
+ " " + dotFile.getAbsolutePath() + " -ooutfile." + format;
105127
cmdRunner.exec(command, path.toFile(), timeout);
@@ -116,11 +138,16 @@ protected String preprocessCode(String src, Options options) {
116138
return replacePaths(imgReplaced, IMAGE_ATTR, path -> replacePath(path, options.basedir));
117139
}
118140

119-
private String getEngineExecutable(@Nullable Engine engine) {
120-
final String cmd = engine == null ? "dot" : engine.toString().toLowerCase(ENGLISH);
121-
final List<String> exes = SystemUtils.executableNames(cmd);
141+
private String getEngineExecutable() {
142+
if (executable != null) {
143+
if (isExecutableFile(pathOf(executable)) || isExecutableFound(executable, envPath)) {
144+
return executable;
145+
}
146+
LOG.warn("Executable '" + executable + "' not found directly and not on PATH. Trying with 'dot'.");
147+
}
148+
final List<String> exes = SystemUtils.executableNames("dot");
122149
for (final String exe : exes) {
123-
if (CommandRunner.isExecutableFound(exe, envPath)) {
150+
if (isExecutableFound(exe, envPath)) {
124151
return exe;
125152
}
126153
}

graphviz-java/src/main/java/guru/nidi/graphviz/service/CommandRunner.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
import java.nio.file.Path;
2727
import java.util.*;
2828
import java.util.function.Function;
29-
import java.util.stream.Collectors;
3029
import java.util.stream.Stream;
3130

31+
import static guru.nidi.graphviz.service.SystemUtils.fileNameEquals;
32+
import static java.util.stream.Collectors.toList;
33+
3234
public class CommandRunner {
3335
private static final Logger LOG = LoggerFactory.getLogger(CommandRunner.class);
3436

@@ -68,7 +70,6 @@ void exec(CommandLine cmd, @Nullable File workDir, int timeout) throws IOExcepti
6870
cmdExec.execute(wrappedCmd, workDir, timeout);
6971
}
7072

71-
// Cross-platform way of finding an executable in the $PATH.
7273
static Stream<Path> which(String program) {
7374
return which(program, Optional.ofNullable(System.getenv("PATH")).orElse(""));
7475
}
@@ -85,19 +86,9 @@ private static Stream<Path> which(@Nullable String optProgram, @Nullable String
8586
.map(path -> {
8687
try (Stream<Path> entries = Files.list(path)) {
8788
return entries
88-
// Filter on the filename
89-
// Doing a case-sensitive compare here, that's not correct on windows ?
90-
.filter(filePath -> program.equals(filePath.getFileName().toString()))
91-
92-
// Filter out folders
93-
.filter(filePath -> Files.isRegularFile(filePath))
94-
95-
// Check if the file is executable
96-
// Does this check work on Windows this way ?
97-
.filter(Files::isExecutable)
98-
99-
// Consume the stream here - we're inside a try-with-resources
100-
.collect(Collectors.toList())
89+
.filter(filePath -> fileNameEquals(program, filePath.getFileName().toString()))
90+
.filter(CommandRunner::isExecutableFile)
91+
.collect(toList())
10192
.stream();
10293
} catch (IOException e) {
10394
LOG.error("Problem finding path for {}", program, e);
@@ -107,6 +98,10 @@ private static Stream<Path> which(@Nullable String optProgram, @Nullable String
10798
.flatMap(stream -> stream);
10899
}
109100

101+
public static boolean isExecutableFile(Path path) {
102+
return Files.isRegularFile(path) && Files.isExecutable(path); //TODO Does this check work on Windows this way ?
103+
}
104+
110105
static boolean isExecutableFound(String program) {
111106
return which(program).anyMatch(path -> true);
112107
}

graphviz-java/src/main/java/guru/nidi/graphviz/service/SystemUtils.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ public static List<String> executableNames(String filename) {
8383
: singletonList(filename);
8484
}
8585

86+
public static boolean fileNameEquals(String file1, String file2) {
87+
return IS_OS_WINDOWS ? file1.equalsIgnoreCase(file2) : file1.equals(file2);
88+
}
89+
8690
public static Function<CommandLine, CommandLine> getShellWrapperOrDefault(boolean shellWrapper) {
8791
if (!shellWrapper) {
8892
return Function.identity();

graphviz-java/src/test/java/guru/nidi/graphviz/engine/EngineTest.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,9 @@ static List<Supplier<GraphvizEngine>> multi() {
154154

155155
@Test
156156
void cmdLine() throws IOException, InterruptedException {
157-
final File dotFile = setUpFakeDotFile();
158-
final CommandLineExecutor cmdExecutor = setUpFakeStubCommandExecutor();
159-
160-
final String envPath = dotFile.getParent();
161-
Graphviz.useEngine(new GraphvizCmdLineEngine(envPath, cmdExecutor));
157+
Graphviz.useEngine(new GraphvizCmdLineEngine("dot")
158+
.searchPath(setUpFakeDotFile().getParent())
159+
.executor(setUpFakeStubCommandExecutor()));
162160

163161
final String actual = Graphviz.fromString("graph g {a--b}").render(SVG_STANDALONE).toString();
164162
assertThat(actual, startsWith(START1_7.replace("\n", System.lineSeparator())));
@@ -169,17 +167,14 @@ void cmdLine() throws IOException, InterruptedException {
169167
*/
170168
@Test
171169
void cmdLineOutputDotFile() throws IOException, InterruptedException {
172-
final File dotFile = setUpFakeDotFile();
173-
final CommandLineExecutor cmdExecutor = setUpFakeStubCommandExecutor();
174-
175-
final String envPath = dotFile.getParent();
176-
177170
final File dotOutputFolder = new File(temp, "out");
178171
dotOutputFolder.mkdir();
179172
final String dotOutputName = "test123";
180173

181174
// Configure engine to output the dotFile to dotOutputFolder
182-
final GraphvizCmdLineEngine engine = new GraphvizCmdLineEngine(envPath, cmdExecutor);
175+
final GraphvizCmdLineEngine engine = new GraphvizCmdLineEngine()
176+
.searchPath(setUpFakeDotFile().getParent())
177+
.executor(setUpFakeStubCommandExecutor());
183178
engine.setDotOutputFile(dotOutputFolder.getAbsolutePath(), dotOutputName);
184179

185180
Graphviz.useEngine(engine);

0 commit comments

Comments
 (0)