• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2020 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Generation of reference from protos
16
17'use strict';
18
19const protobufjs = require('protobufjs');
20const fs = require('fs');
21const path = require('path');
22const argv = require('yargs').argv
23
24const PROJECT_ROOT =
25    path.dirname(path.dirname(path.dirname(path.dirname(__filename))));
26
27const visited = {};
28
29
30// Removes \n due to 80col wrapping and preserves only end-of-sentence line
31// breaks.
32function singleLineComment(comment) {
33  comment = comment || '';
34  comment = comment.trim();
35  comment = comment.replace(/\.\n/g, '<br>');
36  comment = comment.replace(/\n/g, ' ');
37  return comment;
38}
39
40function getFullName(pType) {
41  let cur = pType;
42  let name = pType.name;
43  while (cur && cur.parent != cur && cur.parent instanceof protobufjs.Type) {
44    name = `${cur.parent.name}.${name}`;
45    cur = cur.parent;
46  }
47  return name;
48}
49
50function genType(pType, depth) {
51  depth = depth || 0;
52  console.assert(pType instanceof protobufjs.ReflectionObject);
53  const fullName = getFullName(pType);
54  if (fullName in visited)
55    return '';
56  visited[fullName] = true;
57
58  const heading = '#' +
59      '#'.repeat(Math.min(depth, 2));
60  const anchor = depth > 0 ? `{#${fullName}} ` : '';
61  let md = `${heading} ${anchor}${fullName}`;
62  md += '\n';
63  const fileName = path.basename(pType.filename);
64  const relPath = path.relative(PROJECT_ROOT, pType.filename);
65  md += `${(pType.comment || '').replace(/(\n)?^\s*next.*\bid:.*$/img, '')}`;
66  md += `\n\nDefined in [${fileName}](/${relPath})\n\n`;
67
68  const subTypes = [];
69
70  if (pType instanceof protobufjs.Enum) {
71    md += '#### Enum values:\n';
72    md += 'Name | Value | Description\n';
73    md += '---- | ----- | -----------\n';
74    for (const enumName of Object.keys(pType.values)) {
75      const enumVal = pType.values[enumName];
76      const comment = singleLineComment(pType.comments[enumName]);
77      md += `${enumName} | ${enumVal} | ${comment}\n`
78    }
79  } else {
80    md += '#### Fields:\n';
81    md += 'Field | Type | Description\n';
82    md += '----- | ---- | -----------\n';
83
84    for (const fieldName in pType.fields) {
85      const field = pType.fields[fieldName];
86      let type = field.type;
87      if (field.repeated) {
88        type = `${type}[]`;
89      }
90      if (field.resolvedType) {
91        // The TraceConfig proto is linked from the TracePacket reference.
92        // Instead of recursing and generating the TraceConfig types all over
93        // again, just link to the dedicated TraceConfig reference page.
94        if (getFullName(field.resolvedType) === 'TraceConfig') {
95          type = `[${type}](/docs/reference/trace-config-proto.autogen)`;
96        } else {
97          subTypes.push(field.resolvedType);
98          type = `[${type}](#${getFullName(field.resolvedType)})`;
99        }
100      }
101      md += `${fieldName} | ${type} | ${singleLineComment(field.comment)}\n`
102    }
103  }
104  md += '\n\n\n\n';
105
106  for (const subType of subTypes)
107    md += genType(subType, depth + 1);
108
109  return md;
110}
111
112
113function main() {
114  const inProtoFile = argv['i'];
115  const protoName = argv['p'];
116  const outFile = argv['o'];
117  if (!inProtoFile || !protoName) {
118    console.error('Usage: -i input.proto -p protos.RootType [-o out.md]');
119    process.exit(1);
120  }
121
122  const parser = new protobufjs.Root();
123  parser.resolvePath = (_, target) => {
124    if (target == inProtoFile) {
125      // The root proto file passed from the cmdline will be relative to the
126      // root_build_dir (out/xxx) (e.g.: ../../protos/config)
127      return inProtoFile;
128    }
129    // All the other imports, instead, will be relative to the project root
130    // (e.g. protos/config/...)
131    return path.join(PROJECT_ROOT, target);
132  };
133
134  const cfg = parser.loadSync(
135      inProtoFile, {alternateCommentMode: true, keepCase: true});
136  cfg.resolveAll();
137  const traceConfig = cfg.lookup(protoName);
138  const generatedMd = genType(traceConfig);
139  if (outFile) {
140    fs.writeFileSync(outFile, generatedMd);
141  } else {
142    console.log(generatedMd);
143  }
144  process.exit(0);
145}
146
147main();
148