CliFlag.java

/* ========================================================================
 * PlantUML : a free UML diagram generator
 * ========================================================================
 *
 * (C) Copyright 2009-2025, Arnaud Roques
 *
 * Project Info:  https://plantuml.com
 * 
 * If you like this project or if you find it useful, you can support us at:
 * 
 * https://plantuml.com/patreon (only 1$ per month!)
 * https://plantuml.com/paypal
 * 
 * This file is part of PlantUML.
 *
 * PlantUML is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PlantUML distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 *
 * Original Author:  Arnaud Roques
 *
 *
 */
package net.sourceforge.plantuml.cli;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.Run;
import net.sourceforge.plantuml.preproc.Stdlib;
import net.sourceforge.plantuml.stats.StatsUtils;
import net.sourceforge.plantuml.swing.ClipboardLoop;

public enum CliFlag {

	// General:

	@CliFlagDoc(value = "Show this help", level = 0)
	HELP("-help", aliases("-h", "-?", "--help"), Arity.UNARY_IMMEDIATE_ACTION, HelpPrint::printHelp),

	@CliFlagDoc(value = "Show extended help (advanced options)", level = 0)
	HELP_MORE("-help:more", aliases("-h:more", "-?:more", "--help:more"), Arity.UNARY_IMMEDIATE_ACTION,
			HelpPrint::printHelpMore),

	@CliFlagDoc(value = "Show PlantUML and Java version", level = 0)
	VERSION("-version", aliases("--version"), Arity.UNARY_IMMEDIATE_ACTION, OptionPrint::printVersion),

	@CliFlagDoc(value = "Show information about PlantUML authors", level = 0)
	AUTHOR("-author", aliases("-authors", "-about"), Arity.UNARY_IMMEDIATE_ACTION, OptionPrint::printAbout), //

	@CliFlagDoc(value = "Launch the graphical user interface", level = 0)
	GUI("-gui", Arity.UNARY_BOOLEAN, () -> GlobalConfig.getInstance().put(GlobalConfigKey.GUI, true)),

