blob: 27b81d1d1444114502b56dab45b2b5a98ec41c56 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:html/dom.dart' as dom;
import '../common_util.dart';
import '../queries/index.dart';
import 'ast.dart';
dom.Element hr() => dom.Element.tag('hr');
dom.Element h1(dom.Node content) => dom.Element.tag('h1')..append(content);
dom.Element h2(dom.Node content) => dom.Element.tag('h2')..append(content);
dom.Element h3(dom.Node content) => dom.Element.tag('h3')..append(content);
dom.Element div(dom.Node content) => dom.Element.tag('div')..append(content);
dom.Element divChildren(Iterable<dom.Node> content) =>
dom.Element.tag('div')..nodes.addAll(content);
dom.Element ul(Iterable<dom.Node> content) =>
dom.Element.tag('ul')..nodes.addAll(content);
dom.Element li(dom.Node content) => dom.Element.tag('li')..append(content);
dom.Element liChildren(Iterable<dom.Node> content) =>
dom.Element.tag('li')..nodes.addAll(content);
dom.Element span(dom.Node content) => dom.Element.tag('span')..append(content);
dom.Element spanChildren(Iterable<dom.Node> content) =>
dom.Element.tag('span')..nodes.addAll(content);
dom.Node text(String content) => dom.Text(content);
/// A renderer into HTML, supporting any level of nested nodes.
class HtmlRenderer extends Renderer {
// ignore: prefer_interpolation_to_compose_strings
static final String _css = '''
html {
font-size: 100%;
}
body {
font-family: Noto, Roboto, Arial, Helvetica, sans-serif;
font-size: 1.0rem;
background-color: white;
color: black;
}
ul {
margin-left: 0;
padding-left: 1rem;
}
ul ul {
padding-left: 2rem;
border-left: 3px solid #f77;
background-color: #f1f1f1;
list-style-type: square;
list-style-position: outside;
}
ul ul ul {
padding-left: 3rem;
}
body>ul>li div:last-child {
margin-bottom: 0.8rem;
}
/* Make deeply nested items smaller */
body>ul>li li {
font-size: 0.8rem;
}
''' +
Color.values.map((c) => '''
.${c.className} {
color: ${c.colorName};
font-weight: bold;
}
''').join('\n');
@override
void render(StringSink output, Iterable<Query> queries) {
final document = dom.Document.html('''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
</html>
''');
document.head.append(document.createElement('style')..innerHtml = _css);
final body = document.body;
bool first = true;
for (final query in queries) {
if (!first) {
body.append(hr());
}
first = false;
body
..append(h2(text(query.name)))
..append(div(text(query.getDescription())));
final result = query.distill().export();
body.append(ul([
for (final node in result) liChildren(_renderNode(node, indent: 0))
]));
}
output.writeln(document.outerHtml);
}
Iterable<dom.Node> _renderNode(AnyNode node, {int indent = 0}) => [
if (node.title != null) _renderTitle(node.title),
if (indent == 0)
for (final child in node.children ?? [])
divChildren(_renderNode(child, indent: indent + 1))
else if ((node.children ?? []).isNotEmpty)
ul([
for (final child in node.children)
..._renderNode(child, indent: indent + 1).map(li)
])
];
dom.Node _renderTitle(Title title) {
if (title is StringPiece) {
// Dart analysis failed to narrow the type here..
// ignore: avoid_as
return _renderStringPiece(title as StringPiece);
} else if (title is UniqueSymbolSizeRecord) {
return spanChildren([
_renderStringPiece(title.name),
text(': ${formatSize(title.tally.size)} (${title.tally.size}), '
'${title.tally.count} total symbols'),
div(spanChildren([
text('In categories: '),
...title.categories.map(_renderStringPiece).expand((e) => [
e,
text(' '),
]),
])),
if (title.rustCrates.isNotEmpty)
div(spanChildren([
text('In rust crates: '),
...title.rustCrates.map(_renderStringPiece).expand((e) => [
e,
text(' '),
]),
])),
]);
} else if (title is SizeRecord) {
return spanChildren([
_renderStringPiece(title.name),
text(': ${formatSize(title.tally.size)} (${title.tally.size}), '
'${title.tally.count} total symbols')
]);
} else {
throw Exception('Unsupported $title');
}
}
dom.Node _renderStringPiece(StringPiece piece) {
if (piece is AddColor) {
return _colorize(
spanChildren(piece.details.map(_renderStringPiece)), piece.color);
} else if (piece is StyledString) {
return spanChildren(piece.details.map(_renderStringPiece));
} else if (piece is Plain) {
return text(piece.text);
} else {
throw Exception('Unsupported $piece');
}
}
dom.Element _colorize(dom.Element str, Color color) =>
str..classes.add(color.className);
}
extension _css on Color {
String get className => 'codesize-color-$colorName';
// All codepaths in the switch will always return.
String get colorName {
switch (this) {
case Color.gray:
return 'gray';
case Color.green:
return 'green';
case Color.white:
// Since terminal background is black, but webpage is white,
// we'll swap around the black and white color in HTML.
return 'black';
}
throw Exception('Unreachable');
}
}