Skip to content

Commit

Permalink
Merge pull request #27 from Jumpaku/refactor/cli
Browse files Browse the repository at this point in the history
version v1.0.0
  • Loading branch information
Jumpaku authored May 25, 2024
2 parents 42319ba + 696117a commit 0211d26
Show file tree
Hide file tree
Showing 192 changed files with 10,481 additions and 4,516 deletions.
44 changes: 25 additions & 19 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,37 @@ help: ## Show help

.PHONY: check
check: ## Checks version, runs tests.
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$' < version.txt
$(eval VERSION := $(shell head -n 1 version.txt))
grep -E '^version: $(VERSION)$$' < cmd/cyamli/cli.yaml
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$' ./info/version.txt
$(eval VERSION := $(shell head -n 1 ./info/version.txt))
grep -E '^version: $(VERSION)$$' ./cyamli/cli.yaml
go test ./...

.PHONY: gen-cli
gen-cli: ## Generates Go CLI for cyamli command.
go run ./internal/tools/gen-cli-golang/main.go < cmd/cyamli/cli.yaml > cmd/cyamli/cli.gen.go
.PHONY: install
install: ## Install cyamli built in present status.
go run ./internal/cmd/gen-golang < cyamli/cli.yaml > cyamli/cli.gen.go
go install .
cyamli generate golang -package=cyamli < cyamli/cli.yaml > cyamli/cli.gen.go
go install .

.PHONY: version-apply
version-apply: ## Generates Go CLI for cyamli command.
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$' < version.txt
grep -E '^version: v[0-9]+\.[0-9]+\.[0-9]+$$' < cmd/cyamli/cli.yaml
$(eval VERSION := $(shell head -n 1 version.txt))
sed -E -i.backup "s/^version: v[0-9]+\.[0-9]+\.[0-9]+$$/version: $(VERSION)/g" cmd/cyamli/cli.yaml
rm cmd/cyamli/cli.yaml.backup
make gen-cli
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$' < ./info/version.txt
grep -E '^version: v[0-9]+\.[0-9]+\.[0-9]+$$' < ./cyamli/cli.yaml
$(eval VERSION := $(shell head -n 1 ./info/version.txt))
sed -E -i.backup "s/^version: v[0-9]+\.[0-9]+\.[0-9]+$$/version: $(VERSION)/g" ./cyamli/cli.yaml
rm ./cyamli/cli.yaml.backup
make install
make examples
make docs

.PHONY: examples
examples: ## Generates Go CLI for cyamli command.
go run ./internal/tools/gen-cli-golang/main.go < examples/cmd/example/cli.yaml > examples/cmd/example/cli.gen.go
go run ./internal/tools/gen-cli-golang/main.go < examples/cmd/greet/cli.yaml > examples/cmd/greet/cli.gen.go
go run ./internal/tools/gen-cli-golang/main.go < examples/cmd/demo-app/cli.yaml > examples/cmd/demo-app/cli.gen.go
examples: install ## Generates Go CLI for cyamli command.
go run . generate golang < examples/cmd/example/cli.yaml > examples/cmd/example/cli.gen.go
go run . generate golang < examples/cmd/demo-app/cli.yaml > examples/cmd/demo-app/cli.gen.go

go run ./internal/tools/gen-cli-python3/main.go < examples/cmd/example/cli.yaml > examples/cmd/example/cli_gen.py
go run ./internal/tools/gen-cli-python3/main.go < examples/cmd/greet/cli.yaml > examples/cmd/greet/cli_gen.py
go run ./internal/tools/gen-cli-python3/main.go < examples/cmd/demo-app/cli.yaml > examples/cmd/demo-app/cli_gen.py
go run . generate python3 < examples/cmd/example/cli.yaml > examples/cmd/example/cli_gen.py
go run . generate python3 < examples/cmd/demo-app/cli.yaml > examples/cmd/demo-app/cli_gen.py