	@CliFlagDoc(value = "Render diagrams in dark mode", level = 0)
	DARK_MODE("-darkmode", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Enable verbose logging", level = 0)
	VERBOSE("-verbose", aliases("-v", "--verbose"), Arity.UNARY_BOOLEAN,
			() -> GlobalConfig.getInstance().put(GlobalConfigKey.VERBOSE, true)),

	@CliFlagDoc(value = "Print total processing time", level = 0)
	DURATION("-duration", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Show a textual progress bar", level = 0)
	PROGRESS("-progress", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Show splash screen with progress bar", level = 0)
	SPLASH("-splash", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Check Graphviz installation", level = 0)
	TEST_DOT("-testdot", Arity.UNARY_IMMEDIATE_ACTION, OptionPrint::printTestDot2),

	@CliFlagDoc(value = "Start internal HTTP server for rendering", level = 0)
	PICOWEB("-picoweb", Arity.UNARY_OPTIONAL_COLON),

	// Input & preprocessing:

	@CliFlagDoc(value = "Read source from stdin, write result to stdout", level = 0, newGroup = "Input & preprocessing")
	PIPE("-pipe", aliases("-p"), Arity.UNARY_BOOLEAN),

	PIPEMAP("-pipemap", Arity.UNARY_BOOLEAN), //
	PIPEDELIMITOR("-pipedelimitor", Arity.BINARY_NEXT_ARGUMENT_VALUE), //
	PIPENOSTDERR("-pipenostderr", Arity.UNARY_BOOLEAN), //

	@CliFlagDoc("Generate the Nth image with pipe option")
	@CliDefaultValue("0")
	PIPE_IMAGE_INDEX("-pipeimageindex", Arity.BINARY_NEXT_ARGUMENT_VALUE),

	@CliFlagDoc(value = "Define a preprocessing variable (as with '!define VAR value')", usage="-DVAR=value", level = 0)
	DEFINE("-D", Arity.UNARY_INLINE_KEY_OR_KEY_VALUE), //

	@CliFlagDoc(value = "Include external file (as with '!include file')", usage="-Ifile", level = 1)
	INCLUDE("-I", Arity.UNARY_INLINE_KEY_OR_KEY_VALUE), //

	@CliFlagDoc(value = "Set pragma (as with '!pragma key value')", usage="-Pkey=value", level = 1)
	PRAGMA("-P", Arity.UNARY_INLINE_KEY_OR_KEY_VALUE), //

	@CliFlagDoc(value = "Set skin parameter (as with 'skinparam key value')", usage="-Skey=value", level = 1)
	SKINPARAM("-S", Arity.UNARY_INLINE_KEY_OR_KEY_VALUE), //

	@CliFlagDoc(value = "Apply a theme", usage="-theme name", level = 1)
	THEME("-theme", Arity.BINARY_NEXT_ARGUMENT_VALUE),

	@CliFlagDoc(value = "Specify configuration file", usage="-config file", level = 1)
	CONFIG("-config", Arity.BINARY_NEXT_ARGUMENT_VALUE),

	@CliFlagDoc(value = "Use a specific input charset", usage="-charset name", level = 1)
	@CliDefaultValue("UTF-8")
	CHARSET("-charset", Arity.BINARY_NEXT_ARGUMENT_VALUE),

	// Execution control:

	@CliFlagDoc(value = "Check syntax without generating images", level = 0, newGroup = "Execution control")
	CHECK_ONLY("-checkonly", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Skip image generation when syntax errors are present", level = 0)
	NO_ERROR("-noerror", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Stop at the first syntax error", level = 0)
	FAIL_FAST("-failfast", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Pre-check syntax of all inputs and stop faster on error", level = 0)
	FAIL_FAST2("-failfast2", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Set GraphViz processing timeout in seconds", usage="-timeout N",  level = 1)
	TIMEOUT("-timeout", Arity.BINARY_NEXT_ARGUMENT_VALUE),

	@CliFlagDoc(value = "Use N threads for processing", usage="-nbthread N|auto",  level = 1)
	NB_THREAD("-nbthread", Arity.BINARY_NEXT_ARGUMENT_VALUE),

	// Metadata & assets:
	@CliFlagDoc(value = "Extract PlantUML source from PNG/SVG with metadata", level = 0, newGroup = "Metadata & assets")
	RETRIEVE_METADATA("-metadata", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Do not export metadata in generated files", level = 1)
	NO_METADATA("-nometadata", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Skip PNG/SVG files that are already up-to-date (faster rebuilds)", level = 0)
	CHECK_METADATA("-checkmetadata", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Encode a sprite from an image", usage="-sprite 4|8|16[z] file",  level = 0)
	ENCODE_SPRITE("-sprite", aliases("-encodesprite"), Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Obfuscate diagram texts for secure sharing", level = 0)
	CYPHER("-cypher", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Compute encoded URL from a PlantUML source file", level = 1)
	COMPUTE_URL("-computeurl", aliases("-encodeurl"), Arity.UNARY_BOOLEAN), //

	@CliFlagDoc(value = "Decode an encoded PlantUML URL", usage="-decodeurl string",  level = 1)
	DECODE_URL("-decodeurl", Arity.UNARY_BOOLEAN),

	@CliFlagDoc(value = "Print the list of PlantUML keywords", level = 1)
	LANGUAGE("-language", Arity.UNARY_IMMEDIATE_ACTION, OptionPrint::printLanguage),

	@CliFlagDoc(value = "Specify Graphviz 'dot' executable path", usage="-graphvizdot path",  level = 1)
	GRAPHVIZ_DOT("-graphvizdot", aliases("-graphviz_dot"), Arity.BINARY_NEXT_ARGUMENT_VALUE),

	@CliFlagDoc(value = "Start an FTP server (rarely used)", level = 1)
	FTP("-ftp", Arity.UNARY_OPTIONAL_COLON),

	
	// Output control
	@CliFlagDoc(value = "Generate images in the specified directory", usage="-output dir",  level = 1, newGroup = "Output control")
	OUTPUT_DIR("-output", aliases("-odir", "output_dir"), Arity.BINARY_NEXT_ARGUMENT_VALUE),

	@CliFlagDoc(value = "Allow overwriting read-only files", level = 1)
	OVERWRITE("-overwrite", Arity.UNARY_BOOLEAN, () -> GlobalConfig.getInstance().put(GlobalConfigKey.OVERWRITE, true)),

	@CliFlagDoc(value = "Exclude files matching the given pattern", usage="-exclude pattern",  level = 1)
	EXCLUDE("-exclude", aliases("-x"), Arity.BINARY_NEXT_ARGUMENT_VALUE),

	
	// Other

	CLIPBOARD("-clipboard", Arity.UNARY_IMMEDIATE_ACTION, ClipboardLoop::runOnce), //

	CLIPBOARDLOOP("-clipboardloop", Arity.UNARY_IMMEDIATE_ACTION, ClipboardLoop::runLoop), //

	@CliFlagDoc("Generate intermediate Svek files")
	DEBUG_SVEK("-debugsvek", aliases("-debug_svek"), Arity.UNARY_BOOLEAN),

	@CliFlagDoc("Pretend input files are located in given directory")
	FILE_DIR("-filedir", Arity.BINARY_NEXT_ARGUMENT_VALUE),

	@CliFlagDoc("Override %filename% variable")
	FILENAME("-filename", Arity.BINARY_NEXT_ARGUMENT_VALUE),

	HEADLESS("-headless", Arity.UNARY_BOOLEAN, () -> System.setProperty("java.awt.headless", "true")),

	@CliFlagDoc("List fonts available on your system")
	PRINT_FONTS("-printfonts", Arity.UNARY_IMMEDIATE_ACTION, Run::printFonts),

	@CliFlagDoc("Print standard library information")
	STD_LIB("-stdlib", Arity.UNARY_IMMEDIATE_ACTION, Stdlib::printStdLib),

	STDRPT("-stdrpt", Arity.UNARY_OPTIONAL_COLON),

	@CliFlagDoc("Report syntax errors from stdin without generating images")
	SYNTAX("-syntax", Arity.UNARY_BOOLEAN),

	LICENSE("-license", aliases("-licence"), Arity.UNARY_IMMEDIATE_ACTION, OptionPrint::printLicense),

	WORD("-word", Arity.UNARY_BOOLEAN, () -> GlobalConfig.getInstance().put(GlobalConfigKey.WORD, true)),

	USE_SEPARATOR_MINUS("-useseparatorminus", Arity.UNARY_BOOLEAN,
			() -> GlobalConfig.getInstance().put(GlobalConfigKey.FILE_SEPARATOR, "-")),

	// Output format (choose one)

	@CliFlagDoc(value = "Generate images in EPS format", level = 0, newGroup = "Output format (choose one)")
	T_EPS("-teps", aliases("-eps"), Arity.UNARY_BOOLEAN, FileFormat.EPS),
	T_EPS_TEXT("-teps:text", aliases("-eps:text"), Arity.UNARY_BOOLEAN, FileFormat.EPS_TEXT),

	@CliFlagDoc(value = "Generate HTML file for class diagram", level = 1)
	T_HTML("-thtml", aliases("-html"), Arity.UNARY_BOOLEAN, FileFormat.HTML),

	@CliFlagDoc(value = "Generate LaTeX/Tikz without preamble", level = 1)
	T_LATEX_NOPREAMBLE("-tlatex:nopreamble", aliases("-latex:nopreamble"), Arity.UNARY_BOOLEAN,
			FileFormat.LATEX_NO_PREAMBLE),

	@CliFlagDoc(value = "Generate LaTeX/Tikz output", level = 0)
	T_LATEX("-tlatex", aliases("-latex"), Arity.UNARY_BOOLEAN, FileFormat.LATEX),

	@CliFlagDoc(value = "Generate PDF images", level = 1)
	T_PDF("-tpdf", aliases("-pdf"), Arity.UNARY_BOOLEAN, FileFormat.PDF),

	@CliFlagDoc(value = "Generate PNG images (default)", level = 0)
	T_PNG("-tpng", aliases("-png"), Arity.UNARY_BOOLEAN, FileFormat.PNG),

	T_BASE64("-tbase64", aliases("-base64"), Arity.UNARY_BOOLEAN, FileFormat.BASE64),
	T_BRAILLE("-tbraille", aliases("-braille"), Arity.UNARY_BOOLEAN, FileFormat.BRAILLE_PNG),

	@CliFlagDoc(value = "Generate SCXML file for state diagram", level = 1)
	T_SCXML("-tscxml", Arity.UNARY_BOOLEAN, FileFormat.SCXML),

	@CliFlagDoc(value = "Generate SVG images", level = 0)
	T_SVG("-tsvg", aliases("-svg"), Arity.UNARY_BOOLEAN, FileFormat.SVG),

	@CliFlagDoc(value = "Generate ASCII art diagrams", level = 0)
	T_TXT("-ttxt", aliases("-txt"), Arity.UNARY_BOOLEAN, FileFormat.ATXT),

	@CliFlagDoc(value = "Generate ASCII art diagrams using Unicode", level = 0)
	T_UTXT("-tutxt", aliases("-utxt"), Arity.UNARY_BOOLEAN, FileFormat.UTXT),

	@CliFlagDoc(value = "Generate VDX images", level = 1)
	T_VDX("-tvdx", aliases("-vdx"), Arity.UNARY_BOOLEAN, FileFormat.VDX),

	@CliFlagDoc(value = "Generate XMI files for class diagrams", level = 1)
	T_XMI("-txmi", aliases("-xmi"), Arity.UNARY_BOOLEAN, FileFormat.XMI_STANDARD),
	T_XMI_ARGO("-txmi:argo", aliases("-xmi:argo"), Arity.UNARY_BOOLEAN, FileFormat.XMI_ARGO),
	T_XMI_CUSTOM("-txmi:custom", aliases("-xmi:custom"), Arity.UNARY_BOOLEAN, FileFormat.XMI_CUSTOM),
	T_XMI_SCRIPT("-txmi:script", aliases("-xmi:script"), Arity.UNARY_BOOLEAN, FileFormat.XMI_SCRIPT),
	T_XMI_STAR("-txmi:star", aliases("-xmi:star"), Arity.UNARY_BOOLEAN, FileFormat.XMI_STAR),

	@CliFlagDoc(value = "Output preprocessor text of diagrams", level = 1)
	PREPROCESS("-preproc", Arity.UNARY_BOOLEAN, FileFormat.PREPROC),

	// ************************ stats

	@CliFlagDoc(value = "Disable statistics computation (default)", level = 1, newGroup = "Statistics")
	DISABLE_STATS("-disablestats", Arity.UNARY_BOOLEAN,
			() -> GlobalConfig.getInstance().put(GlobalConfigKey.ENABLE_STATS, true)),

	@CliFlagDoc(value = "Enable statistics computation", level = 1)
	ENABLE_STATS("-enablestats", Arity.UNARY_BOOLEAN,
			() -> GlobalConfig.getInstance().put(GlobalConfigKey.ENABLE_STATS, true)),

	DUMPHTMLSTATS("-dumphtmlstats", Arity.UNARY_IMMEDIATE_ACTION, StatsUtils::outHtml),

	DUMPSTATS("-dumpstats", Arity.UNARY_IMMEDIATE_ACTION, StatsUtils::dumpStats),

	@CliFlagDoc(value = "Output general statistics in HTML format", level = 1)
	HTML_STATS("-htmlstats", Arity.UNARY_BOOLEAN, () -> StatsUtils.setHtmlStats(true)),

	@CliFlagDoc(value = "Generate statistics on the fly", level = 1)
	REALTIME_STATS("-realtimestats", Arity.UNARY_BOOLEAN, () -> StatsUtils.setRealTimeStats(true)),

	@CliFlagDoc(value = "Output general statistics in XML format", level = 1)
	XML_STATS("-xmlstats", Arity.UNARY_BOOLEAN, () -> StatsUtils.setXmlStats(true)),
	
	@CliFlagDoc(value = "Continuously print usage statistics", level = 1)
	LOOP_STATS("-loopstats", Arity.UNARY_IMMEDIATE_ACTION, StatsUtils::loopStats);



	private final String flag;
	private final List<String> aliases;
	private final Arity type;
	private final Object foo;

	CliFlag(String flag, Arity type) {
		this(flag, Collections.emptyList(), type, null);
	}

	CliFlag(String flag, List<String> aliases, Arity type) {
		this(flag, aliases, type, null);
	}

	CliFlag(String flag, Arity type, CliAction foo) {
		this(flag, Collections.emptyList(), type, foo);
	}

	CliFlag(String flag, Arity type, Object foo) {
		this(flag, Collections.emptyList(), type, foo);
	}

	CliFlag(String flag, List<String> aliases, Arity type, Object foo) {
		this.type = type;
		this.flag = flag;
		this.aliases = aliases;
		this.foo = foo;
	}

	CliFlag(String flag, List<String> aliases, Arity type, CliAction foo) {
		this.type = type;
		this.flag = flag;
		this.aliases = aliases;
		this.foo = foo;
	}

	private static List<String> aliases(String... names) {
		return Arrays.asList(names);
	}

	public String getFlag() {
		return flag;
	}

	public List<String> getAliases() {
		return aliases;
	}

	public Arity getType() {
		return type;
	}

	boolean match(String tmp) {
		if (type == Arity.UNARY_INLINE_KEY_OR_KEY_VALUE || type == Arity.UNARY_OPTIONAL_COLON)
			return (tmp.startsWith(flag));

		for (String alias : aliases)
			if (tmp.equals(alias))
				return true;

		return tmp.equals(flag);
	}

	public String getFlagDoc() {
		try {
			final CliFlagDoc annotation = CliFlag.class.getField(this.name()).getAnnotation(CliFlagDoc.class);
			if (annotation != null && !annotation.value().isEmpty())
				return annotation.value();

		} catch (NoSuchFieldException e) {
			// Should never happen for enum constants
		}
		return null;
	}

	public String getNewgroup() {
		try {
			final CliFlagDoc annotation = CliFlag.class.getField(this.name()).getAnnotation(CliFlagDoc.class);
			if (annotation != null && !annotation.value().isEmpty())
				return annotation.newGroup();

		} catch (NoSuchFieldException e) {
			// Should never happen for enum constants
		}
		return "";
	}

	public String getUsage() {
		try {
			final CliFlagDoc annotation = CliFlag.class.getField(this.name()).getAnnotation(CliFlagDoc.class);
			if (annotation != null && !annotation.usage().isEmpty())
				return annotation.usage();

		} catch (NoSuchFieldException e) {
			// Should never happen for enum constants
		}
		return getFlag();
	}

	public int getFlagLevel() {
		try {
			final CliFlagDoc annotation = CliFlag.class.getField(this.name()).getAnnotation(CliFlagDoc.class);
			if (annotation != null)
				return annotation.level();

		} catch (NoSuchFieldException e) {
			// Should never happen for enum constants
		}
		return -1;
	}

	public String getDefaultValue() {
		try {
			final CliDefaultValue annotation = CliFlag.class.getField(this.name()).getAnnotation(CliDefaultValue.class);
			if (annotation != null && !annotation.value().isEmpty())
				return annotation.value();

		} catch (NoSuchFieldException e) {
			// Should never happen for enum constants
		}
		return null;
	}

	public Object getFoo() {
		return foo;
	}

}