1package bootstrap
2
3import (
4	"bytes"
5	"fmt"
6	"html/template"
7	"io/ioutil"
8	"path/filepath"
9	"reflect"
10
11	"github.com/google/blueprint"
12	"github.com/google/blueprint/bootstrap/bpdoc"
13	"github.com/google/blueprint/pathtools"
14)
15
16// ModuleTypeDocs returns a list of bpdoc.ModuleType objects that contain information relevant
17// to generating documentation for module types supported by the primary builder.
18func ModuleTypeDocs(ctx *blueprint.Context, factories map[string]reflect.Value) ([]*bpdoc.Package, error) {
19	// Find the module that's marked as the "primary builder", which means it's
20	// creating the binary that we'll use to generate the non-bootstrap
21	// build.ninja file.
22	var primaryBuilders []*goBinary
23	var minibp *goBinary
24	ctx.VisitAllModulesIf(isBootstrapBinaryModule,
25		func(module blueprint.Module) {
26			binaryModule := module.(*goBinary)
27			if binaryModule.properties.PrimaryBuilder {
28				primaryBuilders = append(primaryBuilders, binaryModule)
29			}
30			if ctx.ModuleName(binaryModule) == "minibp" {
31				minibp = binaryModule
32			}
33		})
34
35	if minibp == nil {
36		panic("missing minibp")
37	}
38
39	var primaryBuilder *goBinary
40	switch len(primaryBuilders) {
41	case 0:
42		// If there's no primary builder module then that means we'll use minibp
43		// as the primary builder.
44		primaryBuilder = minibp
45
46	case 1:
47		primaryBuilder = primaryBuilders[0]
48
49	default:
50		return nil, fmt.Errorf("multiple primary builder modules present")
51	}
52
53	pkgFiles := make(map[string][]string)
54	ctx.VisitDepsDepthFirst(primaryBuilder, func(module blueprint.Module) {
55		switch m := module.(type) {
56		case (*goPackage):
57			pkgFiles[m.properties.PkgPath] = pathtools.PrefixPaths(m.properties.Srcs,
58				filepath.Join(SrcDir, ctx.ModuleDir(m)))
59		default:
60			panic(fmt.Errorf("unknown dependency type %T", module))
61		}
62	})
63
64	mergedFactories := make(map[string]reflect.Value)
65	for moduleType, factory := range factories {
66		mergedFactories[moduleType] = factory
67	}
68
69	for moduleType, factory := range ctx.ModuleTypeFactories() {
70		if _, exists := mergedFactories[moduleType]; !exists {
71			mergedFactories[moduleType] = reflect.ValueOf(factory)
72		}
73	}
74
75	return bpdoc.AllPackages(pkgFiles, mergedFactories, ctx.ModuleTypePropertyStructs())
76}
77
78func writeDocs(ctx *blueprint.Context, filename string) error {
79	moduleTypeList, err := ModuleTypeDocs(ctx, nil)
80	if err != nil {
81		return err
82	}
83
84	buf := &bytes.Buffer{}
85
86	unique := 0
87
88	tmpl, err := template.New("file").Funcs(map[string]interface{}{
89		"unique": func() int {
90			unique++
91			return unique
92		}}).Parse(fileTemplate)
93	if err != nil {
94		return err
95	}
96
97	err = tmpl.Execute(buf, moduleTypeList)
98	if err != nil {
99		return err
100	}
101
102	err = ioutil.WriteFile(filename, buf.Bytes(), 0666)
103	if err != nil {
104		return err
105	}
106
107	return nil
108}
109
110const (
111	fileTemplate = `
112<html>
113<head>
114<title>Build Docs</title>
115<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
116<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
117<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
118</head>
119<body>
120<h1>Build Docs</h1>
121<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
122  {{range .}}
123    <p>{{.Text}}</p>
124    {{range .ModuleTypes}}
125      {{ $collapseIndex := unique }}
126      <div class="panel panel-default">
127        <div class="panel-heading" role="tab" id="heading{{$collapseIndex}}">
128          <h2 class="panel-title">
129            <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{{$collapseIndex}}" aria-expanded="false" aria-controls="collapse{{$collapseIndex}}">
130               {{.Name}}
131            </a>
132          </h2>
133        </div>
134      </div>
135      <div id="collapse{{$collapseIndex}}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading{{$collapseIndex}}">
136        <div class="panel-body">
137          <p>{{.Text}}</p>
138          {{range .PropertyStructs}}
139            <p>{{.Text}}</p>
140            {{template "properties" .Properties}}
141          {{end}}
142        </div>
143      </div>
144    {{end}}
145  {{end}}
146</div>
147</body>
148</html>
149
150{{define "properties"}}
151  <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
152    {{range .}}
153      {{$collapseIndex := unique}}
154      {{if .Properties}}
155        <div class="panel panel-default">
156          <div class="panel-heading" role="tab" id="heading{{$collapseIndex}}">
157            <h4 class="panel-title">
158              <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse{{$collapseIndex}}" aria-expanded="false" aria-controls="collapse{{$collapseIndex}}">
159                 {{.Name}}{{range .OtherNames}}, {{.}}{{end}}
160              </a>
161            </h4>
162          </div>
163        </div>
164        <div id="collapse{{$collapseIndex}}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading{{$collapseIndex}}">
165          <div class="panel-body">
166            <p>{{.Text}}</p>
167            {{range .OtherTexts}}<p>{{.}}</p>{{end}}
168            {{template "properties" .Properties}}
169          </div>
170        </div>
171      {{else}}
172        <div>
173          <h4>{{.Name}}{{range .OtherNames}}, {{.}}{{end}}</h4>
174          <p>{{.Text}}</p>
175          {{range .OtherTexts}}<p>{{.}}</p>{{end}}
176          <p><i>Type: {{.Type}}</i></p>
177          {{if .Default}}<p><i>Default: {{.Default}}</i></p>{{end}}
178        </div>
179      {{end}}
180    {{end}}
181  </div>
182{{end}}
183`
184)
185