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  }