001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.cli.commands; 007 008import org.apache.commons.cli.CommandLine; 009import org.apache.commons.cli.Option; 010import org.apache.logging.log4j.LogManager; 011import org.apache.logging.log4j.Logger; 012 013import java.io.IOException; 014import java.io.PrintWriter; 015import java.io.StringWriter; 016import java.io.Writer; 017import java.net.URI; 018import java.net.URISyntaxException; 019import java.nio.charset.StandardCharsets; 020import java.nio.file.Files; 021import java.nio.file.Path; 022import java.nio.file.StandardOpenOption; 023import java.util.Collection; 024import java.util.List; 025 026import dev.metaschema.cli.processor.CallingContext; 027import dev.metaschema.cli.processor.ExitCode; 028import dev.metaschema.cli.processor.command.AbstractTerminalCommand; 029import dev.metaschema.cli.processor.command.CommandExecutionException; 030import dev.metaschema.cli.processor.command.ExtraArgument; 031import dev.metaschema.cli.processor.command.ICommandExecutor; 032import dev.metaschema.core.model.IModule; 033import dev.metaschema.core.model.util.MermaidErDiagramGenerator; 034import dev.metaschema.core.util.ObjectUtils; 035import dev.metaschema.databind.IBindingContext; 036import edu.umd.cs.findbugs.annotations.NonNull; 037import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 038 039/** 040 * This command implementation supports generation of a diagram depicting the 041 * objects and relationships within a provided Metaschema module. 042 */ 043public class GenerateDiagramCommand 044 extends AbstractTerminalCommand { 045 private static final Logger LOGGER = LogManager.getLogger(GenerateDiagramCommand.class); 046 047 @NonNull 048 private static final String COMMAND = "generate-diagram"; 049 @NonNull 050 private static final List<ExtraArgument> EXTRA_ARGUMENTS; 051 052 static { 053 EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of( 054 ExtraArgument.newInstance("metaschema-module-file-or-URL", true), 055 ExtraArgument.newInstance("destination-diagram-file", false))); 056 } 057 058 @Override 059 public String getName() { 060 return COMMAND; 061 } 062 063 @Override 064 public String getDescription() { 065 return "Generate a diagram for the provided Metaschema module"; 066 } 067 068 @SuppressWarnings("null") 069 @Override 070 public Collection<? extends Option> gatherOptions() { 071 return List.of(MetaschemaCommands.OVERWRITE_OPTION); 072 } 073 074 @Override 075 public List<ExtraArgument> getExtraArguments() { 076 return EXTRA_ARGUMENTS; 077 } 078 079 @Override 080 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) { 081 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand); 082 } 083 084 /** 085 * Execute the diagram generation command. 086 * 087 * @param callingContext 088 * information about the calling context 089 * @param cmdLine 090 * the parsed command line details 091 * @throws CommandExecutionException 092 * if an error occurred while executing the command 093 */ 094 @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", 095 justification = "Catching generic exception for CLI error handling") 096 protected void executeCommand( 097 @NonNull CallingContext callingContext, 098 @NonNull CommandLine cmdLine) throws CommandExecutionException { 099 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 // Print the result 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}