github.com/tiagovtristao/plz@v13.4.0+incompatible/src/query/print.go (about) 1 package query 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "reflect" 8 "sort" 9 "strings" 10 "time" 11 12 "github.com/thought-machine/please/src/core" 13 ) 14 15 // Print produces a Python call which would (hopefully) regenerate the same build rule if run. 16 // This is of course not ideal since they were almost certainly created as a java_library 17 // or some similar wrapper rule, but we've lost that information by now. 18 func Print(graph *core.BuildGraph, labels []core.BuildLabel, fields []string) { 19 for _, label := range labels { 20 if len(fields) == 0 { 21 fmt.Fprintf(os.Stdout, "# %s:\n", label) 22 } 23 if len(fields) > 0 { 24 newPrinter(os.Stdout, graph.TargetOrDie(label), 0).PrintFields(fields) 25 } else { 26 newPrinter(os.Stdout, graph.TargetOrDie(label), 0).PrintTarget() 27 } 28 } 29 } 30 31 // specialFields is a mapping of field name -> any special casing relating to how to print it. 32 var specialFields = map[string]func(*printer) (string, bool){ 33 "name": func(p *printer) (string, bool) { 34 return "'" + p.target.Label.Name + "'", true 35 }, 36 "building_description": func(p *printer) (string, bool) { 37 s, ok := p.genericPrint(reflect.ValueOf(p.target.BuildingDescription)) 38 return s, ok && p.target.BuildingDescription != core.DefaultBuildingDescription 39 }, 40 "deps": func(p *printer) (string, bool) { 41 return p.genericPrint(reflect.ValueOf(p.target.DeclaredDependenciesStrict())) 42 }, 43 "exported_deps": func(p *printer) (string, bool) { 44 return p.genericPrint(reflect.ValueOf(p.target.ExportedDependencies())) 45 }, 46 "visibility": func(p *printer) (string, bool) { 47 if len(p.target.Visibility) == 1 && p.target.Visibility[0] == core.WholeGraph[0] { 48 return "['PUBLIC']", true 49 } 50 return p.genericPrint(reflect.ValueOf(p.target.Visibility)) 51 }, 52 "container": func(p *printer) (string, bool) { 53 if p.target.ContainerSettings == nil { 54 return "True", p.target.Containerise 55 } 56 return p.genericPrint(reflect.ValueOf(p.target.ContainerSettings.ToMap())) 57 }, 58 "tools": func(p *printer) (string, bool) { 59 return p.genericPrint(reflect.ValueOf(p.target.AllTools())) 60 }, 61 } 62 63 // fieldPrecedence defines a specific ordering for fields. 64 var fieldPrecedence = map[string]int{ 65 "name": -100, 66 "srcs": -90, 67 "visibility": 90, 68 "deps": 100, 69 } 70 71 // A printer is responsible for creating the output of 'plz query print'. 72 type printer struct { 73 w io.Writer 74 target *core.BuildTarget 75 indent int 76 doneFields map[string]bool 77 error bool // true if something went wrong 78 surroundSyntax bool // true if we are quoting strings or surrounding slices with [] etc. 79 } 80 81 // newPrinter creates a new printer instance. 82 func newPrinter(w io.Writer, target *core.BuildTarget, indent int) *printer { 83 return &printer{ 84 w: w, 85 target: target, 86 indent: indent, 87 doneFields: make(map[string]bool, 50), // Leave enough space for all of BuildTarget's fields. 88 } 89 } 90 91 // printf is an internal function which prints to the internal writer with an indent. 92 func (p *printer) printf(msg string, args ...interface{}) { 93 fmt.Fprint(p.w, strings.Repeat(" ", p.indent)) 94 fmt.Fprintf(p.w, msg, args...) 95 } 96 97 // PrintTarget prints an entire build target. 98 func (p *printer) PrintTarget() { 99 if p.target.IsHashFilegroup { 100 p.printf("hash_filegroup(\n") 101 } else if p.target.IsFilegroup { 102 p.printf("filegroup(\n") 103 } else if p.target.IsRemoteFile { 104 p.printf("remote_file(\n") 105 } else { 106 p.printf("build_rule(\n") 107 } 108 p.surroundSyntax = true 109 p.indent += 4 110 v := reflect.ValueOf(p.target).Elem() 111 t := v.Type() 112 f := make(orderedFields, t.NumField()) 113 for i := 0; i < t.NumField(); i++ { 114 f[i].structIndex = i 115 f[i].printIndex = i 116 if index, present := fieldPrecedence[p.fieldName(t.Field(i))]; present { 117 f[i].printIndex = index 118 } 119 } 120 sort.Sort(f) 121 for _, orderedField := range f { 122 p.printField(t.Field(orderedField.structIndex), v.Field(orderedField.structIndex)) 123 } 124 p.indent -= 4 125 p.printf(")\n\n") 126 } 127 128 // PrintFields prints a subset of fields of a build target. 129 func (p *printer) PrintFields(fields []string) bool { 130 v := reflect.ValueOf(p.target).Elem() 131 for _, field := range fields { 132 f := p.findField(field) 133 if contents, shouldPrint := p.shouldPrintField(f, v.FieldByIndex(f.Index)); shouldPrint { 134 if !strings.HasSuffix(contents, "\n") { 135 contents += "\n" 136 } 137 p.printf("%s", contents) 138 } 139 } 140 return p.error 141 } 142 143 // findField returns the field which would print with the given name. 144 // This isn't as simple as using reflect.Value.FieldByName since the print names 145 // are different to the actual struct names. 146 func (p *printer) findField(field string) reflect.StructField { 147 t := reflect.ValueOf(p.target).Elem().Type() 148 for i := 0; i < t.NumField(); i++ { 149 if f := t.Field(i); p.fieldName(f) == field { 150 return f 151 } 152 } 153 log.Fatalf("Unknown field %s", field) 154 return reflect.StructField{} 155 } 156 157 // fieldName returns the name we'll use to print a field. 158 func (p *printer) fieldName(f reflect.StructField) string { 159 if name := f.Tag.Get("name"); name != "" { 160 return name 161 } 162 // We don't bother specifying on some fields when it's equivalent other than case. 163 return strings.ToLower(f.Name) 164 } 165 166 // printField prints a single field of a build target. 167 func (p *printer) printField(f reflect.StructField, v reflect.Value) { 168 if contents, shouldPrint := p.shouldPrintField(f, v); shouldPrint { 169 name := p.fieldName(f) 170 p.printf("%s = %s,\n", name, contents) 171 p.doneFields[name] = true 172 } 173 } 174 175 // shouldPrintField returns whether we should print a field and what we'd print if we did. 176 func (p *printer) shouldPrintField(f reflect.StructField, v reflect.Value) (string, bool) { 177 if f.Tag.Get("print") == "false" { // Indicates not to print the field. 178 return "", false 179 } else if p.target.IsFilegroup && f.Tag.Get("hide") == "filegroup" { 180 return "", false 181 } 182 name := p.fieldName(f) 183 if p.doneFields[name] { 184 return "", false 185 } 186 if customFunc, present := specialFields[name]; present { 187 return customFunc(p) 188 } 189 return p.genericPrint(v) 190 } 191 192 // genericPrint is the generic print function for a field. 193 func (p *printer) genericPrint(v reflect.Value) (string, bool) { 194 switch v.Kind() { 195 case reflect.Slice: 196 return p.printSlice(v), v.Len() > 0 197 case reflect.Map: 198 return p.printMap(v), v.Len() > 0 199 case reflect.String: 200 return p.quote(v.String()), v.Len() > 0 201 case reflect.Bool: 202 return "True", v.Bool() 203 case reflect.Int, reflect.Int32: 204 return fmt.Sprintf("%d", v.Int()), v.Int() > 0 205 case reflect.Struct, reflect.Interface: 206 if stringer, ok := v.Interface().(fmt.Stringer); ok { 207 return p.quote(stringer.String()), true 208 } 209 return "", false 210 case reflect.Int64: 211 if v.Type().Name() == "Duration" { 212 secs := v.Interface().(time.Duration).Seconds() 213 return fmt.Sprintf("%0.0f", secs), secs > 0.0 214 } 215 } 216 log.Error("Unknown field type %s: %s", v.Kind(), v.Type().Name()) 217 p.error = true 218 return "", false 219 } 220 221 // printSlice prints the representation of a slice field. 222 func (p *printer) printSlice(v reflect.Value) string { 223 if v.Len() == 1 { 224 // Single-element slices are printed on one line 225 elem, _ := p.genericPrint(v.Index(0)) 226 return p.surround("[", elem, "]", "") 227 } 228 s := make([]string, v.Len()) 229 indent := strings.Repeat(" ", p.indent+4) 230 for i := 0; i < v.Len(); i++ { 231 elem, _ := p.genericPrint(v.Index(i)) 232 s[i] = p.surround(indent, elem, ",", "\n") 233 } 234 return p.surround("[\n", strings.Join(s, ""), strings.Repeat(" ", p.indent)+"]", "") 235 } 236 237 // printMap prints the representation of a map field. 238 func (p *printer) printMap(v reflect.Value) string { 239 keys := v.MapKeys() 240 sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() }) 241 s := make([]string, len(keys)) 242 indent := strings.Repeat(" ", p.indent+4) 243 for i, key := range keys { 244 keyElem, _ := p.genericPrint(key) 245 valElem, _ := p.genericPrint(v.MapIndex(key)) 246 s[i] = p.surround(indent, keyElem+": "+valElem, ",", "\n") 247 } 248 return p.surround("{\n", strings.Join(s, ""), strings.Repeat(" ", p.indent)+"}", "") 249 } 250 251 // quote quotes the given string appropriately for the current printing method. 252 func (p *printer) quote(s string) string { 253 if p.surroundSyntax { 254 return "'" + s + "'" 255 } 256 return s 257 } 258 259 // surround surrounds the given string with a prefix and suffix, if appropriate for the current printing method. 260 func (p *printer) surround(prefix, s, suffix, always string) string { 261 if p.surroundSyntax { 262 return prefix + s + suffix + always 263 } 264 return s + always 265 } 266 267 // An orderedField is used to sort the fields into the order we print them in. 268 // This isn't necessarily the same as the order on the struct. 269 type orderedField struct { 270 structIndex, printIndex int 271 } 272 273 type orderedFields []orderedField 274 275 func (f orderedFields) Len() int { return len(f) } 276 func (f orderedFields) Swap(a, b int) { f[a], f[b] = f[b], f[a] } 277 func (f orderedFields) Less(a, b int) bool { return f[a].printIndex < f[b].printIndex }