Skip to content

Commit

Permalink
First release of the kroki CLI (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
ggrossetie committed Feb 17, 2019
1 parent a95bd5c commit 5195743
Show file tree
Hide file tree
Showing 12 changed files with 741 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
language: go

go:
- "1.11.x"

env:
- GO111MODULE=on GOLANGCI_LINT_VERSION=1.12.5

install:
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v${GOLANGCI_LINT_VERSION}
- go mod vendor

script:
- go test -race ./...
- golangci-lint run .
68 changes: 68 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
= Kroki CLI

image:https://travis-ci.org/yuzutech/kroki-cli.svg?branch=master[Build Status,link=https://travis-ci.org/yuzutech/kroki-cli]

A CLI for https://kroki.io.

== Usage

kroki convert hello.dot

By default the diagram type will be inferred from the diagram file extension and the default output format will be `SVG`.

Long format:

kroki convert hello.dot --type dot --format svg

The output format can also be inferred from the output file using the `--out-file` flag:

kroki convert simple.er --out-file out.png

Read from `stdin`:

cat hello.dot | kroki convert - -t dot

By default when reading from `stdin` using `-`, the result will be output to `stdout`.
If you want to output to a file you can use `-o`:

cat hello.dot | kroki convert - -t dot -o out.png

Similarly, you can also output to `stdout` when reading from a file using the special value `-` with the `--out-file` flag:

kroki convert simple.er --out-file -

== Configuration

To configure the endpoint, you can use a configuration file.
The CLI will look for the following locations:

* `/etc/kroki.yml`
* `$HOME/kroki.yml`
* `kroki.yml`

You can also specify an alternate config file using the `--config` flag:

kroki convert hello.dot --config config.yml

The config file should contain the endpoint URL and the HTTP timeout.
By default Kroki will use the demonstration server: https://demo.kroki.io and a timeout of 20 seconds.

CAUTION: Please note that the demonstration server usage is restricted to reasonable, non-commercial use-cases.
We provide no guarantee regarding uptime or latency.

Example:

.kroki.yml
```yml
endpoint: 'https://localhost:8000'
timeout: 30s
```

If you don't want to use a file you can also use the following environment variables:

* `KROKI_ENDPOINT`
* `KROKI_TIMEOUT`

[]

KROKI_ENDPOINT=https://localhost:8000 KROKI_TIMEOUT=1m kroki convert hello.dot
33 changes: 33 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cmd

import "github.com/spf13/viper"

func SetupConfig() {
// Default values
viper.SetDefault("endpoint", "https://demo.kroki.io")
viper.SetDefault("timeout", "20s")

// Config file name
viper.SetConfigName("kroki")

// Default config paths
viper.AddConfigPath("/etc")
viper.AddConfigPath("$HOME")
viper.AddConfigPath(".")

// Environment variables
viper.SetEnvPrefix("kroki")
err := viper.BindEnv("endpoint")
if err != nil {
exit(err)
}
err = viper.BindEnv("timeout")
if err != nil {
exit(err)
}
}


func InitDefaultConfig() {
viper.ReadInConfig() // ignore error
}
255 changes: 255 additions & 0 deletions cmd/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package cmd

import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/yuzutech/kroki-go"
)

func Convert(cmd *cobra.Command, args []string) {
filePath := args[0]
graphFormat, err := cmd.Flags().GetString("type")
if err != nil {
exit(err)
}
imageFormat, err := cmd.Flags().GetString("format")
if err != nil {
exit(err)
}
outFile, err := cmd.Flags().GetString("out-file")
if err != nil {
exit(err)
}
client := GetClient(cmd)
if filePath == "-" {
reader := bufio.NewReader(os.Stdin)
ConvertFromReader(client, graphFormat, imageFormat, outFile, reader)
} else {
ConvertFromFile(client, filePath, graphFormat, imageFormat, outFile)
}
}

func ConvertFromReader(client kroki.Client, diagramTypeRaw string, imageFormatRaw string, outFile string, reader io.Reader) {
if diagramTypeRaw == "" {
exit("diagram type must be specify using --type flag")
}
diagramType, err := GraphFormatFromValue(diagramTypeRaw)
if err != nil {
exit(err)
}
imageFormat, err := ResolveImageFormat(imageFormatRaw, outFile)
if err != nil {
exit(err)
}
text, err := GetTextFromReader(reader)
if err != nil {
exit(err)
}
result, err := client.FromString(text, diagramType, imageFormat)
if err != nil {
exit(err)
}
if outFile == "" || outFile == "-" {
fmt.Println(result)
} else {
err = client.WriteToFile(outFile, result)
if err != nil {
exit(err)
}
}
}

func GetTextFromReader(reader io.Reader) (result string, err error) {
input, err := ioutil.ReadAll(reader)
return string(input), err
}

func ConvertFromFile(client kroki.Client, filePath string, graphFormatRaw string, imageFormatRaw string, outFile string) {
graphFormat, err := ResolveGraphFormat(graphFormatRaw, filePath)
if err != nil {
exit(err)
}
imageFormat, err := ResolveImageFormat(imageFormatRaw, outFile)
if err != nil {
exit(err)
}
result, err := client.FromFile(filePath, graphFormat, imageFormat)
if err != nil {
exit(err)
}
if outFile == "-" {
fmt.Println(result)
} else {
err = client.WriteToFile(ResolveOutputFilePath(outFile, filePath, imageFormat), result)
if err != nil {
exit(err)
}
}
}

