github.com/jbendotnet/noms@v0.0.0-20190904222105-c43e4293ea92/cmd/noms/splore/noms_splore.go (about)

     1  // Copyright 2017 Attic Labs, Inc. All rights reserved.
     2  // Licensed under the Apache License, version 2.0:
     3  // http://www.apache.org/licenses/LICENSE-2.0
     4  
     5  package splore
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"net"
    11  	"net/http"
    12  	"reflect"
    13  
    14  	"github.com/attic-labs/kingpin"
    15  	"github.com/attic-labs/noms/cmd/util"
    16  	"github.com/attic-labs/noms/go/config"
    17  	"github.com/attic-labs/noms/go/d"
    18  	"github.com/attic-labs/noms/go/spec"
    19  	"github.com/attic-labs/noms/go/types"
    20  	"github.com/attic-labs/noms/go/util/verbose"
    21  	humanize "github.com/dustin/go-humanize"
    22  	"github.com/skratchdot/open-golang/open"
    23  )
    24  
    25  var httpServe = http.Serve // override for tests
    26  
    27  const indexHtml = `<!DOCTYPE html>
    28  <html>
    29  	<head>
    30  		<title>noms splore %s</title>
    31  		<style>
    32  		body { margin: 0; user-select: none; }
    33  		</style>
    34  	</head>
    35  	<body>
    36  		<div id="splore"></div>
    37  		<script src="/out.js"></script>
    38  	</body>
    39  </html>`
    40  
    41  type node struct {
    42  	nodeInfo
    43  	Children []nodeChild `json:"children"`
    44  }
    45  
    46  type nodeInfo struct {
    47  	HasChildren bool   `json:"hasChildren"`
    48  	ID          string `json:"id"`
    49  	Name        string `json:"name"`
    50  }
    51  
    52  type nodeChild struct {
    53  	Key   nodeInfo `json:"key"`
    54  	Label string   `json:"label"`
    55  	Value nodeInfo `json:"value"`
    56  }
    57  
    58  func Cmd(noms *kingpin.Application) (*kingpin.CmdClause, util.KingpinHandler) {
    59  	splore := noms.Command("splore", `Interactively explore a Noms database using a web browser.
    60  See Spelling Objects at https://github.com/attic-labs/noms/blob/master/doc/spelling.md for details on the path argument.
    61  `)
    62  	port := splore.Flag("port", "Server port. Defaults to a random port.").Short('p').Int()
    63  	browser := splore.Flag("browser", "Immediately open a web browser.").Short('b').Bool()
    64  	spec := splore.Arg("database or path", "The noms database or path to splore.").Required().String()
    65  	return splore, func(input string) (exitCode int) {
    66  		return run(&http.ServeMux{}, *port, *browser, *spec)
    67  	}
    68  }
    69  
    70  func run(mux *http.ServeMux, port int, browser bool, spStr string) int {
    71  	var sp spec.Spec
    72  	var getValue func() types.Value
    73  
    74  	cfg := config.NewResolver()
    75  	if pathSp, err := spec.ForPath(cfg.ResolvePathSpec(spStr)); err == nil {
    76  		sp = pathSp
    77  		getValue = func() types.Value { return sp.GetValue() }
    78  	} else if dbSp, err := spec.ForDatabase(cfg.ResolveDbSpec(spStr)); err == nil {
    79  		sp = dbSp
    80  		getValue = func() types.Value { return sp.GetDatabase().Datasets() }
    81  	} else {
    82  		d.CheckError(fmt.Errorf("Not a path or database: %s", spStr))
    83  	}
    84  
    85  	defer sp.Close()
    86  
    87  	req := func(w http.ResponseWriter, contentType string) {
    88  		sp.GetDatabase().Rebase()
    89  		w.Header().Add("Content-Type", contentType)
    90  		w.Header().Add("Cache-Control", "max-age=0,no-cache")
    91  	}
    92  
    93  	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    94  		req(w, "text/html")
    95  		fmt.Fprintf(w, indexHtml, sp.String())
    96  	})
    97  
    98  	mux.HandleFunc("/out.js", func(w http.ResponseWriter, r *http.Request) {
    99  		req(w, "application/javascript")
   100  		// To develop JS, uncomment this line and run `yarn start`:
   101  		//http.ServeFile(w, r, "splore/out.js")
   102  		// To build noms-splore. uncomment this line and run `yarn buildgo`:
   103  		fmt.Fprint(w, outJs)
   104  	})
   105  
   106  	mux.HandleFunc("/getNode", func(w http.ResponseWriter, r *http.Request) {
   107  		req(w, "application/json")
   108  		r.ParseForm()
   109  		id := r.Form.Get("id")
   110  
   111  		var v types.Value
   112  		switch {
   113  		case id == "":
   114  			v = getValue()
   115  		case id[0] == '#':
   116  			abspath, err := spec.NewAbsolutePath(id)
   117  			d.PanicIfError(err)
   118  			v = abspath.Resolve(sp.GetDatabase())
   119  		default:
   120  			path := types.MustParsePath(id)
   121  			v = path.Resolve(getValue(), sp.GetDatabase())
   122  		}
   123  
   124  		if v == nil {
   125  			http.Error(w, `{"error": "not found"}`, http.StatusNotFound)
   126  			return
   127  		}
   128  
   129  		err := json.NewEncoder(w).Encode(node{
   130  			nodeInfo: info(v, id),
   131  			Children: getNodeChildren(v, id),
   132  		})
   133  		d.PanicIfError(err)
   134  	})
   135  
   136  	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
   137  	d.PanicIfError(err)
   138  
   139  	if browser {
   140  		open.Run("http://" + l.Addr().String())
   141  	}
   142  
   143  	if !verbose.Quiet() {
   144  		fmt.Println("Listening on", l.Addr().String())
   145  	}
   146  
   147  	d.PanicIfError(httpServe(l, mux))
   148  	return 0
   149  }
   150  
   151  func getNodeChildren(v types.Value, parentPath string) (children []nodeChild) {
   152  	childPath := func(f string, args ...interface{}) string {
   153  		return parentPath + fmt.Sprintf(f, args...)
   154  	}
   155  
   156  	switch v := v.(type) {
   157  	case types.Bool, types.Number, types.String:
   158  		children = []nodeChild{}
   159  	case types.Blob:
   160  		children = getMetaChildren(v)
   161  		if children == nil {
   162  			children = []nodeChild{}
   163  		}
   164  	case types.List:
   165  		children = getMetaChildren(v)
   166  		if children == nil {
   167  			children = make([]nodeChild, v.Len())
   168  			v.IterAll(func(vi types.Value, i uint64) {
   169  				children[i] = nodeChild{
   170  					Value: info(vi, childPath("[%d]", i)),
   171  				}
   172  			})
   173  		}
   174  	case types.Map:
   175  		children = getMetaChildren(v)
   176  		if children == nil {
   177  			children = make([]nodeChild, v.Len())
   178  			i := 0
   179  			v.IterAll(func(k, v types.Value) {
   180  				children[i] = nodeChild{
   181  					Key:   info(k, childPath("@at(%d)@key", i)),
   182  					Value: info(v, childPath("@at(%d)", i)),
   183  				}
   184  				i++
   185  			})
   186  		}
   187  	case types.Set:
   188  		children = getMetaChildren(v)
   189  		if children == nil {
   190  			children = make([]nodeChild, v.Len())
   191  			i := 0
   192  			v.IterAll(func(v types.Value) {
   193  				children[i] = nodeChild{
   194  					Value: info(v, childPath("@at(%d)", i)),
   195  				}
   196  				i++
   197  			})
   198  		}
   199  	case types.Ref:
   200  		children = []nodeChild{{
   201  			Value: info(v, childPath("@target")),
   202  		}}
   203  	case types.Struct:
   204  		children = make([]nodeChild, v.Len())
   205  		i := 0
   206  		v.IterFields(func(name string, v types.Value) bool {
   207  			children[i] = nodeChild{
   208  				Label: name,
   209  				Value: info(v, childPath(".%s", name)),
   210  			}
   211  			i++
   212  
   213  			return false
   214  		})
   215  	case *types.Type:
   216  		switch d := v.Desc.(type) {
   217  		case types.CompoundDesc:
   218  			children = make([]nodeChild, len(d.ElemTypes))
   219  			for i, t := range d.ElemTypes {
   220  				children[i] = nodeChild{
   221  					Value: info(t, childPath("[%d]", i)),
   222  				}
   223  			}
   224  		case types.StructDesc:
   225  			children = make([]nodeChild, d.Len())
   226  			i := 0
   227  			d.IterFields(func(name string, t *types.Type, optional bool) {
   228  				children[i] = nodeChild{
   229  					Label: name,
   230  					Value: info(t, childPath(".%s", name)),
   231  				}
   232  				i++
   233  			})
   234  		default:
   235  			children = []nodeChild{} // TODO: cycles?
   236  		}
   237  	default:
   238  		panic(fmt.Errorf("unsupported value type %T", v))
   239  	}
   240  	return
   241  }
   242  
   243  func nodeName(v types.Value) string {
   244  	typeName := func(iface interface{}) string {
   245  		return reflect.TypeOf(iface).Name()
   246  	}
   247  
   248  	switch v := v.(type) {
   249  	case types.Bool, types.Number, types.String:
   250  		return fmt.Sprintf("%#v", v)
   251  	case types.Blob:
   252  		return fmt.Sprintf("%s(%s)", typeName(v), humanize.Bytes(v.Len()))
   253  	case types.List, types.Map, types.Set:
   254  		return fmt.Sprintf("%s(%d)", typeName(v), v.(types.Collection).Len())
   255  	case types.Ref:
   256  		kind := v.TargetType().Desc.Kind()
   257  		return fmt.Sprintf("%s#%s", kind.String(), v.TargetHash().String())
   258  	case types.Struct:
   259  		if v.Name() == "" {
   260  			return "{}"
   261  		}
   262  		return v.Name()
   263  	case *types.Type:
   264  		switch d := v.Desc.(type) {
   265  		case types.StructDesc:
   266  			if d.Name == "" {
   267  				return "struct {}"
   268  			}
   269  			return "struct " + d.Name
   270  		default:
   271  			return v.Desc.Kind().String()
   272  		}
   273  	}
   274  	panic("unreachable")
   275  }
   276  
   277  // getMetaChildren returns the nodeChild children, as refs, of v if it's backed
   278  // by a meta sequence, or nil if not.
   279  //
   280  // This isn't exposed directly on the API but for now just guess it:
   281  // - If there are no chunks, it must be a leaf.
   282  // - If there are MORE chunks than the length of the blob/collection then it
   283  //   can only be a leaf with multiple ref values per entry.
   284  // - If there are EQUAL then it could be either, but heuristically assume
   285  //   that it's a leaf with a ref value per entry. It's highly unlikely that a
   286  //   blob/collection will chunk with single elements.
   287  // - If there are LESS then it could be either a chunked blob/collection or a
   288  //   collection of mixed types, but heuristically assume that's it's chunked.
   289  func getMetaChildren(v types.Value) (children []nodeChild) {
   290  	var l uint64
   291  	if col, ok := v.(types.Collection); ok {
   292  		l = col.Len()
   293  	} else {
   294  		l = v.(types.Blob).Len()
   295  	}
   296  
   297  	v.WalkRefs(func(r types.Ref) {
   298  		if r.TargetType().Desc.Kind() == v.Kind() {
   299  			children = append(children, nodeChild{
   300  				Value: info(r, "#"+r.TargetHash().String()),
   301  			})
   302  		}
   303  	})
   304  
   305  	if uint64(len(children)) >= l {
   306  		children = nil
   307  	}
   308  	return
   309  }
   310  
   311  func nodeHasChildren(v types.Value) bool {
   312  	switch k := v.Kind(); k {
   313  	case types.BlobKind, types.BoolKind, types.NumberKind, types.StringKind:
   314  		return false
   315  	case types.RefKind:
   316  		return true
   317  	case types.ListKind, types.SetKind, types.MapKind:
   318  		return v.(types.Collection).Len() > 0
   319  	case types.StructKind:
   320  		return v.(types.Struct).Len() > 0
   321  	case types.TypeKind:
   322  		switch d := v.(*types.Type).Desc.(type) {
   323  		case types.CompoundDesc:
   324  			return len(d.ElemTypes) > 0
   325  		case types.StructDesc:
   326  			return d.Len() > 0
   327  		default:
   328  			return false // TODO: cycles?
   329  		}
   330  	default:
   331  		panic(fmt.Errorf("unreachable kind %s", k.String()))
   332  	}
   333  }
   334  
   335  func info(v types.Value, id string) nodeInfo {
   336  	return nodeInfo{
   337  		HasChildren: nodeHasChildren(v),
   338  		ID:          id,
   339  		Name:        nodeName(v),
   340  	}
   341  }