{"id":32115110,"url":"https://github.com/tomitribe/crest","last_synced_at":"2026-02-21T17:02:36.857Z","repository":{"id":9718359,"uuid":"11673895","full_name":"tomitribe/crest","owner":"tomitribe","description":"Command-line API modeled after JAX-RS (Command REST)","archived":false,"fork":false,"pushed_at":"2025-12-18T01:32:07.000Z","size":1509,"stargazers_count":58,"open_issues_count":37,"forks_count":18,"subscribers_count":20,"default_branch":"master","last_synced_at":"2026-02-11T16:35:00.759Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomitribe.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2013-07-26T00:12:26.000Z","updated_at":"2025-12-18T01:32:10.000Z","dependencies_parsed_at":"2025-07-25T12:14:24.944Z","dependency_job_id":"39b6a7d3-a2f2-4177-b2ae-9b4643565520","html_url":"https://github.com/tomitribe/crest","commit_stats":null,"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/tomitribe/crest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Fcrest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Fcrest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Fcrest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Fcrest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomitribe","download_url":"https://codeload.github.com/tomitribe/crest/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomitribe%2Fcrest/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29468393,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-15T01:01:38.065Z","status":"online","status_checked_at":"2026-02-15T02:00:07.449Z","response_time":118,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-20T15:47:00.857Z","updated_at":"2026-02-21T17:02:36.850Z","avatar_url":"https://github.com/tomitribe.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"= CREST\n\nCommand-line API styled after JAX-RS\n\nCREST allows you to get to the real work as quickly as possible when writing command line tools in Java.\n\n * 100% annotation based\n * Use Bean Validation or custom validators on use input\n * Contains Several builtin validations\n * Generates help from annotations\n * Supports default values\n * Use variable substitution on defaults\n * Supports lists and var-ags\n * Supports any java type, usually out of the box\n\nSimply annotate the parameters of any Java method so it can be invoked from a command-line interface\n with near-zero additional work.  Command-registration, help text and validation is taken care of for you.\n\n== Start your project\n\nUse the Maven archetype and run your first command now.  Copy the following commands and paste them into your terminal.\n\n----\nmvn archetype:generate -DarchetypeGroupId=org.tomitribe -DarchetypeArtifactId=tomitribe-crest-archetype -DarchetypeVersion=0.22 -DgroupId=org.example -DartifactId=mycommand\ncd mycommand/\nmvn clean install\n./target/mycommand hello\n----\nIf all went well you should see the following output:\n\n----\nHello, World!\n\n----\n\nYes, you can actually create executable command-line programs in Java!\n\n== Example: rsync as a Crest command\n\nFor example, to do something that might be similar to rsync in java, you could create the following\nmethod signature in any java object.\n\n[source,java]\n----\npackage org.example.toolz;\n\nimport org.tomitribe.crest.api.Command;\nimport org.tomitribe.crest.api.Default;\nimport org.tomitribe.crest.api.Option;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.util.regex.Pattern;\n\npublic class AnyName {\n\n    @Command\n    public void rsync(@Option(\"recursive\") boolean recursive,\n                      @Option(\"links\") boolean links,\n                      @Option(\"perms\") boolean perms,\n                      @Option(\"owner\") boolean owner,\n                      @Option(\"group\") boolean group,\n                      @Option(\"devices\") boolean devices,\n                      @Option(\"specials\") boolean specials,\n                      @Option(\"times\") boolean times,\n                      @Option(\"exclude\") Pattern exclude,\n                      @Option(\"exclude-from\") File excludeFrom,\n                      @Option(\"include\") Pattern include,\n                      @Option(\"include-from\") File includeFrom,\n                      @Option(\"progress\") @Default(\"true\") boolean progress,\n                      URI[] sources,\n                      URI dest) {\n\n        // TODO write the implementation...\n    }\n}\n----\n\nSome quick notes on `@Command` usage:\n\n  - Multiple classes that use `@Command` are allowed\n  - Muttiple `@Command` methods are allowed in a class\n  - `@Command` methods in a class may have the same or different name\n  - The command name is derived from the method name if not specified in `@Command`\n\n=== Executing the Command\n\nPack this class in an uber jar with the Crest library and you could execute this command from the command line as follows:\n\n[listing]\n----\n$ java -jar target/toolz-1.0.0-SNAPSHOT.jar rsync\nMissing argument: URI...\n\nUsage: rsync [options] URI... URI\n\nOptions:\n  --devices\n  --exclude=\u003cPattern\u003e\n  --exclude-from=\u003cFile\u003e\n  --group\n  --include=\u003cPattern\u003e\n  --include-from=\u003cFile\u003e\n  --links\n  --owner\n  --perms\n  --no-progress\n  --recursive\n  --specials\n  --times\n----\n\nOf course, if we execute the command without the required arguments it will error out.  This is the value of Crest -- it does this dance for you.\n\nIn a dozen and more years of writing tools on different teams, two truths seem to prevail:\n\n - 90% of writing scripts is parsing and validating user input\n - Don't do that well and you'll be lucky if it gets more than six months of use\n\nComputers are easy, humans are complex.  Let Crest deal with the humans, you just write code.\n\n== Help Text\n\nIn the above example we have no details in our help other than what can be generated from inspecting the code.  To add actual descriptions to our\ncode we simply need to put an `OptionDescriptions.properties` in the same package as our class.\n\n[listing]\n----\n#code\n# \u003coption\u003e = \u003cdescription\u003e\n# \u003ccommand\u003e.\u003coption\u003e = \u003cdescription\u003e\n# The most specific key always wins\n\nrecursive      = recurse into directories\nlinks          = copy symlinks as symlinks\nperms          = preserve permissions\nowner          = preserve owner (super-user only)\ngroup          = preserve group\ntimes          = preserve times\ndevices        = preserve device files (super-user only)\nspecials       = preserve special files\nexclude        = exclude files matching PATTERN\nexclude-from   = read exclude patterns from FILE\ninclude        = don't exclude files matching PATTERN\ninclude-from   = read include patterns from FILE\nprogress       = this is not the description that will be chosen\nrsync.progress = don't show progress during transfer\n----\n\nSome quick notes on `OptionDescription.properties` files:\n\n - These are Java `java.util.ResourceBundle` objects, so i18n is supported\n - Use `OptionDescription_en.properties` and similar for Locale specific help text\n - In DRY spirit, every `@Command` in the package shares the same `OptionDescription` ResourceBundle and keys\n - Use `\u003ccommand\u003e.\u003coption\u003e` as the key for situations where sharing is not desired\n\nWith the above in our classpath, our command's help will now look like the following:\n\n[listing]\n----\n$ java -jar target/toolz-1.0.0-SNAPSHOT.jar rsync\nMissing argument: URI...\n\nUsage: rsync [options] URI... URI\n\nOptions:\n  --devices                 preserve device files (super-user only)\n  --exclude=\u003cPattern\u003e       exclude files matching PATTERN\n  --exclude-from=\u003cFile\u003e     read exclude patterns from FILE\n  --group                   preserve group\n  --include=\u003cPattern\u003e       don't exclude files matching PATTERN\n  --include-from=\u003cFile\u003e     read include patterns from FILE\n  --links                   copy symlinks as symlinks\n  --owner                   preserve owner (super-user only)\n  --perms                   preserve permissions\n  --no-progress             don't show progress during transfer\n  --recursive               recurse into directories\n  --specials                preserve special files\n  --times                   preserve times\n----\n\n== Bash Completion\n\nFor those that use Bash as their shell, command completion can be added to your environment as follows:\n\n[listing]\n----\nsource $(yourcommand _completion -f)\n----\n\nUsing the example \"toolz\" cli created above, this could work like so:\n\n[listing]\n----\nsource $(java -jar target/toolz-1.0.0-SNAPSHOT.jar _completion -f)\n----\n\nIf the toolz-1.0.0-SNAPSHOT.jar was turned into an executable file called `toolz` via the `really-executable-jar` maven plugin and `toolz` is in the system `PATH`, this would also work:\n\n[listing]\n----\nsource $(toolz _completion -f)\n----\n\n\n== @Default values\n\nSetting defaults to the `@Option` parameters of our `@Command` method can be done via the `@Default` annotation.  Using as\n simplified version of our `rsync`\n example, we might possibly wish to specify a default `exclude` pattern.\n\n\n[source,java]\n----\n@Command\npublic void rsync(@Option(\"exclude\") @Default(\".*~\") Pattern exclude,\n                  @Option(\"include\") Pattern include,\n                  @Option(\"progress\") @Default(\"true\") boolean progress,\n                  URI[] sources,\n                  URI dest) {\n\n    // TODO write the implementation...\n}\n----\n\nSome quick notes about `@Option`:\n\n - `@Option` parameters are, by default, optional\n - When `@Default` is not used, the value will be its equivalent JVM default -- typically `0` or `null`\n - Add `@Required` to force a user to specify a value\n\nDefault values will show up in help output automatically, no need to update your `OptionDescriptions.properties`\n\n[listing]\n----\nUsage: rsync [options] URI... URI\n\nOptions:\n  --exclude=\u003cPattern\u003e      exclude files matching PATTERN\n                           (default: .*~)\n  --include=\u003cPattern\u003e      don't exclude files matching PATTERN\n  --no-progress            don't show progress during transfer\n----\n\n=== Advanced\n\nDefault values also support interpolations:\n\n[source,java]\n----\n@Command\npublic void myCommand(@Option(\"myoption\") @Default(\"${env.MY_ENV_VAR}\") String exclude) {\n    // TODO write the implementation...\n}\n@Command\npublic void myCommand(@Option(\"myoption\") @Default(\"${sys.MY_ENV_VAR}\") String exclude) {\n    // TODO write the implementation...\n}\n----\n\n`env` is a prefix used to read the default in the environment variables and `sys` to read the system properties.\n\nTIP: you can also register custom `DefaultsContext` in the interpolation registry using `META-INF/services/org.tomitribe.crest.contexts.DefaultsContext`\nfile to register it (just put a fully qualified implementation per line). The prefix will be the simple name of the implementation in lowercase. For instance\n`org.company.MyEnv` will use `myenv`.\n\nFinally the interpolation in such a form supports defaults:\n\n[source,java]\n----\n@Command\npublic void myCommand(@Option(\"myoption\") @Default(\"${env.MY_ENV_VAR:defaultIfEnvNotSet}\") String exclude) {\n    // TODO write the implementation...\n}\n----\n\n\n== @Option Lists and Arrays\n\nThere are situations where you might want to allow the same flag to be specified twice.  Simply turn the `@Option` parameter into an\narray or list that uses generics.\n\n[source,java]\n----\n@Command\npublic void rsync(@Option(\"exclude\") @Default(\".*~\") Pattern[] excludes,\n                  @Option(\"include\") Pattern include,\n                  @Option(\"progress\") @Default(\"true\") boolean progress,\n                  URI[] sources,\n                  URI dest) {\n\n    // TODO write the implementation...\n}\n----\n\nThe user can now specify multiple values when invoking the command by repeating the flag.\n\n[source]\n----\n$ java -jar target/toolz-1.0.0-SNAPSHOT.jar rsync --exclude=\".*\\.log\" --exclude=\".*\\.iml\"  ...\n----\n\n== @Default @Option Lists and Arrays\n\nShould you want to specify these two `exclude` values as the defaults, simply use a *comma* `,` to separate them in `@Default`\n\n[source,java]\n----\n@Command\npublic void rsync(@Option(\"exclude\") @Default(\".*\\\\.iml,.*\\\\.iml\") Pattern[] excludes,\n                  @Option(\"include\") Pattern include,\n                  @Option(\"progress\") @Default(\"true\") boolean progress,\n                  URI[] sources,\n                  URI dest) {\n\n}\n----\n\nIf you happen to need comma for something, use *tab* `\\t` instead.  When a tab is present in the `@Default` string, it becomes the preferred splitter.\n\n[source,java]\n----\n@Command\npublic void rsync(@Option(\"exclude\") @Default(\".*\\\\.iml\\t.*\\\\.iml\") Pattern[] excludes,\n                  @Option(\"include\") Pattern include,\n                  @Option(\"progress\") @Default(\"true\") boolean progress,\n                  URI[] sources,\n                  URI dest) {\n\n}\n----\n\nIf you happen to need both tab and comma for something (really????), use *unicode* zero `\\u0000` instead.\n\n[source,java]\n----\n@Command\npublic void rsync(@Option(\"exclude\") @Default(\".*\\\\.iml\\u0000.*\\\\.iml\") Pattern[] excludes,\n                  @Option(\"include\") Pattern include,\n                  @Option(\"progress\") @Default(\"true\") boolean progress,\n                  URI[] sources,\n                  URI dest) {\n\n}\n----\n\n\n== @Default and ${variable} Substitution\n\nIn the event you want to make defaults contextual, you can use `${some.property}` in the `@Default` string and\n the `java.lang.System.getProperties()` object to supply the value.\n\n[source,java]\n----\n@Command\npublic void hello(@Option(\"name\") @Default(\"${user.name}\") String user) throws Exception\n    System.out.printf(\"Hello, %s%n\", user);\n}\n----\n\n== Return Values\n\nIn the above we wrote to the console, which is fine for simple things but can make testing hard.  So far our commands are still POJOs and\nnothing is stopping us from unit testing them as plain java objects -- except asserting output writen to `System.out`.\n\nSimply return `java.lang.String` and it will be written to `System.out` for you.\n\n[source,java]\n----\n@Command\npublic String hello(@Option(\"name\") @Default(\"${user.name}\") String user) throws Exception\n    return String.format(\"Hello, %s%n\", user);\n}\n----\n\nIn the event you need to write a significant amount of data, you can return `org.tomitribe.crest.api.StreamingOutput` which is an exact copy of the\nequivalent JAX-RS http://docs.oracle.com/javaee/6/api/javax/ws/rs/core/StreamingOutput.html[StreamingOutput] interface.\n\n[source,java]\n----\n@Command\npublic StreamingOutput cat(final File file) {\n    if (!file.exists()) throw new IllegalStateException(\"File does not exist: \" + file.getAbsolutePath());\n    if (!file.canRead()) throw new IllegalStateException(\"Not readable: \" + file.getAbsolutePath());\n    if (!file.isFile()) throw new IllegalStateException(\"Not a file: \" + file.getAbsolutePath());\n\n    return new StreamingOutput() {\n        @Override\n        public void write(OutputStream output) throws IOException {\n            final InputStream input = new BufferedInputStream(new FileInputStream(file));\n            try {\n                final byte[] buffer = new byte[1024];\n                int length;\n                while ((length = input.read(buffer)) != -1) {\n                    output.write(buffer, 0, length);\n                }\n                output.flush();\n            } finally {\n                if (input != null) input.close();\n            }\n        }\n    };\n}\n----\n\nNote a `null` check is not necessary for the `File file` parameter as Crest will not let the value of any plain argument be unspecified.  All parameters which do not use `@Option` are treated as required\n\n== Stream injections\n\nCommand are often linked to console I/O. For that reason it is important to be able to interact\nwith Crest in/out/error streams. They are provided by the contextual `Environment` instance and using its thread local\nyou can retrieve them. However to make it easier to work with you can inject them as well.\n\nOut stream (out and error ones) needs to be `PrintStream` typed and input is typed as a `InputStream`.\nJust use these types as command parameters and decorate it with `@In`/`@Out`/`@Err`:\n\n[source,java]\n----\npublic class IOMe {\n    @org.tomitribe.crest.api.Command\n    public static void asserts(@In final InputStream in,\n                               @Out final PrintStream out,\n                               @Err PrintStream err) {\n        // ...\n    }\n}\n----\n\nNOTE: using a parameter typed `Environment` you'll get it injected as well but this one is not in `crest-api`.\n\n== Custom Java Types\n\nYou may have been seeing `File` and `Pattern` in the above examples and wondering exactly which Java classes Crest supports parameters to `@Command` methods.\nThe short answer is, any.  Crest does *not* use `java.beans.PropertyEditor` implementations by default like libraries such as Spring do.\n\nAfter nearly 20 years of Java's existence, it's safe to say two styles dominate converting a `String` into a Java object:\n\n * A *Constructor* that take a single String as an argument.  Examples:\n ** `java.io.File(String)`\n ** `java.lang.Integer(String)`\n ** `java.net.URL(String)`\n * A *static method* that returns an instance of the same class.  Examples:\n ** `java.util.regex.Pattern.compile(String)`\n ** `java.net.URI.create(String)`\n ** `java.util.concurrent.TimeUnit.valueOf(String)`\n\n\nUse either of these conventions and Crest will have no problem instantiating your object with the user-supplied `String` from the command-line args.\n\nThis should cover *95%* of all cases, but in the event it does not, you can create a `java.beans.PropertyEditor` and register it with the JVM.\nUse your Google-fu to learn how to do that.\n\nThe order of precedence is as follows:\n\n 1. Constructor\n 2. Static method\n 3. `java.beans.PropertyEditor`\n\n== Custom Validation\n\nIf we look at our `cat` command we had earlier and yank the very boiler-plate read/write stream logic, all we have left is some code validating the user input.\n\n[source,java]\n----\n@Command\npublic StreamingOutput cat(final File file) {\n    if (!file.exists()) throw new IllegalStateException(\"File does not exist: \" + file.getAbsolutePath());\n    if (!file.canRead()) throw new IllegalStateException(\"Not readable: \" + file.getAbsolutePath());\n    if (!file.isFile()) throw new IllegalStateException(\"Not a file: \" + file.getAbsolutePath());\n\n    return new StreamingOutput() {\n        @Override\n        public void write(OutputStream os) throws IOException {\n            IO.copy(file, os);\n        }\n    };\n}\n----\n\nThis validation code, too, can be yanked.  Crest supports the use of http://beanvalidation.org[Bean Validation] to validate `@Command` method\nparameters.\n\n[source,java]\n----\n@Command\npublic StreamingOutput cat(@Exists @Readable final File file) {\n    if (!file.isFile()) throw new IllegalStateException(\"Not a file: \" + file.getAbsolutePath());\n\n    return new StreamingOutput() {\n        @Override\n        public void write(OutputStream os) throws IOException {\n            IO.copy(file, os);\n        }\n    };\n}\n----\n\nHere we've eliminated two of our very tedious checks with Bean Validation annotations that Crest provides out of the box, but we still have one more to\nget rid of.  We can eliminate that one by writing our own annotation and using the Bean Validation API to wire it all together.\n\nHere is what an annotation to do the `file.isFile()` check might look like -- let's call the annotation simply `@IsFile`\n\n\n[source,java]\n----\npackage org.example.toolz;\n\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\nimport javax.validation.Payload;\nimport java.io.File;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport org.tomitribe.crest.val.Exists;\n\nimport static java.lang.annotation.ElementType.ANNOTATION_TYPE;\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.ElementType.METHOD;\nimport static java.lang.annotation.ElementType.PARAMETER;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n@Exists\n@Documented\n@javax.validation.Constraint(validatedBy = {IsFile.Constraint.class})\n@Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER})\n@Retention(RUNTIME)\npublic @interface IsFile {\n    Class\u003c?\u003e[] groups() default {};\n\n    String message() default \"{org.exampe.toolz.IsFile.message}\";\n\n    Class\u003c? extends Payload\u003e[] payload() default {};\n\n    public static class Constraint implements ConstraintValidator\u003cIsFile, File\u003e {\n\n        @Override\n        public void initialize(IsFile constraintAnnotation) {\n        }\n\n        @Override\n        public boolean isValid(File file, ConstraintValidatorContext context) {\n            return file.isFile();\n        }\n    }\n}\n----\n\nWe can then update our code as follows to use this validation and eliminate all our boiler-plate.\n\n[source,java]\n----\n@Command\npublic StreamingOutput cat(@IsFile @Readable final File file) {\n\n    return new StreamingOutput() {\n        @Override\n        public void write(OutputStream os) throws IOException {\n            IO.copy(file, os);\n        }\n    };\n}\n----\n\nNotice that we also removed `@Exists` from the method parameter?  Since we put `@Exists` on the `@IsFile` annotation,\nthe `@IsFile` annotation effectively inherits the `@Exists` logic.\nOur `@IsFile` annotation could inherit any number of annotations this way.\n\nAs the true strength of a great library of tools is the effort put into ensuring correct input, it's very wise to\nbite the bullet and proactively invest in creating a reusable set of validation annotations to cover your typical input\ntypes.\n\nPull requests are *very* strongly encouraged for any annotations that might be useful to others.\n\n=== Bean Validation-less validations\n\nYou can also use the built-in crest validator style, it enables to lighten the dependencies by not requiring bean validation.\nTo do that you must:\n\n. define a custom validation annotation\n. implementation the validation as a `Consumer\u003cParamType\u003e` or `BiConsumer\u003cAnnotationDefinedIn1, ParamType\u003e`\n\nIf the validation fails, the implementation just throws an exception with a meaningful error message.\n\nHere is a trivial example to check a `Path` is a directory:\n\n[source,java]\n----\n @Target(PARAMETER)\n @Retention(RUNTIME)\n @Validation(CrestDirectory.Impl.class)\n public @interface CrestDirectory {\n }\n\n public class Impl implements Consumer\u003cPath\u003e {\n   @Override\n   public void accept(final Path file) {\n       if (!Files.isDirectory(file)) {\n           throw new IllegalStateException(\"'\" + file + \"' is not a directory\");\n       }\n   }\n}\n----\n\nTIP: the instances of the implementation are looked up by class in the `Environment` and if none matches a plain `new` is done calling the default constructor.\n\n== Maven pom.xml setup\n\nThe following sample pom.xml will get you 90% of your way to fun with Crest and project\nthat will output a small uber jar with all the required dependencies.\n\n[source,xml]\n----\n\u003c?xml version=\"1.0\"?\u003e\n\u003cproject xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\u003e\n  \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n\n  \u003cgroupId\u003eorg.example\u003c/groupId\u003e\n  \u003cartifactId\u003etoolz\u003c/artifactId\u003e\n  \u003cversion\u003e0.3-SNAPSHOT\u003c/version\u003e\n\n  \u003cdependencies\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003eorg.tomitribe\u003c/groupId\u003e\n      \u003cartifactId\u003etomitribe-crest\u003c/artifactId\u003e\n      \u003cversion\u003e0.3-SNAPSHOT\u003c/version\u003e\n    \u003c/dependency\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003ejunit\u003c/groupId\u003e\n      \u003cartifactId\u003ejunit\u003c/artifactId\u003e\n      \u003cversion\u003e4.10\u003c/version\u003e\n      \u003cscope\u003etest\u003c/scope\u003e\n    \u003c/dependency\u003e\n\n    \u003c!-- Add tomitribe-crest-xbean if you want classpath scanning for @Command --\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003eorg.tomitribe\u003c/groupId\u003e\n      \u003cartifactId\u003etomitribe-crest-xbean\u003c/artifactId\u003e\n      \u003cversion\u003e0.3-SNAPSHOT\u003c/version\u003e\n    \u003c/dependency\u003e\n  \u003c/dependencies\u003e\n\n  \u003cbuild\u003e\n    \u003cdefaultGoal\u003einstall\u003c/defaultGoal\u003e\n    \u003cplugins\u003e\n      \u003cplugin\u003e\n        \u003cartifactId\u003emaven-shade-plugin\u003c/artifactId\u003e\n        \u003cversion\u003e2.1\u003c/version\u003e\n        \u003cexecutions\u003e\n          \u003cexecution\u003e\n            \u003cphase\u003epackage\u003c/phase\u003e\n            \u003cgoals\u003e\n              \u003cgoal\u003eshade\u003c/goal\u003e\n            \u003c/goals\u003e\n            \u003cconfiguration\u003e\n              \u003ctransformers\u003e\n                \u003ctransformer implementation=\"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\"\u003e\n                  \u003cmainClass\u003eorg.tomitribe.crest.Main\u003c/mainClass\u003e\n                \u003c/transformer\u003e\n              \u003c/transformers\u003e\n            \u003c/configuration\u003e\n          \u003c/execution\u003e\n        \u003c/executions\u003e\n      \u003c/plugin\u003e\n    \u003c/plugins\u003e\n  \u003c/build\u003e\n\n  \u003crepositories\u003e\n    \u003crepository\u003e\n      \u003cid\u003esonatype-nexus-snapshots\u003c/id\u003e\n      \u003cname\u003eSonatype Nexus Snapshots\u003c/name\u003e\n      \u003curl\u003ehttps://oss.sonatype.org/content/repositories/snapshots\u003c/url\u003e\n      \u003creleases\u003e\n        \u003cenabled\u003efalse\u003c/enabled\u003e\n      \u003c/releases\u003e\n      \u003csnapshots\u003e\n        \u003cenabled\u003etrue\u003c/enabled\u003e\n      \u003c/snapshots\u003e\n    \u003c/repository\u003e\n  \u003c/repositories\u003e\n\n\u003c/project\u003e\n----\n\n== Bean Parameter Binding\n\nIf you don't want to inject in all your commands the same N parameters you can modelize them as an object.\nJust use standard parameters as constructor parameters of the bean:\n\n[source,java]\n----\npublic class ColorfulCmd {\n    @Command\n    public static void exec(final Color color) {\n        // ...\n    }\n}\n----\n\nTo identify `Color` as an \"option aware\" parameter just decorate it with `@Options`:\n\n[source,java]\n----\n@Options\npublic class Color { // getters omitted for brevity\n    private final int r;\n    private final int g;\n    private final int b;\n    private final int a;\n\n    public Color(@Option(\"r\") @Default(\"255\") final int r,\n                 @Option(\"g\") @Default(\"255\") final int g,\n                 @Option(\"b\") @Default(\"255\") final int b,\n                 @Option(\"a\") @Default(\"255\") final int a) {\n        this.r = r;\n        this.g = g;\n        this.b = b;\n        this.a = a;\n    }\n}\n----\n\n=== Prefixing options\n\nIf you reuse the same parameter N times you'll probably want to prefix options. If we take previous example (`Params`)\nyou can desire to use `--background.r` and `--foreground.r` (same for g, b, a).\n\nJust use `@Option` in the method parameter to do so:\n\n[source,java]\n----\npublic class ColorfulCmd {\n    @Command\n    public static void exec(@Option(\"background.\") final Color colorBg, @Option(\"foreground.\") final Color colorFg) {\n        // ...\n    }\n}\n----\n\nNOTE: the '.' is not automatically added to allow you use to another convention like '-' or '_' ones for instance.\n\n=== Override defaults\n\nIf you reuse the same parameter model accross command parameter you'll surely want to override some default in some cases.\nFor that purpose just use `@Defaults` and define the mappings you want:\n\n[source,java]\n----\npublic class ColorfulCmd {\n    @Command\n    public static void exec(@Defaults({\n                                @Defaults.DefaultMapping(name = \"r\", value = \"0\"),\n                                @Defaults.DefaultMapping(name = \"g\", value = \"0\"),\n                                @Defaults.DefaultMapping(name = \"b\", value = \"0\"),\n                                @Defaults.DefaultMapping(name = \"a\", value = \"0\")\n                            })\n                            @Option(\"background.\")\n                            final Color colorBg,\n\n                            @Defaults({\n                                @Defaults.DefaultMapping(name = \"r\", value = \"255\"),\n                                @Defaults.DefaultMapping(name = \"g\", value = \"255\"),\n                                @Defaults.DefaultMapping(name = \"b\", value = \"255\"),\n                                @Defaults.DefaultMapping(name = \"a\", value = \"255\")\n                            })\n                            @Option(\"foreground.\")\n                            final Color colorFg) {\n        // ...\n    }\n}\n----\n\n=== Interceptors\n\nSometimes you need to modify the command invocation or \"insert\" code before/after the command execution. For that purpose crest has some light\ninterceptor support.\n\nDefining an interceptor is as easy as defining a class with:\n\n[source,java]\n----\npublic static class MyInterceptor {\n    @CrestInterceptor\n    public Object intercept(final CrestContext crestContext) {\n        return crestContext.proceed();\n    }\n}\n----\n\nThe constraint for an interceptor are:\n\n- being decorated with `@CrestInterceptor`\n- the method needs to be public\n- the method needs to table a single parameter of type `CrestContext`\n\nNOTE: you can pass `@CrestInterceptor` a value changing the key used to mark the interceptor.\n\nTo let a command use an interceptor or multiple ones just list them ordered in `interceptedBy` parameter:\n\n[source,java]\n----\n@Command(interceptedBy = { MySecurityInterceptor.class, MyLoggingInterceptor.class, MyParameterFillingInterceptor.class })\npublic void test1(\n         @Option(\"o1\") final String o1,\n         @Option(\"o2\") final int o2,\n         @Err final PrintStream err,\n         @Out final PrintStream out,\n         @In final InputStream is,\n         @Option(\"o3\") final String o3,\n         final URL url) {\n    // do something\n}\n----\n\nCrest supports 3 styles of declaring interceptors\n\n==== Via `@Command(interceptedBy)`\n\nThe `@Command` declaration uses the `interceptedBy` attribute to name the interceptor class.\n\n[source,java]\n----\npublic static class Foo {\n\n    @Command(interceptedBy = GreenInterceptor.class)\n    public String fighters(final String arg) {\n        return arg;\n    }\n----\n\nThe `GreenInterceptor` definition is as usual\n\n[source,java]\n----\npublic class GreenInterceptor {\n\n    @CrestInterceptor\n    public Object intercept(final CrestContext crestContext) {\n        return crestContext.proceed();\n    }\n}\n----\n\n==== Custom annotation containing `@CrestInterceptor(FooInterceptor.class)`\n\nIn this style, we define our own custom annotation `@Red` that names `RedInterceptor` directly\n\n[source,java]\n----\n@CrestInterceptor(RedInterceptor.class)\n@Retention(value = RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD})\npublic @interface Red {\n}\n----\n\n...and use it on our `@Command` method as follows\n\n[source,java]\n----\npublic static class Foo {\n\n    @Red\n    @Command\n    public String fighters(final String arg) {\n        return arg;\n    }\n----\n\nThe `RedInterceptor` definition is as usual\n\n[source,java]\n----\npublic class RedInterceptor {\n\n    @CrestInterceptor\n    public Object intercept(final CrestContext crestContext) {\n        return crestContext.proceed();\n    }\n}\n----\n\n==== Custom annotation containing `@CrestInterceptor` loosely coupled to an implementation\n\nIn this style, we define our own custom annotation `@Blue`, but it is not bound to a specific implementation.  The `@CrestInterceptor` does not mention the class.\n\n[source,java]\n----\n@CrestInterceptor\n@Retention(value = RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD})\npublic @interface Blue {\n}\n----\n\nThe `@Blue` is used on our `@Command` method as in the previous example\n\n[source,java]\n----\npublic static class Foo {\n\n    @Blue\n    @Command\n    public String fighters(final String arg) {\n        return arg;\n    }\n----\n\nThe `BlueInterceptor` definition identifies itself as the implementation of `@Blue` by using that annotation on its class\n\n[source,java]\n----\n@Blue\npublic class BlueInterceptor {\n\n    @CrestInterceptor\n    public Object intercept(final CrestContext crestContext) {\n        return crestContext.proceed();\n    }\n}\n----\n\nThis can be useful if you create an API jar where `@Blue` might be contained, but you want to put the implementation in a different jar.  Perhaps there are different implementations, each it it's own jar, and people choose the implementation they want by including the desired implementation jar in the classpath.\n\nIn this approach, however, it is necessary to ensure `BlueInterceptor.class` is visible to Crest by creating a `Loader` implementation such as the following\n\n[source,java]\n----\npackage org.example.myapp;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class Loader implements org.tomitribe.crest.api.Loader {\n\n    @Override\n    public Iterator\u003cClass\u003c?\u003e\u003e iterator() {\n        final List\u003cClass\u003c?\u003e\u003e classes = new ArrayList\u003c\u003e();\n        classes.add(BlueInterceptor.class);\n        return classes.listIterator();\n    }\n}\n----\n\nand declaring it in the jar at `META-INF/services/org.tomitribe.crest.api.Loader` with the following contents:\n\n[source]\n----\norg.example.myapp.Loader\n----\n\n\n==== Example for security\n\nCrest provides a `org.tomitribe.crest.interceptor.security.SecurityInterceptor` which\nhandles `@RolesAllowed` using the SPI `org.tomitribe.crest.interceptor.security.RoleProvider` to determine\nif you can call or not the command contextually.\n\nNOTE: `RoleProvider` is taken from `Environment` services. You can register it through `org.tomitribe.crest.environments.SystemEnvironment` constructor\nand just set it as environment on `org.tomitribe.crest.environments.Environment.ENVIRONMENT_THREAD_LOCAL`.\n\n\nHere a sample command using it:\n\n[source,java]\n----\n@RolesAllowed(\"test\")\n@Command(interceptedBy = SecurityInterceptor.class)\npublic static String val() {\n    return \"ok\";\n}\n----\n\n== Maven Archetype\n\nA maven archetype is available to quickly bootstrap small projects complete with the a pom like the above.  Save yourself some time on copy/paste then find/replace.\n\n[listing]\n----\nmvn archetype:generate \\\n -DarchetypeGroupId=org.tomitribe \\\n -DarchetypeArtifactId=tomitribe-crest-archetype \\\n -DarchetypeVersion=1.0.0-SNAPSHOT\n----\n\n== Maven Plugin\n\nIf you don't want to rely on runtime scanning to find classes but still want to avoid to list command classes or just reuse crest Main\nyou can use Maven Plugin to find it and generate a descriptor used to load classes.\n\nHere is how to define it in your pom:\n\n[source,xml]\n----\n\u003cplugin\u003e\n  \u003cgroupId\u003eorg.tomitribe\u003c/groupId\u003e\n  \u003cversion\u003e${crest.version}\u003c/version\u003e\n  \u003cartifactId\u003ecrest-maven-plugin\u003c/artifactId\u003e\n    \u003cexecutions\u003e\n      \u003cexecution\u003e\n        \u003cgoals\u003e\n          \u003cgoal\u003edescriptor\u003c/goal\u003e\n        \u003c/goals\u003e\n      \u003c/execution\u003e\n    \u003c/executions\u003e\n\u003c/plugin\u003e\n----\n\n== DeltaSpike Annotation Processor\n\n\nAdding this dependency to your project:\n\n[source,xml]\n----\n\u003cdependency\u003e\n  \u003cgroupId\u003eorg.tomitribe\u003c/groupId\u003e\n  \u003cartifactId\u003etomitribe-crest-generator\u003c/artifactId\u003e\n  \u003cversion\u003e${crest.version}\u003c/version\u003e\n  \u003cscope\u003eprovided\u003c/scope\u003e\n\u003c/dependency\u003e\n----\n\nCrest Generator can integrates with DeltaSpike to generate binding pojo. It will split `@ConfigProperty` on first dot\nand create one binding per prefix.\n\nHere is an example:\n\n[source,java]\n----\npublic class DeltaspikeBean {\n    @Inject\n    @ConfigProperty(name = \"app.service.base\", defaultValue = \"http://localhost:8080\")\n    private String base;\n\n    @Inject\n    @ConfigProperty(name = \"app.service.retries\")\n    private Integer retries;\n}\n----\n\nIt will generate the following binding:\n\n[source,java]\n----\npackage org.tomitribe.crest.generator.generated;\n\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.HashMap;\n\nimport org.apache.deltaspike.core.api.config.ConfigResolver;\nimport org.apache.deltaspike.core.spi.config.ConfigSource;\nimport org.tomitribe.crest.api.Default;\nimport org.tomitribe.crest.api.Option;\n\nimport static java.util.Collections.singletonList;\n\npublic class App {\n    private String serviceBase;\n    private Integer serviceRetries;\n\n    public App(\n        @Option(\"service-base\") @Default(\"http://localhost:8080\") String serviceBase,\n        @Option(\"service-retries\") Integer serviceRetries) {\n        final Map\u003cString, String\u003e ____properties = new HashMap\u003c\u003e();\n        this.serviceBase = serviceBase;\n        ____properties.put(\"app.service.base\", String.valueOf(serviceBase));\n        this.serviceRetries = serviceRetries;\n        ____properties.put(\"app.service.retries\", String.valueOf(serviceRetries));\n        ConfigResolver.addConfigSources(Collections.\u003cConfigSource\u003esingletonList(new ConfigSource() {\n            @Override\n            public int getOrdinal() {\n                return 0;\n            }\n\n            @Override\n            public Map\u003cString, String\u003e getProperties() {\n                return ____properties;\n            }\n\n            @Override\n            public String getPropertyValue(final String key) {\n                return ____properties.get(key);\n            }\n\n            @Override\n            public String getConfigName() {\n                return \"crest-app\";\n            }\n\n            @Override\n            public boolean isScannable() {\n                return true;\n            }\n        }));    }\n\n    public String getServiceBase() {\n        return serviceBase;\n    }\n\n    public void setServiceBase(final String serviceBase) {\n        this.serviceBase = serviceBase;\n    }\n\n    public Integer getServiceRetries() {\n        return serviceRetries;\n    }\n\n    public void setServiceRetries(final Integer serviceRetries) {\n        this.serviceRetries = serviceRetries;\n    }\n\n}\n----\n\nThen you just need to reuse it ad a crest command parameter:\n\n[source,java]\n----\n@Command\npublic void myCommand(@Option(\"app-\") final App app) {\n  // ...\n}\n----\n\nThe nice thing is it will integrate with crest of course but also with DeltaSpike. It means the previous code\nwill also make DeltaSpike injection respecting `App` configuration (`--app-service-base=... --app-service-retries=3` for instance).\n\nIf you create a fatjar using TomEE embedded it means you can handle all your DeltaSpike configuration this way\nand you just need to write a TomEE Embedded runner to get DeltaSpike configuration wired from the command line:\n\n[source,java]\n----\nimport org.apache.tomee.embedded.Main;\n\npublic final class Runner {\n    @Command(\"run\")\n    public static void run(@Option(\"app-\") App app) {\n        Main.main(new String[] { \"--as-war\", \"--single-classloader\" } /*fatjar \"as war\" deployment*/);\n        // automatically @Inject @ConfigProperty will be populated :)\n    }\n}\n----\n\nPotential enhancement(s):\n\n- option to generate TomEE Embedded main?\n- Tamaya integration on the same model?\n- Owner integration\n- ...\n\n== Cli module\n\nCli module aims to provide a basic integration with JLine.\n\nAll starts from `org.tomitribe.crest.cli.api.CrestCli` class. Current version is extensible through inheritance but already provides:\n\n- support of maven plugin commands (crest-commands.txt)\n- JLine integration\n- Basic pipping support (`mycommand | jgrep foo`)\n- History support is you return a file in `org.tomitribe.crest.cli.api.CrestCli.cliHistoryFile`\n- `org.tomitribe.crest.cli.api.interceptor.interactive.Interactivable` can be used to mark a parameter as required but compatible with interactive mode\n(ie the parameter is asked in interactive mode if missing).\n\nSample usage:\n\n[source,java]\n----\nfinal CrestCli cli = new CrestCli();\ncli.run();\n----\n\nTIP: `CrestCli` also has a `main(String[])` so it can be used directly as well.\n\nNOTE: if you don't provide an `exit` command one is added by default.\n\n== GraalVM integration\n\nTomitribe Crest works very smoothly with GraalVM enabling you to get a native binary from your CLI.\n\nYou can do it writing manually your `reflections.json` but you can also do it through maven using `Apache Geronimo Arthur` plugin.\nIn this last case, you can set up your CLI auto-configuration with this setup:\n\nIMPORTANT: requires Tomitribe Crest \u003e= 0.17.\n\n[source,xml]\n----\n\u003cplugin\u003e\n  \u003cgroupId\u003eorg.apache.geronimo.arthur\u003c/groupId\u003e\n  \u003cartifactId\u003earthur-maven-plugin\u003c/artifactId\u003e\n  \u003cversion\u003e1.0.3\u003c/version\u003e\n  \u003cconfiguration\u003e\n    \u003cgraalVersion\u003e21.3.0.r17\u003c/graalVersion\u003e \u003c1\u003e\n    \u003cmain\u003eorg.tomitribe.crest.Main\u003c/main\u003e \u003c2\u003e\n    \u003cextensionProperties\u003e \u003c4\u003e\n      \u003c!-- starts with, excludes exists too, don't forget help if you don't override it yourself --\u003e\n      \u003ctomitribe.crest.command.includes\u003e\n        com.superbiz.command,\n        org.tomitribe.crest.cmds.processors.Help\n      \u003c/tomitribe.crest.command.includes\u003e\n      \u003c!-- \u003ctomitribe.crest.editors.includes\u003e....\u003c/tomitribe.crest.editors.includes\u003e --\u003e \u003c5\u003e\n    \u003c/extensionProperties\u003e\n    \u003cenableAllSecurityServices\u003efalse\u003c/enableAllSecurityServices\u003e \u003c6\u003e\n  \u003c/configuration\u003e\n  \u003cdependencies\u003e \u003c3\u003e\n    \u003c!-- enable crest auto registration for commands/interceptors --\u003e\n    \u003cdependencies\u003e\n       \u003cdependency\u003e\n         \u003cgroupId\u003eorg.tomitribe\u003c/groupId\u003e\n         \u003cartifactId\u003etomitribe-crest-arthur-extension\u003c/artifactId\u003e\n         \u003cversion\u003e${crest.version}\u003c/version\u003e\n       \u003c/dependency\u003e\n     \u003c/dependencies\u003e\n  \u003c/dependencies\u003e\n\u003c/plugin\u003e\n----\n\u003c.\u003e Ensure to adjust the Graal and JVM base version (here Graal 21.3.0 in its Java 17 flavor),\n\u003c.\u003e Reuse default Crest main,\n\u003c.\u003e Enable crest extension for Arthur,\n\u003c.\u003e Customize the command scanning, note that you can tune the includes/excludes and the values are comma separated and use a \"start with\" matching logic,\n\u003c.\u003e If you are using `@Editor`, you can control the scanning there too similarly to commands,\n\u003c.\u003e This option is deprecated in recent graal versions so avoid a warning using a recent version, no direct link with crest itself,\n\nThen just run: `mvn install arthur:native-image` and you will get a `target/\u003cartifctId\u003e.graal.bin` binary you can share and execute on the built platform.\n\nIMPORTANT: only scanned editors (`@Editor`) are handled by the extension, SPI ones (`META-INF/services/org.tomitribe.crest.api.Editor`) can be used if you register them within GraalVM configuration and enable `ServiceLoader`.\n\n=== GraalVM example\n\n[source,java]\n.Cat.java\n----\npackage org.superbiz.crest.demo;\n\nimport org.tomitribe.crest.api.Command;\nimport org.tomitribe.crest.api.Editor;\nimport org.tomitribe.util.editor.AbstractConverter;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\npublic class Cat {\n    @Command(usage = \"Cat a file.\")\n    public String cat(final Path file) throws IOException {\n        return Files.readString(file);\n    }\n\n    @Editor(Path.class)\n    public static class PathEditor extends AbstractConverter {\n        @Override\n        protected Object toObjectImpl(final String text) {\n            return Paths.get(text);\n        }\n    }\n}\n----\n\n[source,xml]\n.pom.xml\n----\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cproject xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\"\u003e\n  \u003cmodelVersion\u003e4.0.0\u003c/modelVersion\u003e\n\n  \u003cgroupId\u003eorg.superbiz.demo\u003c/groupId\u003e\n  \u003cartifactId\u003edemo-crest-arthur\u003c/artifactId\u003e\n  \u003cversion\u003e1.0-SNAPSHOT\u003c/version\u003e\n\n  \u003cproperties\u003e\n    \u003ccrest.version\u003e...\u003c/crest.version\u003e \u003c!-- \u003e= 0.17 --\u003e\n  \u003c/properties\u003e\n\n  \u003cdependencies\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003eorg.tomitribe\u003c/groupId\u003e\n      \u003cartifactId\u003etomitribe-crest\u003c/artifactId\u003e\n      \u003cversion\u003e${crest.vesion}\u003c/version\u003e\n    \u003c/dependency\u003e\n  \u003c/dependencies\u003e\n\n  \u003cbuild\u003e\n    \u003cplugins\u003e\n      \u003cplugin\u003e\n        \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n        \u003cartifactId\u003emaven-resources-plugin\u003c/artifactId\u003e\n        \u003cversion\u003e3.2.0\u003c/version\u003e\n        \u003cconfiguration\u003e\n          \u003cencoding\u003eUTF-8\u003c/encoding\u003e\n        \u003c/configuration\u003e\n      \u003c/plugin\u003e\n      \u003cplugin\u003e\n        \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n        \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n        \u003cversion\u003e3.8.1\u003c/version\u003e\n        \u003cconfiguration\u003e\n          \u003crelease\u003e17\u003c/release\u003e\n          \u003csource\u003e17\u003c/source\u003e\n          \u003ctarget\u003e17\u003c/target\u003e\n        \u003c/configuration\u003e\n      \u003c/plugin\u003e\n      \u003cplugin\u003e\n        \u003cgroupId\u003eorg.apache.geronimo.arthur\u003c/groupId\u003e\n        \u003cartifactId\u003earthur-maven-plugin\u003c/artifactId\u003e\n        \u003cversion\u003e1.0.3\u003c/version\u003e\n        \u003cconfiguration\u003e\n          \u003cgraalVersion\u003e21.3.0.r17\u003c/graalVersion\u003e\n          \u003cmain\u003eorg.tomitribe.crest.Main\u003c/main\u003e\n          \u003cgraalExtensions\u003e\n            \u003c!-- enable crest auto registration for commands/interceptors --\u003e\n            \u003cgraalExtension\u003eorg.tomitribe:tomitribe-crest-arthur-extension:0.17-SNAPSHOT\u003c/graalExtension\u003e\n          \u003c/graalExtensions\u003e\n          \u003cextensionProperties\u003e\n            \u003c!-- starts with, excludes exists too --\u003e\n            \u003ctomitribe.crest.command.includes\u003e\n              ${project.groupId}.,\n              org.tomitribe.crest.cmds.processors.Help\n            \u003c/tomitribe.crest.command.includes\u003e\n          \u003c/extensionProperties\u003e\n          \u003c!-- this option is deprecated in recent graal versions --\u003e\n          \u003cenableAllSecurityServices\u003efalse\u003c/enableAllSecurityServices\u003e\n        \u003c/configuration\u003e\n        \u003cdependencies\u003e\n          \u003cdependency\u003e \u003c!-- force arthur to support java 17 --\u003e\n            \u003cgroupId\u003eorg.apache.xbean\u003c/groupId\u003e\n            \u003cartifactId\u003exbean-asm9-shaded\u003c/artifactId\u003e\n            \u003cversion\u003e4.20\u003c/version\u003e\n          \u003c/dependency\u003e\n        \u003c/dependencies\u003e\n      \u003c/plugin\u003e\n    \u003c/plugins\u003e\n  \u003c/build\u003e\n\u003c/project\u003e\n----\n\nOnce this project created, you can run `mvn clean install arthur:native-image`.\nThis creates a `./target/demo-crest-arthur.graal.bin` binary and you can execute `cat` command using: `./target/demo-crest-arthur.graal.bin cat \u003csome file\u003e`.\n\n=== Use Crest Maven Plugin Scanning\n\nit is also possible to make Arthur extension use `crest-maven-plugin` scan goal (`descriptor`).\nJust set extension property `tomitribe.crest.useInPlaceRegistrations` to `true`:\n\n[source,xml]\n----\n\u003cplugin\u003e\n  \u003cgroupId\u003eorg.tomitribe\u003c/groupId\u003e\n  \u003cartifactId\u003ecrest-maven-plugin\u003c/artifactId\u003e\n  \u003cversion\u003e${crest.version}\u003c/version\u003e\n  \u003cexecutions\u003e\n    \u003cexecution\u003e\n      \u003cid\u003escan\u003c/id\u003e\n      \u003cphase\u003eprocess-classes\u003c/phase\u003e\n      \u003cgoals\u003e\n        \u003cgoal\u003edescriptor\u003c/goal\u003e\n      \u003c/goals\u003e\n    \u003c/execution\u003e\n  \u003c/executions\u003e\n\u003c/plugin\u003e\n\u003cplugin\u003e\n  \u003cgroupId\u003eorg.apache.geronimo.arthur\u003c/groupId\u003e\n  \u003cartifactId\u003earthur-maven-plugin\u003c/artifactId\u003e\n  \u003cversion\u003e1.0.3\u003c/version\u003e\n  \u003cconfiguration\u003e\n    \u003cgraalVersion\u003e21.3.0.r17\u003c/graalVersion\u003e\n    \u003cmain\u003eorg.tomitribe.crest.Main\u003c/main\u003e\n    \u003cgraalExtensions\u003e\n      \u003cgraalExtension\u003eorg.tomitribe:tomitribe-crest-arthur-extension:0.17-SNAPSHOT\u003c/graalExtension\u003e\n    \u003c/graalExtensions\u003e\n    \u003cextensionProperties\u003e\n      \u003c!-- reuse crest maven plugin scanning --\u003e\n      \u003ctomitribe.crest.useInPlaceRegistrations\u003etrue\u003c/tomitribe.crest.useInPlaceRegistrations\u003e\n    \u003c/extensionProperties\u003e\n    \u003cenableAllSecurityServices\u003efalse\u003c/enableAllSecurityServices\u003e\n  \u003c/configuration\u003e\n  \u003cdependencies\u003e\n    \u003cdependency\u003e \u003c!-- force arthur to support java 17 --\u003e\n      \u003cgroupId\u003eorg.apache.xbean\u003c/groupId\u003e\n      \u003cartifactId\u003exbean-asm9-shaded\u003c/artifactId\u003e\n      \u003cversion\u003e4.20\u003c/version\u003e\n    \u003c/dependency\u003e\n  \u003c/dependencies\u003e\n\u003c/plugin\u003e\n----\n\nThis enables to use the same scanning for both tasks and therefore to have a common and unified scanning for java and native runs.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomitribe%2Fcrest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomitribe%2Fcrest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomitribe%2Fcrest/lists"}