├── .env ├── .gitignore ├── LICENSE.md ├── README.md ├── plantuml-go.go ├── puml_test.go └── test.puml /.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yogendra/plantuml-go/1e0758c537a343cb48d3de014ef23b61109527ef/.env -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | plantuml-go 2 | docs/public* 3 | /.idea 4 | hugo.exe 5 | *.test 6 | *.prof 7 | nohup.out 8 | cover.out 9 | *.swp 10 | *.swo 11 | .DS_Store 12 | *~ 13 | vendor/*/ 14 | *.bench 15 | coverage*.out 16 | 17 | GoBuilds 18 | dist 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yogendra/plantuml-go/1e0758c537a343cb48d3de014ef23b61109527ef/LICENSE.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PlantUML Go Client 2 | This project provides a handy CLI for PlantUML users. 3 | 4 | ## Motivation 5 | * Self-contained tool 6 | * Non-Java 7 | * Able to work with hosted PlantUML server 8 | * Produces "Text Format" 9 | * Produces Link 10 | * Produced Images (Wow!) 11 | * Learn [Go][http://golang.org] 12 | 13 | ## How to use? 14 | 15 | Get go package first. 16 | 17 | ```shell 18 | go get http://github.com/yogendra/plantuml-go 19 | ``` 20 | _I am too new to Go and I could be wrong_ 21 | 22 | Now, run `plantuml-go` 23 | 24 | ```shell 25 | plantuml-go my-uml.puml 26 | ``` 27 | 28 | or 29 | 30 | ```shell 31 | echo "@startuml 32 | a -> b : hello world 33 | @enduml" | plantuml-go 34 | ``` 35 | 36 | ### How to generate images? 37 | 38 | User `-f png -o output` options on command line 39 | 40 | ```shell 41 | plantuml-go -f png -o output my-uml.puml 42 | ``` 43 | 44 | Above command will create a `my-uml.png` file next to `my-uml.puml` file (same director). 45 | 46 | ##ToDo 47 | * CD/CI 48 | * Release 49 | * Improve Test Coverage -------------------------------------------------------------------------------- /plantuml-go.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "bytes" 6 | "io/ioutil" 7 | "flag" 8 | "fmt" 9 | "path/filepath" 10 | "strings" 11 | "net/http" 12 | "io" 13 | "compress/zlib" 14 | ) 15 | const ( 16 | FORMAT_TXT = "txt" 17 | FORMAT_PNG = "png" 18 | FORMAT_SVG = "svg" 19 | STYLE_TXT = "text" 20 | STYLE_LINK = "link" 21 | STYLE_OUTPUT = "output" 22 | mapper = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" 23 | ) 24 | func main() { 25 | opts := parseArgs(); 26 | if opts.InputStream != nil { 27 | process(&opts, encodeAsTextFormat(opts.InputStream), ""); 28 | }else{ 29 | for _, filename := range opts.FileNames { 30 | data,err := ioutil.ReadFile(filename) 31 | if err != nil{ 32 | fmt.Println(err); 33 | continue 34 | } 35 | process(&opts,encodeAsTextFormat(data), filename); 36 | } 37 | } 38 | } 39 | func process(options *Options, textFormat string, filename string) { 40 | if options.Style == STYLE_TXT { 41 | fmt.Printf("%s\n" , textFormat) 42 | } else if options.Style == STYLE_LINK { 43 | fmt.Printf("%s/%s/%s\n", options.Server, options.Format, textFormat) 44 | } else if options.Style == STYLE_OUTPUT { 45 | link := fmt.Sprintf("%s/%s/%s", options.Server, options.Format, textFormat) 46 | output := os.Stdout 47 | 48 | if filename != "" { 49 | outputFilename := strings.TrimSuffix(filename, filepath.Ext(filename)) 50 | outputFilename = fmt.Sprintf("%s.%s", outputFilename, options.Format) 51 | output, _ = os.OpenFile(outputFilename, os.O_RDWR|os.O_CREATE, 0666) 52 | } 53 | response, err := http.Get(link) 54 | if err != nil { 55 | fmt.Println(err) 56 | return 57 | } 58 | if response.StatusCode != 200 { 59 | fmt.Println("Error in Fetching: %s\n%s\n", link, response.Status) 60 | return 61 | } 62 | 63 | io.Copy(output, response.Body) 64 | output.Close() 65 | } 66 | } 67 | 68 | type Options struct { 69 | Server string 70 | Format string 71 | Style string 72 | InputStream []byte 73 | FileNames []string 74 | } 75 | 76 | func encodeAsTextFormat(raw []byte) string{ 77 | compressed := deflate(raw) 78 | return base64_encode(compressed); 79 | } 80 | 81 | func deflate(input []byte) []byte { 82 | var b bytes.Buffer 83 | w,_ := zlib.NewWriterLevel(&b, zlib.BestCompression) 84 | w.Write(input) 85 | w.Close() 86 | return b.Bytes() 87 | } 88 | 89 | func base64_encode(input []byte) string { 90 | var buffer bytes.Buffer 91 | inputLength := len(input) 92 | for i := 0; i < 3 - inputLength % 3; i++ { 93 | input = append(input, byte(0)) 94 | } 95 | 96 | for i := 0; i < inputLength; i += 3 { 97 | b1, b2, b3, b4 := input[i], input[i+1], input[i+2], byte(0) 98 | 99 | b4 = b3 & 0x3f 100 | b3 = ((b2 & 0xf) << 2) | (b3 >> 6) 101 | b2 = ((b1 & 0x3) << 4) | (b2 >> 4) 102 | b1 = b1 >> 2 103 | 104 | for _,b := range []byte{b1,b2,b3,b4} { 105 | buffer.WriteByte(byte(mapper[b])) 106 | } 107 | } 108 | return string(buffer.Bytes()) 109 | } 110 | 111 | func parseArgs() Options { 112 | flag.CommandLine.Init(os.Args[0], flag.ExitOnError) 113 | server := flag.String("s", "http://plantuml.com/plantuml", "Plantuml `server` address. Used when generating link or extracting output") 114 | format := flag.String("f", "png", "Output `format` type. (Options: png,txt,svg)") 115 | style := flag.String("o", "text", "Indicates if `output` style. (Options: text, link, output)") 116 | help := flag.Bool("h", false, "Show help (this) text") 117 | flag.Parse() 118 | fileList := flag.Args() 119 | files := []string{} 120 | if len(fileList) > 0 { 121 | fileMap := make(map[string]int) 122 | for _, f := range fileList { 123 | abs, err := filepath.Abs(f) 124 | if (err != nil) { 125 | fmt.Errorf("%s: Unable to resolve filename\n", f) 126 | } 127 | _, ok := fileMap[abs] 128 | if !ok { 129 | files = append(files, abs) 130 | fileMap[abs] = 1 131 | } 132 | } 133 | } 134 | var inputStream []byte 135 | stat, _ := os.Stdin.Stat() 136 | if (stat.Mode() & os.ModeCharDevice) == 0 { 137 | data,err := ioutil.ReadAll(os.Stdin) 138 | if err == nil { 139 | inputStream = data 140 | } 141 | } 142 | 143 | 144 | 145 | if *help || len(files) == 0 && len(inputStream) == 0 { 146 | fmt.Printf(`USAGE: 147 | plantuml-go [OPTIONS] files 148 | Reads and process files based on options 149 | plantuml-go [OPTIONS] 150 | Reads and process stdin. NOTE: Ouput will be on stdout 151 | OPTIONS 152 | `,); 153 | flag.PrintDefaults() 154 | os.Exit(1) 155 | } 156 | opts := Options{*server, *format,*style, inputStream, files} 157 | return opts; 158 | } -------------------------------------------------------------------------------- /puml_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEncodeAsText(t *testing.T){ 8 | expected := "SYWkIImgAStDuNBAJrBGjLDmpCbCJbMmKiX8pSd9vt98pKifpSq11000__y0" 9 | input := `@startuml 10 | Bob -> Alice : hello 11 | @enduml` 12 | actual := encodeAsTextFormat([]byte(input)) 13 | if actual != expected { 14 | t.Fatalf("Expected: %s\nActual:%s", expected, actual); 15 | } 16 | } -------------------------------------------------------------------------------- /test.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | Bob -> Alice : hello 3 | @enduml --------------------------------------------------------------------------------