├── Article_CN.md ├── README.md ├── go.mod ├── graph.go ├── graph_test.go └── main.go /Article_CN.md: -------------------------------------------------------------------------------- 1 | > [本文](http://poloxue.com/go/go-module-relationship-visible-tool)首发于[我的博客](http://poloxue.com),如果觉得有用,欢迎点赞收藏,让更多的朋友看到。 2 | 3 | 最近,我开发了一个非常简单的小工具,总的代码量 200 行不到。今天,简单介绍下它。这是个什么工具呢?它是一个用于可视化展示 Go Module 依赖关系的工具。 4 | 5 | # 为何开发 6 | 7 | 为什么会想到开发这个工具?主要有两点原因: 8 | 9 | 一是最近经常看到大家在社区讨论 Go Module。于是,我也花了一些时间研究了下。期间,遇到了一个需求,如何清晰地识别模块中依赖项之间的关系。一番了解后,发现了 `go mod graph`。 10 | 11 | 效果如下: 12 | 13 | ``` 14 | $ go mod graph 15 | github.com/poloxue/testmod golang.org/x/text@v0.3.2 16 | github.com/poloxue/testmod rsc.io/quote/v3@v3.1.0 17 | github.com/poloxue/testmod rsc.io/sampler@v1.3.1 18 | golang.org/x/text@v0.3.2 golang.org/x/tools@v0.0.0-20180917221912-90fa682c2a6e 19 | rsc.io/quote/v3@v3.1.0 rsc.io/sampler@v1.3.0 20 | rsc.io/sampler@v1.3.1 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c 21 | rsc.io/sampler@v1.3.0 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c 22 | ``` 23 | 24 | 每一行的格式是 `模块 依赖模块`,基本能满足要求,但总觉得还是不那么直观。 25 | 26 | 二是我之前手里有一个项目,包管理一直用的是 dep。于是,我也了解了下它,把官方文档仔细读了一遍。其中的[某个章节](https://golang.github.io/dep/docs/daily-dep.html)介绍了依赖项可视化展示的方法。 27 | 28 | 文档中给出的包关系图: 29 | 30 |
31 | 32 |
33 | 34 | 看到这张图的时候,眼睛瞬间就亮了,图形化就是优秀,不同依赖之间的关系一目了然。这不就是我想要的效果吗?666,点个赞。 35 | 36 | 但 ...,随之而来的问题是,go mod 没这个能力啊。怎么办? 37 | 38 | # 如何实现 39 | 40 | 先看看是不是已经有人做了这件事了。网上搜了下,没找到。那是不是能自己实现?应该可以借鉴下 dep 的思路吧? 41 | 42 | 如下是 dep 依赖实现可视化的方式: 43 | 44 | ```bash 45 | # linux 46 | $ sudo apt-get install graphviz 47 | $ dep status -dot | dot -T png | display 48 | 49 | # macOS 50 | $ brew install graphviz 51 | $ dep status -dot | dot -T png | open -f -a /Applications/Preview.app 52 | 53 | # Windows 54 | > choco install graphviz.portable 55 | > dep status -dot | dot -T png -o status.png; start status.png 56 | ``` 57 | 58 | 这里展示了三大系统下的使用方式,它们都安装了一个软件包,graphviz。从名字上看,这应该是一个用来实现可视化的软件,即用来画图的。事实也是这样,可以看看它的[官网](http://www.graphviz.org/documentation/)。 59 | 60 | 再看下它的使用,发现都是通过管道命令组合的方式,而且前面的部分基本相同,都是 `dep status -dot | dot -T png`。后面的部分在不同的系统就不同了,Linux 是 `display`,MacOS 是 `open -f -a /Applications/Preview.app`,Window 是 `start status.png`。 61 | 62 | 稍微分析下就会明白,前面是生成图片,后面是显示图片。因为不同系统的图片展示命令不同,所以后面的部分也就不同了。 63 | 64 | 现在关心的重点在前面,即 `dep status -dot | dot -T png` 干了啥,它究竟是如何实现绘图的?大致猜测,dot -T png 是由 dep status -dot 提供的数据生成图片。继续看下 `dep status -dot` 的执行效果吧。 65 | 66 | ```bash 67 | $ dep status -dot 68 | digraph { 69 | node [shape=box]; 70 | 2609291568 [label="github.com/poloxue/hellodep"]; 71 | 953278068 [label="rsc.io/quote\nv3.1.0"]; 72 | 3852693168 [label="rsc.io/sampler\nv1.0.0"]; 73 | 2609291568 -> 953278068; 74 | 953278068 -> 3852693168; 75 | } 76 | ``` 77 | 78 | 咋一看,输出的是一段看起来不知道是啥的代码,这应该是 graphviz 用于绘制图表的语言。那是不是还有学习下?当然不用啊,这里用的很简单,直接套用就行了。 79 | 80 | 试着分析一下吧,前面两行可以不用关心,这应该是 graphviz 特定的写法,表示要画的是什么图。我们主要关心如何将数据以正确形式提供出来。 81 | 82 | ```bash 83 | 2609291568 [label="github.com/poloxue/hellodep"]; 84 | 953278068 [label="rsc.io/quote\nv3.1.0"]; 85 | 3852693168 [label="rsc.io/sampler\nv1.0.0"]; 86 | 2609291568 -> 953278068; 87 | 953278068 -> 3852693168; 88 | ``` 89 | 90 | 一看就知道,这里有两种结构,分别是为依赖项关联 ID ,和通过 ID 和 `->` 表示依赖间的关系。 91 | 92 | 按上面的猜想,我们可以试着画出一个简单的图, 用于表示 a 模块依赖 b 模块。执行命令如下,将绘图代码通过 `each` 管道的方式发送给 `dot` 命令。 93 | 94 | ``` 95 | $ echo 'digraph { 96 | node [shape=box]; 97 | 1 [label="a"]; 98 | 2 [label="b"]; 99 | 1 -> 2; 100 | }' | dot -T png | open -f -a /Applications/Preview.app 101 | ``` 102 | 103 | 效果如下: 104 | 105 |
106 | 107 |
108 | 109 | 绘制一个依赖关系图竟然这么简单。 110 | 111 | 看到这里,是不是发现问题已经变得非常简单了。我们只要将 `go mod graph` 的输出转化为类似的结构就能实现可视化了。 112 | 113 | # 开发流程介绍 114 | 115 | 接下来,开发这个小程序吧,我将这个小程序命名为 `modv`,即 module visible 的意思。项目源码位于 [poloxue/modv](https://github.com/poloxue/modv)。 116 | 117 | # 接收管道的输入 118 | 119 | 先要检查数据输入管道是否正常。 120 | 121 | 我们的目标是使用类似 `dep` 中作图的方式,`go mod graph` 通过管道将数据传递给 `modv`。因此,要先检查 `os.Stdin`,即检查标准输入状态是否正常, 以及是否是管道传输。 122 | 123 | 下面是 main 函数的代码,位于 [main.go](https://github.com/poloxue/modv/blob/master/main.go) 中。 124 | 125 | ```go 126 | func main() { 127 | info, err := os.Stdin.Stat() 128 | if err != nil { 129 | fmt.Println("os.Stdin.Stat:", err) 130 | PrintUsage() 131 | os.Exit(1) 132 | } 133 | 134 | // 是否是管道传输 135 | if info.Mode()&os.ModeNamedPipe == 0 { 136 | fmt.Println("command err: command is intended to work with pipes.") 137 | PrintUsage() 138 | os.Exit(1) 139 | } 140 | ``` 141 | 142 | 一旦确认输入设备一切正常,我们就可以进入到数据读取、解析与渲染的流程了。 143 | 144 | ```go 145 | mg := NewModuleGraph(os.Stdin) 146 | mg.Parse() 147 | mg.Render(os.Stdout) 148 | } 149 | ``` 150 | 151 | 接下来,开始具体看看如何实现数据的处理流程。 152 | 153 | # 抽象实现结构 154 | 155 | 先定义一个结构体,并大致定义整个流程。 156 | 157 | ```go 158 | type ModGraph struct { 159 | Reader io.Reader // 读取数据流 160 | } 161 | 162 | func NewModGraph(r io.Reader) *ModGraph { 163 | return &ModGraph{Reader: r} 164 | } 165 | 166 | // 执行数据的处理转化 167 | func (m *ModGraph) Parse() error {} 168 | 169 | // 结果渲染与输出 170 | func (m *ModGraph) Render(w io.Writer) error {} 171 | ``` 172 | 173 | 再看下 `go mod graph` 的输出吧,如下: 174 | 175 | ```bash 176 | github.com/poloxue/testmod golang.org/x/text@v0.3.2 177 | github.com/poloxue/testmod rsc.io/quote/v3@v3.1.0 178 | ... 179 | ``` 180 | 181 | 每一行的结构是 `模块 依赖项`。现在的目标是要它解析成下面这样的结构: 182 | 183 | ```bash 184 | digraph { 185 | node [shape=box]; 186 | 1 github.com/poloxue/testmod; 187 | 2 golang.org/x/text@v0.3.2; 188 | 3 rsc.io/quote/v3@v3.1.0; 189 | 1 -> 2; 190 | 1 -> 3; 191 | } 192 | ``` 193 | 194 | 前面说过,这里包含了两种不同的结构,分别是模块与 ID 关联关系,以及模块 ID 表示模块间的依赖关联。为 `ModGraph` 结构体增加两个成员表示它们。 195 | 196 | ```go 197 | type ModGraph struct { 198 | r io.Reader // 数据流读取实例,这里即 os.Stdin 199 | 200 | // 每一项名称与 ID 的映射 201 | Mods map[string]int 202 | // ID 和依赖 ID 关系映射,一个 ID 可能依赖多个项 203 | Dependencies map[int][]int 204 | } 205 | ``` 206 | 207 | 要注意的是,增加了两个 map 成员后,记住要在 `NewModGraph` 中初始化下它们。 208 | 209 | # mod graph 输出解析 210 | 211 | 如何进行解析? 212 | 213 | 介绍到这里,目标已经很明白了。就是要将输入数据解析到 `Mods` 和 `Dependencies` 两个成员中,实现代码都在 `Parse` 方法中。 214 | 215 | 为了方便进行数据读取,首先,我们利用 `bufio` 基于 `reader` 创建一个新的 `bufReader`, 216 | 217 | ```go 218 | func (m *ModGraph) Parse() error { 219 | bufReader := bufio.NewReader(m.Reader) 220 | ... 221 | ``` 222 | 223 | 为便于按行解析数据,我们通过 bufReader 的 `ReadBytes()` 方法循环一行一行地读取 os.Stdin 中的数据。然后,对每一行数据按空格切分,获取到依赖关系的两项。代码如下: 224 | 225 | ```go 226 | for { 227 | relationBytes, err := bufReader.ReadBytes('\n') 228 | if err != nil { 229 | if err == io.EOF { 230 | return nil 231 | } 232 | return err 233 | } 234 | 235 | relation := bytes.Split(relationBytes, []byte(" ")) 236 | // module and dependency 237 | mod, depMod := strings.TrimSpace(string(relation[0])), strings.TrimSpace(string(relation[1])) 238 | 239 | ... 240 | } 241 | ``` 242 | 243 | 接下来,就是将解析出来的依赖关系组织到 `Mods` 和 `Dependencies` 两个成员中。模块 ID 是生成规则采用的是最简单的实现方式,从 1 自增。实现代码如下: 244 | 245 | ```go 246 | modId, ok := m.Mods[mod] 247 | if !ok { 248 | modId = serialID 249 | m.Mods[mod] = modId 250 | serialID += 1 251 | } 252 | 253 | depModId, ok := m.Mods[depMod] 254 | if !ok { 255 | depModId = serialID 256 | m.Mods[depMod] = depModId 257 | serialID += 1 258 | } 259 | 260 | if _, ok := m.Dependencies[modId]; ok { 261 | m.Dependencies[modId] = append(m.Dependencies[modId], depModId) 262 | } else { 263 | m.Dependencies[modId] = []int{depModId} 264 | } 265 | ``` 266 | 267 | 解析的工作到这里就结束了。 268 | 269 | # 渲染解析的结果 270 | 271 | 这个小工具还剩下最后一步工作要做,即将解析出来的数据渲染出来,以满足 `graphviz` 工具的作图要求。实现代码是 `Render`部分: 272 | 273 | 首先,定义一个模板,以生成满足要求的输出格式。 274 | 275 | ```go 276 | var graphTemplate = `digraph { 277 | node [shape=box]; 278 | {{ range $mod, $modId := .mods -}} 279 | {{ $modId }} [label="{{ $mod }}"]; 280 | {{ end -}} 281 | {{- range $modId, $depModIds := .dependencies -}} 282 | {{- range $_, $depModId := $depModIds -}} 283 | {{ $modId }} -> {{ $depModId }}; 284 | {{ end -}} 285 | {{- end -}} 286 | } 287 | ` 288 | ``` 289 | 290 | 这一块没啥好介绍的,主要是要熟悉 Go 中的 `text/template` 模板的语法规范。为了展示友好,这里通过 `-` 实现换行的去除,整体而言不影响阅读。 291 | 292 | 接下来,看 `Render` 方法的实现,把前面解析出来的 `Mods` 和 `Dependencies` 放入模板进行渲染。 293 | 294 | ```go 295 | func (m *ModuleGraph) Render(w io.Writer) error { 296 | templ, err := template.New("graph").Parse(graphTemplate) 297 | if err != nil { 298 | return fmt.Errorf("templ.Parse: %v", err) 299 | } 300 | 301 | if err := templ.Execute(w, map[string]interface{}{ 302 | "mods": m.Mods, 303 | "dependencies": m.Dependencies, 304 | }); err != nil { 305 | return fmt.Errorf("templ.Execute: %v", err) 306 | } 307 | 308 | return nil 309 | } 310 | ``` 311 | 312 | 现在,全部工作都完成了。最后,将这个流程整合到 main 函数。接下来就是使用了。 313 | 314 | # 使用体验 315 | 316 | 开始体验下吧。补充一句,这个工具,我现在只测试了 Mac 下的使用,如有问题,欢迎提出来。 317 | 318 | 首先,要先安装一下 `graphviz`,安装的方式在本文开头已经介绍了,选择你的系统安装方式。 319 | 320 | 接着是安装 `modv`,命令如下: 321 | 322 | ```bash 323 | $ go get github.com/poloxue/modv 324 | ``` 325 | 326 | 安装完成!简单测试下它的使用。 327 | 328 | 以 MacOS 为例。先下载测试库,github.com/poloxue/testmod。 进入 testmod 目录执行命令: 329 | 330 | ```bash 331 | $ go mod graph | modv | dot -T png | open -f -a /Applications/Preview.app 332 | ``` 333 | 334 | 如果执行成功,将看到如下的效果: 335 | 336 | ![](https://blogimg.poloxue.com/0014-go-mod-graph-visible-04.png) 337 | 338 | 完美地展示了各个模块之间的依赖关系。 339 | 340 | # 一些思考 341 | 342 | 本文是篇实践性的文章,从一个简单想法到成功呈现出一个可以使用的工具。虽然,开发起来并不难,从开发到完成,仅仅花了一两个小时。但我的感觉,这确实是个有实际价值的工具。 343 | 344 | 还有一些想法没有实现和验证,比如一旦项目较大,是否可以方便的展示某个指定节点的依赖树,而非整个项目。还有,在其他项目向 Go Module 迁移的时候,这个小工具是否能产生一些价值。 345 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a module dependency visualizer for go mod. 2 | 3 | # Usage 4 | 5 | Modv's usage is different in different systems. 6 | 7 | ## Linux 8 | 9 | Install `graphviz`. For Ubuntu/Debian 10 | ```bash 11 | $ sudo apt-get install graphviz 12 | ``` 13 | 14 | For ArchLinux 15 | 16 | ``` 17 | $ sudo pacman -S --needed graphviz 18 | ``` 19 | 20 | Install `modv` and use it. 21 | 22 | ``` 23 | $ go install github.com/poloxue/modv 24 | $ go mod graph | modv | dot -T svg -o /tmp/modv.svg && xdg-open /tmp/modv.svg 25 | ``` 26 | 27 | 28 | ## MacOS 29 | 30 | ```bash 31 | $ brew install graphviz 32 | $ go get github.com/poloxue/modv 33 | ``` 34 | 35 | Try the following. 36 | 37 | ``` 38 | $ go mod graph | modv | dot -T png | open -f -a /Applications/Preview.app 39 | ``` 40 | 41 | If error accured, for eaxmple,`FSPathMakeRef(/Applications/Preview.app) failed with error -43.`,try the command: 42 | 43 | ``` 44 | $ go mod graph | modv | dot -T png | open -f -a /System/Applications/Preview.app 45 | ``` 46 | 47 | ## Windows 48 | 49 | First, install `graphviz`: 50 | 51 | ```bash 52 | $ choco install graphviz.portable 53 | ``` 54 | For [MSYS2](https://www.msys2.org/) 55 | 56 | ```bash 57 | $ pacman -S mingw-w64-x86_64-graphviz 58 | ``` 59 | 60 | Try it. 61 | 62 | ```bash 63 | $ go get github.com/poloxue/modv 64 | $ go mod graph | modv | dot -T svg -o graph.svg; start graph.svg 65 | ``` 66 | 67 | # Demo 68 | 69 | If MacOS, tye the following: 70 | 71 | ```bash 72 | $ git clone https://github.com/poloxue/testmod 73 | $ cd testmod 74 | $ go mod graph | modv | dot -T png | open -f -a /System/Applications/Preview.app 75 | ``` 76 | 77 | Output: 78 | 79 | ![](http://blogimg.poloxue.com/0014-go-mod-graph-visible-04.png) 80 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/poloxue/modv 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /graph.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "strings" 9 | "text/template" 10 | ) 11 | 12 | var graphTemplate = `digraph { 13 | {{- if eq .direction "horizontal" -}} 14 | rankdir=LR; 15 | {{ end -}} 16 | node [shape=box]; 17 | {{ range $mod, $modId := .mods -}} 18 | {{ $modId }} [label="{{ $mod }}"]; 19 | {{ end -}} 20 | {{- range $modId, $depModIds := .dependencies -}} 21 | {{- range $_, $depModId := $depModIds -}} 22 | {{ $modId }} -> {{ $depModId }}; 23 | {{ end -}} 24 | {{- end -}} 25 | } 26 | ` 27 | 28 | type ModuleGraph struct { 29 | Reader io.Reader 30 | 31 | Mods map[string]int 32 | Dependencies map[int][]int 33 | } 34 | 35 | func NewModuleGraph(r io.Reader) *ModuleGraph { 36 | return &ModuleGraph{ 37 | Reader: r, 38 | 39 | Mods: make(map[string]int), 40 | Dependencies: make(map[int][]int), 41 | } 42 | } 43 | 44 | func (m *ModuleGraph) Parse() error { 45 | bufReader := bufio.NewReader(m.Reader) 46 | 47 | serialID := 1 48 | for { 49 | relationBytes, err := bufReader.ReadBytes('\n') 50 | if err != nil { 51 | if err == io.EOF { 52 | return nil 53 | } 54 | return err 55 | } 56 | 57 | relation := bytes.Split(relationBytes, []byte(" ")) 58 | mod, depMod := strings.TrimSpace(string(relation[0])), strings.TrimSpace(string(relation[1])) 59 | 60 | mod = strings.Replace(mod, "@", "\n", 1) 61 | depMod = strings.Replace(depMod, "@", "\n", 1) 62 | 63 | modId, ok := m.Mods[mod] 64 | if !ok { 65 | modId = serialID 66 | m.Mods[mod] = modId 67 | serialID += 1 68 | } 69 | 70 | depModId, ok := m.Mods[depMod] 71 | if !ok { 72 | depModId = serialID 73 | m.Mods[depMod] = depModId 74 | serialID += 1 75 | } 76 | 77 | m.Dependencies[modId] = append(m.Dependencies[modId], depModId) 78 | } 79 | } 80 | 81 | func (m *ModuleGraph) Render(w io.Writer) error { 82 | templ, err := template.New("graph").Parse(graphTemplate) 83 | if err != nil { 84 | return fmt.Errorf("templ.Parse: %v", err) 85 | } 86 | 87 | var direction string 88 | if len(m.Dependencies) > 15 { 89 | direction = "horizontal" 90 | } 91 | 92 | if err := templ.Execute(w, map[string]interface{}{ 93 | "mods": m.Mods, 94 | "dependencies": m.Dependencies, 95 | "direction": direction, 96 | }); err != nil { 97 | return fmt.Errorf("templ.Execute: %v", err) 98 | } 99 | 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /graph_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "testing" 8 | ) 9 | 10 | func TestModuleGraph_Parse(t *testing.T) { 11 | type args struct { 12 | reader io.Reader 13 | } 14 | tests := []struct { 15 | name string 16 | args args 17 | want []byte 18 | }{ 19 | { 20 | name: "full", 21 | args: args{ 22 | bytes.NewReader([]byte(`github.com/poloxue/testmod golang.org/x/text@v0.3.2 23 | github.com/poloxue/testmod rsc.io/quote/v3@v3.1.0 24 | github.com/poloxue/testmod rsc.io/sampler@v1.3.1 25 | golang.org/x/text@v0.3.2 golang.org/x/tools@v0.0.0-20180917221912-90fa682c2a6e 26 | rsc.io/quote/v3@v3.1.0 rsc.io/sampler@v1.3.0 27 | rsc.io/sampler@v1.3.1 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c 28 | rsc.io/sampler@v1.3.0 golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c`))}, 29 | want: []byte(`digraph { 30 | node [shape=box]; 31 | 1 [label="github.com/poloxue/testmod"]; 32 | 2 [label="golang.org/x/text@v0.3.2"]; 33 | 3 [label="rsc.io/quote/v3@v3.1.0"]; 34 | 4 [label="rsc.io/sampler@v1.3.1"]; 35 | 5 [label="golang.org/x/tools@v0.0.0-20180917221912-90fa682c2a6e"]; 36 | 6 [label="rsc.io/sampler@v1.3.0"]; 37 | 7 [label="golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c"] 38 | 1 -> 2; 39 | 1 -> 3; 40 | 1 -> 4; 41 | 2 -> 5; 42 | 3 -> 6; 43 | 4 -> 7; 44 | 6 -> 7; 45 | }`), 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | moduleGraph := NewModuleGraph(tt.args.reader) 51 | moduleGraph.Parse() 52 | for k, v := range moduleGraph.Mods { 53 | fmt.Println(v, k) 54 | } 55 | 56 | for k, v := range moduleGraph.Dependencies { 57 | fmt.Println(k) 58 | fmt.Println(v) 59 | fmt.Println() 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | ) 8 | 9 | func PrintUsage() { 10 | fmt.Printf("\nUsages:\n\n") 11 | switch runtime.GOOS { 12 | case "darwin": 13 | fmt.Printf("\tgo mod graph | modv | dot -T svg | open -f -a /System/Applications/Preview.app") 14 | case "linux": 15 | fmt.Printf("\tgo mod graph | modv | dot -T svg -o /tmp/modv.svg | xdg-open /tmp/modv.svg") 16 | case "windows": 17 | fmt.Printf("\tgo mod graph | modv | dot -T png -o graph.png; start graph.png") 18 | } 19 | 20 | fmt.Printf("\n\n") 21 | } 22 | 23 | func main() { 24 | info, err := os.Stdin.Stat() 25 | if err != nil { 26 | fmt.Println("os.Stdin.Stat:", err) 27 | PrintUsage() 28 | os.Exit(1) 29 | } 30 | 31 | if info.Mode()&os.ModeNamedPipe == 0 { 32 | fmt.Println("command err: command is intended to work with pipes.") 33 | PrintUsage() 34 | os.Exit(1) 35 | } 36 | 37 | mg := NewModuleGraph(os.Stdin) 38 | if err := mg.Parse(); err != nil { 39 | fmt.Println("mg.Parse: ", err) 40 | PrintUsage() 41 | os.Exit(1) 42 | } 43 | 44 | if err := mg.Render(os.Stdout); err != nil { 45 | fmt.Println("mg.Render: ", err) 46 | PrintUsage() 47 | os.Exit(1) 48 | } 49 | } 50 | --------------------------------------------------------------------------------