// Copyright 2016-2017, Pulumi Corporation. All rights reserved. package main import ( "fmt" "os" "strings" "unicode" "github.com/spf13/cobra" "github.com/pulumi/pulumi-fabric/pkg/compiler/ast" "github.com/pulumi/pulumi-fabric/pkg/pack" "github.com/pulumi/pulumi-fabric/pkg/tokens" "github.com/pulumi/pulumi-fabric/pkg/util/cmdutil" "github.com/pulumi/pulumi-fabric/pkg/util/contract" ) func newPackInfoCmd() *cobra.Command { var printAll bool var printIL bool var printSymbols bool var printExportedSymbols bool var cmd = &cobra.Command{ Use: "info [packages...]", Short: "Print information about one or more packages", Long: "Print information about one or more packages\n" + "\n" + "This command prints metadata, symbol, and/or IL from one or more packages.", Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { // If printAll is true, flip all the flags. if printAll { printIL = true printSymbols = true printExportedSymbols = true } var pkg *pack.Package var err error if len(args) == 0 { // No package specified, just load from the current directory. pwd, locerr := os.Getwd() if locerr != nil { return locerr } if pkg, err = detectPackage(pwd); err != nil { return err } printPackage(pkg, printSymbols, printExportedSymbols, printIL) } else { // Enumerate the list of packages, deserialize them, and print information. var path string for _, arg := range args { pkg, path = readPackageFromArg(arg) if pkg == nil { if pkg, err = detectPackage(path); err != nil { return err } printPackage(pkg, printSymbols, printExportedSymbols, printIL) } } } return nil }), } cmd.PersistentFlags().BoolVarP( &printSymbols, "all", "a", false, "Print everything: the package, symbols, and IL") cmd.PersistentFlags().BoolVarP( &printExportedSymbols, "exports", "e", false, "Print just the exported symbols") cmd.PersistentFlags().BoolVarP( &printIL, "il", "i", false, "Pretty-print the package's IL") cmd.PersistentFlags().BoolVarP( &printSymbols, "symbols", "s", false, "Print a complete listing of all symbols, exported or otherwise") return cmd } func printComment(pc *string, indent string) { // Prints a comment header using the given indentation, wrapping at 100 lines. if pc != nil { prefix := "// " maxlen := 100 - len(indent) // For every tab, chew up 3 more chars (so each one is 4 chars wide). for _, i := range indent { if i == '\t' { maxlen -= 3 } } maxlen -= len(prefix) if maxlen < 40 { maxlen = 40 } c := make([]rune, 0) for _, r := range *pc { c = append(c, r) } for len(c) > 0 { fmt.Print(indent + prefix) // Now, try to split the comment as close to maxlen-3 chars as possible (taking into account indent+"// "), // but don't split words -- only split at whitespace characters if we can help it. six := maxlen for { if len(c) <= six { six = len(c) break } else if unicode.IsSpace(c[six]) { // It's a space, set six to the first non-space character beforehand, and eix to the first // non-space character afterwards. for six > 0 && unicode.IsSpace(c[six-1]) { six-- } break } else if six == 0 { // We hit the start of the string and didn't find any spaces. Start over and try to find the // first space *beyond* the start point (instead of *before*) and use that. six = maxlen + 1 for six < len(c) && !unicode.IsSpace(c[six]) { six++ } break } // We need to keep searching, back up one and try again. six-- } // Print what we've got thus far, plus a newline. fmt.Printf("%v\n", string(c[:six])) // Now find the first non-space character beyond the split point and use that for the remainder. eix := six for eix < len(c) && unicode.IsSpace(c[eix]) { eix++ } c = c[eix:] } } } // printPackage pretty-prints the package metadata. func printPackage(pkg *pack.Package, printSymbols bool, printExports bool, printIL bool) { printComment(pkg.Description, "") fmt.Printf("package \"%v\" {\n", pkg.Name) if pkg.Author != nil { fmt.Printf("%vauthor \"%v\"\n", tab, *pkg.Author) } if pkg.Website != nil { fmt.Printf("%vwebsite \"%v\"\n", tab, *pkg.Website) } if pkg.License != nil { fmt.Printf("%vlicense \"%v\"\n", tab, *pkg.License) } // Print the dependencies: fmt.Printf("%vdependencies [", tab) if pkg.Dependencies != nil && len(*pkg.Dependencies) > 0 { fmt.Printf("\n") for _, dep := range pack.StableDependencies(*pkg.Dependencies) { fmt.Printf("%v%v: \"%v\"\n", tab+tab, dep, (*pkg.Dependencies)[dep]) } fmt.Printf("%v", tab) } fmt.Printf("]\n") // Print the modules (just names by default, or full symbols and/or IL if requested). printModules(pkg, printSymbols, printExports, printIL, tab) fmt.Printf("}\n") } func printModules(pkg *pack.Package, printSymbols bool, printExports bool, printIL bool, indent string) { if pkg.Modules != nil { pkgtok := tokens.NewPackageToken(pkg.Name) for _, name := range ast.StableModules(*pkg.Modules) { mod := (*pkg.Modules)[name] modtok := tokens.NewModuleToken(pkgtok, name) // Print the name. fmt.Printf("%vmodule \"%v\" {", indent, name) // Now, if requested, print the tokens. if printSymbols || printExports { if mod.Exports != nil || mod.Members != nil { fmt.Printf("\n") exports := make(map[tokens.Token]bool) if mod.Exports != nil { // Print the exports. fmt.Printf("%vexports [", indent+tab) if mod.Exports != nil && len(*mod.Exports) > 0 { fmt.Printf("\n") for _, exp := range ast.StableModuleExports(*mod.Exports) { ref := (*mod.Exports)[exp].Referent.Tok fmt.Printf("%v\"%v\" -> \"%v\"\n", indent+tab+tab, exp, ref) exports[ref] = true } fmt.Printf("%v", indent+tab) } fmt.Printf("]\n") } if mod.Members != nil { // Print the members. for _, member := range ast.StableModuleMembers(*mod.Members) { memtok := tokens.NewModuleMemberToken(modtok, member) printModuleMember(memtok, (*mod.Members)[member], printExports, exports, indent+tab) } fmt.Printf("%v", indent) } } } else { // Print a "..." so that it's clear we're omitting information, versus the module being empty. fmt.Printf("...") } fmt.Printf("}\n") } } } func printModuleMember(tok tokens.ModuleMember, member ast.ModuleMember, exportOnly bool, exports map[tokens.Token]bool, indent string) { printComment(member.GetDescription(), indent) if !exportOnly || exports[tokens.Token(tok)] { switch member.GetKind() { case ast.ClassKind: printClass(tokens.Type(tok), member.(*ast.Class), exportOnly, indent) case ast.ModulePropertyKind: printModuleProperty(tok, member.(*ast.ModuleProperty), indent) case ast.ModuleMethodKind: printModuleMethod(tok, member.(*ast.ModuleMethod), indent) default: contract.Failf("Unexpected ModuleMember kind: %v (tok %v)\n", member.GetKind(), tok) } } } func printClass(tok tokens.Type, class *ast.Class, exportOnly bool, indent string) { fmt.Printf("%vclass \"%v\"", indent, tok.Name()) var mods []string if class.Sealed != nil && *class.Sealed { mods = append(mods, "sealed") } if class.Abstract != nil && *class.Abstract { mods = append(mods, "abstract") } if class.Record != nil && *class.Record { mods = append(mods, "record") } if class.Interface != nil && *class.Interface { mods = append(mods, "interface") } if class.Attributes != nil { for _, att := range *class.Attributes { mods = append(mods, "@"+att.Decorator.Tok.String()) } } fmt.Print(modString(mods)) if class.Extends != nil { fmt.Printf("\n%vextends %v", indent+tab+tab, string(class.Extends.Tok)) } if class.Implements != nil { for _, impl := range *class.Implements { fmt.Printf("\n%vimplements %v", indent+tab+tab, string(impl.Tok)) } } fmt.Printf(" {") if class.Members != nil { fmt.Printf("\n") for _, member := range ast.StableClassMembers(*class.Members) { memtok := tokens.NewClassMemberToken(tok, member) printClassMember(memtok, (*class.Members)[member], exportOnly, indent+tab) } fmt.Print(indent) } fmt.Printf("}\n") } func printClassMember(tok tokens.ClassMember, member ast.ClassMember, exportOnly bool, indent string) { printComment(member.GetDescription(), indent) acc := member.GetAccess() if !exportOnly || (acc != nil && *acc == tokens.PublicAccessibility) { switch member.GetKind() { case ast.ClassPropertyKind: printClassProperty(tok.Name(), member.(*ast.ClassProperty), indent) case ast.ClassMethodKind: printClassMethod(tok.Name(), member.(*ast.ClassMethod), indent) default: contract.Failf("Unexpected ClassMember kind: %v\n", member.GetKind()) } } } func printClassProperty(name tokens.ClassMemberName, prop *ast.ClassProperty, indent string) { var mods []string if prop.Access != nil { mods = append(mods, string(*prop.Access)) } if prop.Static != nil && *prop.Static { mods = append(mods, "static") } if prop.Readonly != nil && *prop.Readonly { mods = append(mods, "readonly") } if prop.Attributes != nil { for _, att := range *prop.Attributes { mods = append(mods, "@"+att.Decorator.Tok.String()) } } fmt.Printf("%vproperty \"%v\"%v", indent, name, modString(mods)) if prop.Type != nil { fmt.Printf(": %v", prop.Type.Tok) } if prop.Getter != nil || prop.Setter != nil { fmt.Printf(" {\n") if prop.Getter != nil { printClassMethod(tokens.ClassMemberName("get"), prop.Getter, indent+" ") } if prop.Setter != nil { printClassMethod(tokens.ClassMemberName("set"), prop.Setter, indent+" ") } fmt.Printf("%v}\n", indent) } else { fmt.Printf("\n") } } func printClassMethod(name tokens.ClassMemberName, meth *ast.ClassMethod, indent string) { var mods []string if meth.Access != nil { mods = append(mods, string(*meth.Access)) } if meth.Static != nil && *meth.Static { mods = append(mods, "static") } if meth.Sealed != nil && *meth.Sealed { mods = append(mods, "sealed") } if meth.Abstract != nil && *meth.Abstract { mods = append(mods, "abstract") } if meth.Attributes != nil { for _, att := range *meth.Attributes { mods = append(mods, "@"+att.Decorator.Tok.String()) } } fmt.Printf("%vmethod \"%v\"%v: %v\n", indent, name, modString(mods), funcSig(meth)) } func printModuleMethod(tok tokens.ModuleMember, meth *ast.ModuleMethod, indent string) { fmt.Printf("%vmethod \"%v\": %v\n", indent, tok.Name(), funcSig(meth)) } func printModuleProperty(tok tokens.ModuleMember, prop *ast.ModuleProperty, indent string) { var mods []string if prop.Readonly != nil && *prop.Readonly { mods = append(mods, "readonly") } fmt.Printf("%vproperty \"%v\"%v", indent, tok.Name(), modString(mods)) if prop.Type != nil { fmt.Printf(": %v", prop.Type.Tok) } fmt.Printf("\n") } func modString(mods []string) string { if len(mods) == 0 { return "" } s := " [" for i, mod := range mods { if i > 0 { s += ", " } s += mod } s += "]" return s } // spaces returns a string with the given number of spaces. func spaces(num int) string { return strings.Repeat(" ", num) } // tab is a tab represented as spaces, since some consoles have ridiculously wide true tabs. var tab = spaces(4) func funcSig(fun ast.Function) string { sig := "(" // To create a signature, first concatenate the parameters. params := fun.GetParameters() if params != nil { for i, param := range *params { if i > 0 { sig += ", " } sig += string(param.Name.Ident) var mods []string if param.Attributes != nil { for _, att := range *param.Attributes { mods = append(mods, "@"+att.Decorator.Tok.String()) } } sig += modString(mods) if param.Type != nil { sig += ": " + string(param.Type.Tok) } } } sig += ")" // And then the return type, if present. ret := fun.GetReturnType() if ret != nil { sig += ": " + string(ret.Tok) } return sig }