github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/contrib/scripts/check-and-generate-port-registry.go (about) 1 //usr/bin/go run $0 $@ ; exit 2 package main 3 4 import ( 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "math" 11 "os" 12 "path/filepath" 13 "sort" 14 "strings" 15 ) 16 17 const ( 18 inputDir = "configs/coins" 19 outputFile = "docs/ports.md" 20 ) 21 22 type PortInfo struct { 23 CoinName string 24 BlockbookInternalPort uint16 25 BlockbookPublicPort uint16 26 BackendRPCPort uint16 27 BackendServicePorts map[string]uint16 28 } 29 30 type PortInfoSlice []*PortInfo 31 32 type Config struct { 33 Coin struct { 34 Name string `json:"name"` 35 } 36 Ports map[string]uint16 `json:"ports"` 37 } 38 39 func checkPorts() int { 40 ports := make(map[uint16][]string) 41 status := 0 42 43 files, err := ioutil.ReadDir(inputDir) 44 if err != nil { 45 panic(err) 46 } 47 48 for _, fi := range files { 49 if fi.IsDir() || fi.Name()[0] == '.' { 50 continue 51 } 52 53 path := filepath.Join(inputDir, fi.Name()) 54 f, err := os.Open(path) 55 if err != nil { 56 panic(fmt.Errorf("%s: %s", path, err)) 57 } 58 defer f.Close() 59 60 v := Config{} 61 d := json.NewDecoder(f) 62 err = d.Decode(&v) 63 if err != nil { 64 panic(fmt.Errorf("%s: json: %s", path, err)) 65 } 66 67 if _, ok := v.Ports["blockbook_internal"]; !ok { 68 fmt.Printf("%s: missing blockbook_internal port\n", v.Coin.Name) 69 status = 1 70 } 71 if _, ok := v.Ports["blockbook_public"]; !ok { 72 fmt.Printf("%s: missing blockbook_public port\n", v.Coin.Name) 73 status = 1 74 } 75 if _, ok := v.Ports["backend_rpc"]; !ok { 76 fmt.Printf("%s: missing backend_rpc port\n", v.Coin.Name) 77 status = 1 78 } 79 80 for _, port := range v.Ports { 81 if port > 0 { 82 ports[port] = append(ports[port], v.Coin.Name) 83 } 84 } 85 } 86 87 for port, coins := range ports { 88 if len(coins) > 1 { 89 fmt.Printf("port %d: registered by %q\n", port, coins) 90 status = 1 91 } 92 } 93 94 if status != 0 { 95 fmt.Println("Got some errors") 96 } 97 return status 98 } 99 100 func main() { 101 output := "stdout" 102 if len(os.Args) > 1 { 103 if len(os.Args) == 2 && os.Args[1] == "-w" { 104 output = outputFile 105 } else { 106 fmt.Fprintf(os.Stderr, "Usage: %s [-w]\n", filepath.Base(os.Args[0])) 107 fmt.Fprintf(os.Stderr, " -w write output to %s instead of stdout\n", outputFile) 108 os.Exit(1) 109 } 110 } 111 112 status := checkPorts() 113 if status != 0 { 114 os.Exit(status) 115 } 116 117 slice, err := loadPortInfo(inputDir) 118 if err != nil { 119 panic(err) 120 } 121 122 sortPortInfo(slice) 123 124 err = writeMarkdown(output, slice) 125 if err != nil { 126 panic(err) 127 } 128 } 129 130 func loadPortInfo(dir string) (PortInfoSlice, error) { 131 files, err := ioutil.ReadDir(dir) 132 if err != nil { 133 return nil, err 134 } 135 136 items := make(PortInfoSlice, 0, len(files)) 137 138 for _, fi := range files { 139 if fi.IsDir() || fi.Name()[0] == '.' { 140 continue 141 } 142 143 path := filepath.Join(dir, fi.Name()) 144 f, err := os.Open(path) 145 if err != nil { 146 return nil, fmt.Errorf("%s: %s", path, err) 147 } 148 defer f.Close() 149 150 v := Config{} 151 d := json.NewDecoder(f) 152 err = d.Decode(&v) 153 if err != nil { 154 return nil, fmt.Errorf("%s: json: %s", path, err) 155 } 156 157 item := &PortInfo{CoinName: v.Coin.Name, BackendServicePorts: map[string]uint16{}} 158 for k, v := range v.Ports { 159 if v == 0 { 160 continue 161 } 162 163 switch k { 164 case "blockbook_internal": 165 item.BlockbookInternalPort = v 166 case "blockbook_public": 167 item.BlockbookPublicPort = v 168 case "backend_rpc": 169 item.BackendRPCPort = v 170 default: 171 if len(k) > 8 && k[:8] == "backend_" { 172 item.BackendServicePorts[k[8:]] = v 173 } 174 } 175 } 176 177 items = append(items, item) 178 } 179 180 return items, nil 181 } 182 183 func sortPortInfo(slice PortInfoSlice) { 184 // normalizes values in order to sort zero values at the bottom of the slice 185 normalize := func(a, b uint16) (uint16, uint16) { 186 if a == 0 { 187 a = math.MaxUint16 188 } 189 if b == 0 { 190 b = math.MaxUint16 191 } 192 return a, b 193 } 194 195 // sort values by BlockbookPublicPort, then by BackendRPCPort and finally by 196 // CoinName; zero values are sorted at the bottom of the slice 197 sort.Slice(slice, func(i, j int) bool { 198 a, b := normalize(slice[i].BlockbookPublicPort, slice[j].BlockbookPublicPort) 199 200 if a < b { 201 return true 202 } 203 if a > b { 204 return false 205 } 206 207 a, b = normalize(slice[i].BackendRPCPort, slice[j].BackendRPCPort) 208 209 if a < b { 210 return true 211 } 212 if a > b { 213 return false 214 } 215 216 return strings.Compare(slice[i].CoinName, slice[j].CoinName) == -1 217 }) 218 } 219 220 func writeMarkdown(output string, slice PortInfoSlice) error { 221 var ( 222 buf bytes.Buffer 223 err error 224 ) 225 226 fmt.Fprintf(&buf, "# Registry of ports\n\n") 227 228 header := []string{"coin", "blockbook internal port", "blockbook public port", "backend rpc port", "backend service ports (zmq)"} 229 writeTable(&buf, header, slice) 230 231 fmt.Fprintf(&buf, "\n> NOTE: This document is generated from coin definitions in `configs/coins`.\n") 232 233 out := os.Stdout 234 if output != "stdout" { 235 out, err = os.OpenFile(output, os.O_CREATE|os.O_WRONLY, 0644) 236 if err != nil { 237 return err 238 } 239 defer out.Close() 240 } 241 242 n, err := out.Write(buf.Bytes()) 243 if err != nil { 244 return err 245 } 246 if n < len(buf.Bytes()) { 247 return io.ErrShortWrite 248 } 249 250 return nil 251 } 252 253 func writeTable(w io.Writer, header []string, slice PortInfoSlice) { 254 rows := make([][]string, len(slice)) 255 for i, item := range slice { 256 row := make([]string, len(header)) 257 row[0] = item.CoinName 258 if item.BlockbookInternalPort > 0 { 259 row[1] = fmt.Sprintf("%d", item.BlockbookInternalPort) 260 } 261 if item.BlockbookPublicPort > 0 { 262 row[2] = fmt.Sprintf("%d", item.BlockbookPublicPort) 263 } 264 if item.BackendRPCPort > 0 { 265 row[3] = fmt.Sprintf("%d", item.BackendRPCPort) 266 } 267 268 svcPorts := make([]string, 0, len(item.BackendServicePorts)) 269 for k, v := range item.BackendServicePorts { 270 var s string 271 if k == "message_queue" { 272 s = fmt.Sprintf("%d", v) 273 } else { 274 s = fmt.Sprintf("%d %s", v, k) 275 } 276 svcPorts = append(svcPorts, s) 277 } 278 279 row[4] = strings.Join(svcPorts, ", ") 280 281 rows[i] = row 282 } 283 284 padding := make([]int, len(header)) 285 for column := range header { 286 padding[column] = len(header[column]) 287 288 for _, row := range rows { 289 padding[column] = maxInt(padding[column], len(row[column])) 290 } 291 } 292 293 content := make([][]string, 0, len(rows)+2) 294 295 content = append(content, paddedRow(header, padding)) 296 content = append(content, delim("-", padding)) 297 298 for _, row := range rows { 299 content = append(content, paddedRow(row, padding)) 300 } 301 302 for _, row := range content { 303 fmt.Fprintf(w, "|%s|\n", strings.Join(row, "|")) 304 } 305 } 306 307 func maxInt(a, b int) int { 308 if a > b { 309 return a 310 } 311 return b 312 } 313 314 func paddedRow(row []string, padding []int) []string { 315 out := make([]string, len(row)) 316 for i := 0; i < len(row); i++ { 317 format := fmt.Sprintf(" %%-%ds ", padding[i]) 318 out[i] = fmt.Sprintf(format, row[i]) 319 } 320 return out 321 } 322 323 func delim(str string, padding []int) []string { 324 out := make([]string, len(padding)) 325 for i := 0; i < len(padding); i++ { 326 out[i] = strings.Repeat(str, padding[i]+2) 327 } 328 return out 329 }