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 }