1
2
3
4
5
6 package dev.metaschema.cli.commands;
7
8 import org.apache.commons.cli.CommandLine;
9 import org.apache.commons.cli.Option;
10 import org.apache.logging.log4j.LogManager;
11 import org.apache.logging.log4j.Logger;
12
13 import java.io.IOException;
14 import java.io.PrintWriter;
15 import java.io.StringWriter;
16 import java.io.Writer;
17 import java.net.URI;
18 import java.net.URISyntaxException;
19 import java.nio.charset.StandardCharsets;
20 import java.nio.file.Files;
21 import java.nio.file.Path;
22 import java.nio.file.StandardOpenOption;
23 import java.util.Collection;
24 import java.util.List;
25
26 import dev.metaschema.cli.processor.CallingContext;
27 import dev.metaschema.cli.processor.ExitCode;
28 import dev.metaschema.cli.processor.command.AbstractTerminalCommand;
29 import dev.metaschema.cli.processor.command.CommandExecutionException;
30 import dev.metaschema.cli.processor.command.ExtraArgument;
31 import dev.metaschema.cli.processor.command.ICommandExecutor;
32 import dev.metaschema.core.model.IModule;
33 import dev.metaschema.core.model.util.MermaidErDiagramGenerator;
34 import dev.metaschema.core.util.ObjectUtils;
35 import dev.metaschema.databind.IBindingContext;
36 import edu.umd.cs.findbugs.annotations.NonNull;
37 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
38
39
40
41
42
43 public class GenerateDiagramCommand
44 extends AbstractTerminalCommand {
45 private static final Logger LOGGER = LogManager.getLogger(GenerateDiagramCommand.class);
46
47 @NonNull
48 private static final String COMMAND = "generate-diagram";
49 @NonNull
50 private static final List<ExtraArgument> EXTRA_ARGUMENTS;
51
52 static {
53 EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of(
54 ExtraArgument.newInstance("metaschema-module-file-or-URL", true),
55 ExtraArgument.newInstance("destination-diagram-file", false)));
56 }
57
58 @Override
59 public String getName() {
60 return COMMAND;
61 }
62
63 @Override
64 public String getDescription() {
65 return "Generate a diagram for the provided Metaschema module";
66 }
67
68 @SuppressWarnings("null")
69 @Override
70 public Collection<? extends Option> gatherOptions() {
71 return List.of(MetaschemaCommands.OVERWRITE_OPTION);
72 }
73
74 @Override
75 public List<ExtraArgument> getExtraArguments() {
76 return EXTRA_ARGUMENTS;
77 }
78
79 @Override
80 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
81 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
82 }
83
84
85
86
87
88
89
90
91
92
93
94 @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION",
95 justification = "Catching generic exception for CLI error handling")
96 protected void executeCommand(
97 @NonNull CallingContext callingContext,
98 @NonNull CommandLine cmdLine) throws CommandExecutionException {
99
100 List<String> extraArgs = cmdLine.getArgList();
101
102 Path destination = null;
103 if (extraArgs.size() > 1) {
104 destination = MetaschemaCommands.handleDestination(ObjectUtils.requireNonNull(extraArgs.get(1)), cmdLine);
105 }
106
107 IBindingContext bindingContext = MetaschemaCommands.newBindingContextWithDynamicCompilation();
108
109 URI moduleUri;
110 try {
111 moduleUri = resolveAgainstCWD(ObjectUtils.requireNonNull(extraArgs.get(0)));
112 } catch (URISyntaxException ex) {
113 throw new CommandExecutionException(
114 ExitCode.INVALID_ARGUMENTS,
115 String.format("Cannot load module as '%s' is not a valid file or URL. %s",
116 extraArgs.get(0),
117 ex.getLocalizedMessage()),
118 ex);
119 }
120 IModule module = MetaschemaCommands.loadModule(moduleUri, bindingContext);
121
122 if (destination == null) {
123 Writer stringWriter = new StringWriter();
124 try (PrintWriter writer = new PrintWriter(stringWriter)) {
125 MermaidErDiagramGenerator.generate(module, writer);
126 }
127
128
129 if (LOGGER.isInfoEnabled()) {
130 LOGGER.info(stringWriter.toString());
131 }
132 } else {
133 try (Writer writer = Files.newBufferedWriter(
134 destination,
135 StandardCharsets.UTF_8,
136 StandardOpenOption.CREATE,
137 StandardOpenOption.WRITE,
138 StandardOpenOption.TRUNCATE_EXISTING)) {
139 try (PrintWriter printWriter = new PrintWriter(writer)) {
140 MermaidErDiagramGenerator.generate(module, printWriter);
141 }
142 } catch (IOException ex) {
143 throw new CommandExecutionException(ExitCode.IO_ERROR, ex);
144 }
145 }
146 }
147 }