| 
									
										
										
										
											2024-09-17 13:58:36 +02:00
										 |  |  | --- | 
					
						
							|  |  |  | title: CLI Guidelines | 
					
						
							|  |  |  | --- | 
					
						
							| 
									
										
										
										
											2022-04-04 12:02:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | ## Resource centered api
 | 
					
						
							|  |  |  | Every new command group starts with the resource name like `repo`. | 
					
						
							|  |  |  | Commands should be defined as singular. For repository commands it is `repo` and **not** `repos`. | 
					
						
							|  |  |  | You may set aliases to make your command more convenient to use. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```java | 
					
						
							|  |  |  | @CommandLine.Command(name = "repo") | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Subcommands
 | 
					
						
							|  |  |  | Subcommands are action centered and can look like `scm repo create x/y`.  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The RepositoryCreateCommand is a subcommand of RepositoryCommand and must be explicitly annotated. | 
					
						
							|  |  |  | ```java | 
					
						
							|  |  |  | @ParentCommand(value = RepositoryCommand.class) | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Parameters and options
 | 
					
						
							|  |  |  | Every required field for a command must be a parameter. All other fields have to be options. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | `scm repo create git namespace/name --init --description="test"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The repository `type` and `namespace/name` must be set, so they must be annotated as parameters.  | 
					
						
							|  |  |  | The other fields like `init` and `description` are optional and are therefore annotated as options. | 
					
						
							|  |  |  | ```java | 
					
						
							|  |  |  |   @CommandLine.Parameters | 
					
						
							|  |  |  |   private String type; | 
					
						
							|  |  |  |   @CommandLine.Parameters | 
					
						
							|  |  |  |   private String repository; | 
					
						
							|  |  |  |   @CommandLine.Option(names = "--description") | 
					
						
							|  |  |  |   private String description; | 
					
						
							|  |  |  |   @CommandLine.Option(names = "--contact") | 
					
						
							|  |  |  |   private String contact; | 
					
						
							|  |  |  |   @CommandLine.Option(names = "--init") | 
					
						
							|  |  |  |   private boolean init; | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Templating
 | 
					
						
							|  |  |  | Commands which return large texts or much content should allow templating.  | 
					
						
							|  |  |  | This can be achieved by using the TemplateRenderer. | 
					
						
							|  |  |  | If you inject the TemplateRenderer you must annotate it as a Mixin: | 
					
						
							|  |  |  | ```java | 
					
						
							|  |  |  |   @CommandLine.Mixin | 
					
						
							|  |  |  |   private final TemplateRenderer templateRenderer; | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### Table
 | 
					
						
							|  |  |  | Besides "loose" templates, you can use a table-like template to render your output.  | 
					
						
							|  |  |  | For this purpose use the TemplateRender and create table first.  | 
					
						
							|  |  |  | Then add your table headers and rows.  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```java | 
					
						
							|  |  |  |       Table table = templateRenderer.createTable(); | 
					
						
							|  |  |  |       table.addHeader("repoName", "repoType", "repoUrl"); | 
					
						
							|  |  |  |       for (RepositoryCommandDto dto : dtos) { | 
					
						
							|  |  |  |         table.addRow(dto.getNamespace() + "/" + dto.getName(), dto.getType(), dto.getUrl()); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       templateRenderer.renderToStdout(TABLE_TEMPLATE, ImmutableMap.of("rows", table, "repos", dtos)); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #### Result
 | 
					
						
							|  |  |  | ```shell | 
					
						
							|  |  |  |     NAME                   TYPE URL  | 
					
						
							|  |  |  |     scmadmin/nice_repo     git  http://localhost:8081/scm/repo/scmadmin/nice_repo     | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### Key/Value Table
 | 
					
						
							|  |  |  | To create a two column (key-value-style) table you can use the `addKeyValueRow()` method. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```java | 
					
						
							|  |  |  |     Table table = createTable(); | 
					
						
							|  |  |  |     RepositoryCommandDto dto = mapper.map(repository); | 
					
						
							|  |  |  |     table.addLabelValueRow("repoNamespace", dto.getNamespace()); | 
					
						
							|  |  |  |     table.addLabelValueRow("repoName", dto.getName()); | 
					
						
							|  |  |  |     table.addLabelValueRow("repoType", dto.getType()); | 
					
						
							|  |  |  |     table.addLabelValueRow("repoContact", dto.getContact()); | 
					
						
							|  |  |  |     table.addLabelValueRow("repoUrl", dto.getUrl()); | 
					
						
							|  |  |  |     table.addLabelValueRow("repoDescription", dto.getDescription()); | 
					
						
							|  |  |  |     renderToStdout(DETAILS_TABLE_TEMPLATE, ImmutableMap.of("rows", table, "repo", dto)); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #### Result
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```shell | 
					
						
							|  |  |  | Namespace: scmadmin                                         | 
					
						
							|  |  |  | Name     : testrepo                                         | 
					
						
							|  |  |  | Type     : git      | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## I18n
 | 
					
						
							|  |  |  | The CLI client commands should support multiple languages.  | 
					
						
							|  |  |  | This can be done by using translation keys in the related resource bundles.  | 
					
						
							|  |  |  | By default, we support English and German translations. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### Example
 | 
					
						
							|  |  |  | ```java | 
					
						
							|  |  |  |   static final String DEFAULT_TEMPLATE = String.join("\n", | 
					
						
							|  |  |  |     "{{repo.namespace}}/{{repo.name}}", | 
					
						
							|  |  |  |     "{{i18n.repoDescription}}: {{repo.description}}", | 
					
						
							|  |  |  |     "{{i18n.repoType}}: {{repo.type}}", | 
					
						
							|  |  |  |     "{{i18n.repoContact}}: {{repo.contact}}" | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The variables starting with `i18n` are translations from the resource bundles.  | 
					
						
							|  |  |  | The fields starting with `repo` are context related model data from the repository we are currently accessing. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Error handling
 | 
					
						
							|  |  |  | There are different options on how to handle errors.  | 
					
						
							|  |  |  | You can use the TemplateRender and print the errors or exception messages to stderr channel. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | However, you also can throw an exception directly inside your execution.  | 
					
						
							|  |  |  | These exceptions will be handled by the CliExceptionHandler and will be printed to the stderr channel based on a specific template. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Validation
 | 
					
						
							|  |  |  | The CLI commands support Java bean validation.  | 
					
						
							|  |  |  | If you want to use this validation you have to inject the CommandValidator and call `validator.validate()` in the first line of the command execution. | 
					
						
							|  |  |  | Then you can simply annotate your fields with validation annotations. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### Example
 | 
					
						
							|  |  |  | ```java | 
					
						
							|  |  |  |   @Email | 
					
						
							|  |  |  |   @CommandLine.Option(names = {"--contact", "-c"}) | 
					
						
							|  |  |  |   private String contact; | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```java | 
					
						
							|  |  |  |   @Inject | 
					
						
							|  |  |  |   public MyCommand(CommandValidator validator) { | 
					
						
							|  |  |  |     this.validator = validator; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @Override | 
					
						
							|  |  |  | public void run() { | 
					
						
							|  |  |  |   validator.validate(); | 
					
						
							|  |  |  |   ... | 
					
						
							|  |  |  | } |