.PHONY: docs
docs: install ## Generates documentation of cyamli.
go run . generate docs -all -format=markdown < cyamli/cli.yaml > cyamli-docs.md
188 changes: 91 additions & 97 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,163 +1,154 @@
# cyamli

A command line tool to generate command line interfaces for your command line tools from the YAML-based CLI schemas.
A command line tool to generate interfaces for command line tools from YAML-based CLI schemas.

## Highlights
## Overview

This repository:
- defines the CLI schema in YAML.
- provides a typed CLI code generator from the CLI schema.
Developing console apps involves defining and parsing command line interfaces (CLIs) such as command line arguments, which consist of subcommands, options, and positional arguments.

## CLI schema
`cyamli` is a schema-based code generator that generates APIs (Application Programming Interfaces, such as types and functions) to handle typed CLIs.
The schema of a typed CLI can be written in YAML according to the CLI schema definition ( https://github.com/Jumpaku/cyamli/blob/main/cli-schema-definition.ts ).

The CLI schema definition is provided in [`cli-schema-definition.ts`](https://github.com/Jumpaku/cyamli/blob/main/cli-schema-definition.ts).
## Motivation

## CLI code generator
- Schema-based approach leveraging standardized and consistent sources.
- Promoting typed CLIs for the benefits of static checking and code completion.
- Reducing boilerplate by automatically generating the necessary code.

From a YAML file written according to the CLI schema definition, `cyamli` generates a typed code for handling command line arguments.

### Installation
## Installation

`cyamli` can be installed as follows:

```sh
go install "github.com/Jumpaku/cyamli/cmd/cyamli@latest"
```shell
go install github.com/Jumpaku/cyamli@latest
```

or use go generate as follows:
## Usage with an example

```go
//go:generate go run "github.com/Jumpaku/cyamli/cmd/cyamli@latest" golang -schema-path=path/to/cli.yaml -out-path=path/to/cli.gen.go
```
Assume a situation where you need to develop a console app in Go to fetch information from a database.

### Usage
Usage of `cyamli` is as follows:

1. Generating the CLI type.
2. Overwriting the CLI object.
3. Executing program.
1. Define a CLI as a YAML file.
2. Generate the API to parse the CLI in Go.
3. Assign functions to the generated API.

#### Generating the CLI type
### Define a CLI as a YAML file

Prepare the CLI schema for your application, for example:
The following YAML file, `cli.yaml`, defines a CLI for the example console app.

```yaml
name: greet
description: this is an example program
options:
-help:
short: -h
description: Show help information.
type: boolean
name: demo
description: demo app to get table information from databases
subcommands:
hello:
description: Prints "Hello, <target name>! My name is <greeter>!"
list:
description: list tables
options:
-target-name:
short: -t
description: The name of the person to be said hello.
-config:
description: path to config file
short: -c
fetch:
description: show information of tables
options:
-config:
description: path to config file
short: -c
-verbose:
description: show detailed contents for specified tables
short: -v
type: boolean
arguments:
- name: greeter
description: The name of the person who says hello.
- name: tables
variadic: true
description: names of tables to be described
```
Run `cyamli` as follows:
### Generate API to parse the CLI in Go
The following command reads a schema from `cli.yaml` and writes the Go API into `cli.gen.go`.

```sh
cyamli golang < path/to/cli-schema.yaml > path/to/generated/code.go
```shell
cyamli generate golang -schema-path=cli.yaml -out-path=cli.gen.go
```

The above generates a Go code which includes:
`cli.gen.go` includes the following API:

```go
type CLI
type CLI_Input
type CLI_Hello
type CLI_Hello_Input
// CLI represents a root command.
type CLI struct
// NewCLI returns a CLI object.
func NewCLI() CLI
// Run parses command line arguments args and calls a corresponding function assigned in cli.
func Run(cli CLI, args []string) error
// GetDoc returns a help message corresponding to subcommand.
func GetDoc(subcommand []string) string
```

#### Overwriting the CLI object
### Assign functions to the generated API.

To define the behavior of your program, you can utilize the generated types and functions as follows:
`NewCLI()` returns an object `cli` which represents a root command, and its descendant objects represent subcommands.
Each of them has a `FUNC` field.
A function assigned to this field will be called by `Run(cli, os.Args)`.

```go
// Create the CLI object
var cli = NewCLI()
The following code snippet demonstrates an implementation for the example console app.

func main() {
// Overwrite behaviors
cli.FUNC = showHelp
cli.Hello.FUNC = sayHello
// Run with command line arguments
if err := Run(cli, os.Args); err != nil {
panic(err)
}
}
```
```go
package main
Example implementations for `showHelp` and `sayHello` are as follows:
import (
"fmt"
"os"
)
```go
func showHelp(subcommand []string, input CLI_Input, inputErr error) (err error) {
if inputErr != nil {
fmt.Println(cli.DESC_Simple())
panic(inputErr)
func main() {
cli := NewCLI()
cli.FUNC = func(subcommand []string, input CLI_Input, inputErr error) (err error) {
fmt.Println(input, inputErr)
fmt.Println(GetDoc(subcommand))
return nil
}
if input.Opt_Help {
fmt.Println(cli.DESC_Detail())
cli.List.FUNC = func(subcommand []string, input CLI_List_Input, inputErr error) (err error) {
fmt.Println(input, inputErr)
fmt.Println(GetDoc(subcommand))
return nil
}
return nil
}
func sayHello(subcommand []string, input CLI_Hello_Input, inputErr error) (err error) {
if inputErr != nil {
fmt.Println(cli.Hello.DESC_Simple())
return inputErr
cli.Fetch.FUNC = func(subcommand []string, input CLI_Fetch_Input, inputErr error) (err error) {
fmt.Println(input, inputErr)
fmt.Println(GetDoc(subcommand))
return nil
}
if input.Opt_TargetName != "" {
fmt.Printf("Hello, %s! My name is %s!\n", input.Opt_TargetName, input.Arg_Greeter)
} else {
fmt.Printf("Hello! My name is %s!\n", input.Arg_Greeter)
if err := Run(cli, os.Args); err != nil {
panic(err)
}
return nil
}
```

#### Executing program

Execute the `main` function as follows
The example console app can be executed as follows:

```sh
go run main.go -h
# => This is an example program.
go run main.go hello Alice
# => Hello! My name is Alice!
go run main.go hello -target-name=Bob Alice
# => Hello, Bob! My name is Alice!
```shell
go run main.go list -c=config.yaml
go run main.go fetch -c=config.yaml -v table1 table2
```

## Examples

The example CLI applications are found in https://github.com/Jumpaku/cyamli/tree/main/examples .

## Details

### Supported programming languages

The following programming languages are supported currently.
The following programming languages are currently supported:

* Go
* Python3
* Documentation in text, HTML, and Markdown

### Handling command line arguments

Command line arguments according to the following syntax can be handled by the generated API.

```
<program> <subcommand> [<option>|<argument>]... [-- [<argument>]...]
```
- `<program>` is the path to your executable file.
- `<subcommand>` is a sequence of tokens, which represents a path in the command tree illustrated in your CLI schema.
- `<subcommand>` is a sequence of tokens, which represents a path in the command tree illustrated in a defined CLI schema.
- Each element of `<subcommand>` must match the regular expression `^[a-z][a-z0-9]*$`.
- `<subcommand>` may be empty, which means the execution of the root command.
- `<option>` represents an option, which is a token in form of `<option_name>[=<option_value>]`.
Expand All @@ -167,3 +158,6 @@ The following programming languages are supported currently.
- `<argument>` represents an argument, which must be a string that can be parsed as a value of the type of the argument.
- Tokens after `--` are handled as arguments even if prefixed by `-`.
### Usage of cyamli command
The documentation for `cyamli` command is provided at https://github.com/Jumpaku/cyamli/blob/main/cyamli-docs.md .
Loading

0 comments on commit 0211d26

Please sign in to comment.