func ResolveOutputFilePath(outFile string, filePath string, imageFormat kroki.ImageFormat) string {
if outFile != "" {
return outFile
}
fileExtension := path.Ext(filePath)
return filePath[0:len(filePath)-len(fileExtension)] + "." + string(imageFormat)
}

func ResolveImageFormat(imageFormatRaw string, outFile string) (kroki.ImageFormat, error) {
if imageFormatRaw == "" {
if outFile == "" || outFile == "-" {
return kroki.Svg, nil
}
return ImageFormatFromFile(outFile)
}
return ImageFormatFromValue(imageFormatRaw)
}

func ImageFormatFromValue(imageFormatRaw string) (kroki.ImageFormat, error) {
value := strings.ToLower(imageFormatRaw)
switch value {
case "svg":
return kroki.Svg, nil
case "png":
return kroki.ImageFormat("png"), nil
case "jpeg":
return kroki.ImageFormat("jpeg"), nil
case "pdf":
return kroki.ImageFormat("pdf"), nil
default:
return kroki.ImageFormat(""), errors.Errorf(
"invalid image format %s.",
value)
}
}

func ImageFormatFromFile(filePath string) (kroki.ImageFormat, error) {
fileExtension := filepath.Ext(filePath)
value := strings.ToLower(fileExtension)
switch value {
case ".svg":
return kroki.Svg, nil
case ".png":
return kroki.ImageFormat("png"), nil
case ".jpeg", ".jpg":
return kroki.ImageFormat("jpeg"), nil
case ".pdf":
return kroki.ImageFormat("pdf"), nil
default:
return kroki.ImageFormat(""), errors.Errorf(
"invalid image format %s.",
value)
}
}

func ResolveGraphFormat(graphFormatRaw string, filePath string) (kroki.GraphFormat, error) {
if graphFormatRaw == "" {
return GraphFormatFromFile(filePath)
} else {
return GraphFormatFromValue(graphFormatRaw)
}
}

func GraphFormatFromValue(value string) (kroki.GraphFormat, error) {
value = strings.ToLower(value)
switch value {
case "dot", "graphviz":
return kroki.Graphviz, nil
case "plantuml":
return kroki.Plantuml, nil
case "nomnoml":
return kroki.Nomnoml, nil
case "blockdiag":
return kroki.BlockDiag, nil
case "mermaid":
return kroki.Mermaid, nil
case "svgbob":
return kroki.Svgbob, nil
case "umlet":
return kroki.Umlet, nil
case "c4plantuml":
return kroki.C4plantuml, nil
case "seqdiag":
return kroki.SeqDiag, nil
case "erd", "er":
return kroki.GraphFormat("erd"), nil
case "nwdiag":
return kroki.GraphFormat("nwdiag"), nil
case "actdiag":
return kroki.GraphFormat("actdiag"), nil
case "ditaa":
return kroki.GraphFormat("ditaa"), nil
default:
return kroki.GraphFormat(""), errors.Errorf(
"invalid graph format %s.",
value)
}
}

func GraphFormatFromFile(filePath string) (kroki.GraphFormat, error) {
fileExtension := filepath.Ext(filePath)
value := strings.ToLower(fileExtension)
switch value {
case ".dot", ".gv", ".graphviz":
return kroki.Graphviz, nil
case ".puml", ".plantuml":
return kroki.Plantuml, nil
case ".nomnoml":
return kroki.Nomnoml, nil
case ".blockdiag":
return kroki.BlockDiag, nil
case ".mermaid":
return kroki.Mermaid, nil
case ".svgbob":
return kroki.Svgbob, nil
case ".umlet":
return kroki.Umlet, nil
case ".c4puml", ".c4", ".c4plantuml":
return kroki.C4plantuml, nil
case ".seqdiag":
return kroki.SeqDiag, nil
case ".erd", ".er":
return kroki.GraphFormat("erd"), nil
case ".nwdiag":
return kroki.GraphFormat("nwdiag"), nil
case ".actdiag":
return kroki.GraphFormat("actdiag"), nil
case ".ditaa":
return kroki.GraphFormat("ditaa"), nil
default:
return kroki.GraphFormat(""), errors.Errorf(
"unable to infer the graph format from the file extension %s, please specify the diagram type using --type flag.",
value)
}
}

func GetClient(cmd *cobra.Command) kroki.Client {
configFilePath, err := cmd.Flags().GetString("config")
if err != nil {
exit(err)
}
if configFilePath != "" {
file, err := os.Open(configFilePath)
if err != nil {
exit(err)
}
err = viper.ReadConfig(file)
if err != nil {
exit(err)
}
}
return kroki.New(kroki.Configuration{
URL: viper.GetString("endpoint"),
Timeout: viper.GetDuration("timeout"),
})
}
Loading

0 comments on commit 5195743

Please sign in to comment.