Skip to content

Commit 39fa08a

Browse files
committed
Better error reporting.
Plumb most errors during a build through to minibuild so that it can report what asset and builder contained the error.
1 parent 260464d commit 39fa08a

21 files changed

+290
-184
lines changed

packages/betwixt/lib/betwixt.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'src/renderer.dart';
88

99
export 'src/betwixt_function.dart';
1010
export 'src/data.dart';
11+
export 'src/error_reporter.dart';
1112

1213
typedef IncludeLoader = Future<String?> Function(String name);
1314

packages/betwixt/lib/src/compiler.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'package:source_span/source_span.dart';
22

33
import '../betwixt.dart';
44
import 'ast.dart';
5-
import 'error_reporter.dart';
65
import 'functions.dart';
76
import 'include_visitor.dart';
87
import 'parser.dart';

packages/betwixt/lib/src/renderer.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import 'package:betwixt/betwixt.dart';
44
import 'package:source_span/source_span.dart';
55

66
import 'ast.dart';
7-
import 'data.dart';
8-
import 'error_reporter.dart';
97
import 'token.dart';
108

119
/// Renders a template.

packages/betwixt/test/render_test.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'dart:async';
22
import 'dart:convert';
33
import 'dart:io';
44

5-
import 'package:betwixt/src/error_reporter.dart';
65
import 'package:path/path.dart' as p;
76
import 'package:source_span/src/span.dart';
87
import 'package:test/test.dart';

packages/chromatophore/lib/chromatophore.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'src/lexer.dart';
55
export 'src/category.dart';
66
export 'src/formatter.dart';
77
export 'src/language.dart';
8+
export 'src/language_provider.dart';
89

910
/// Takes a string of [source], parses it according to [language], and formats
1011
/// the result using [formatter].

packages/chromatophore/lib/markdown.dart

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import 'chromatophore.dart';
88
class HighlightedCodeBlockSyntax extends BlockSyntax {
99
static final _codeFencePattern = RegExp(r'^(\s*)```(.*)$');
1010

11-
final Map<String, Language> _languages;
11+
final LanguageProvider _languages;
1212
final HtmlFormatter _formatter;
1313

1414
@override
1515
RegExp get pattern => _codeFencePattern;
1616

1717
HighlightedCodeBlockSyntax(
18-
{Map<String, Language> languages = const {},
18+
{LanguageProvider languages = const LanguageProvider(),
1919
Map<Category, String>? cssClasses})
2020
: _languages = languages,
2121
_formatter =
@@ -58,11 +58,7 @@ class HighlightedCodeBlockSyntax extends BlockSyntax {
5858

5959
// Don't syntax highlight text.
6060
if (languageName != 'text') {
61-
language = _languages[languageName] ?? Language.find(languageName);
62-
if (language == null) {
63-
print('Cannot find highlighter for "$languageName".');
64-
// TODO: Better error reporting.
65-
}
61+
language = _languages.find(languageName);
6662
}
6763

6864
String code;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'language.dart';
2+
3+
/// Looks up a [Language] for a given name on a markdown code block.
4+
///
5+
/// By default, just delegates to [Language.find()], but users can implement or
6+
/// extend this to do their own lookup and error reporting.
7+
class LanguageProvider {
8+
const LanguageProvider();
9+
10+
/// Find a [Language] that matches [name].
11+
///
12+
/// If none is found and this returns `null`, then the code will not be
13+
/// syntax highlighted and no error is reported.
14+
Language? find(String name) => Language.find(name);
15+
}

packages/journal/lib/src/builder/code_page_builder.dart

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'package:betwixt/betwixt.dart';
44
import 'package:journal/src/markdown.dart';
55
import 'package:minibuild/minibuild.dart';
66

7-
import '../model/template_data.dart';
7+
import '../model/template.dart';
88
import '../model/post.dart';
99

1010
/// Builds a page containing all code snippets.
@@ -50,15 +50,12 @@ class CodePageBuilder extends GroupBuilder<Post> {
5050
}
5151
}
5252

53-
var codeHtml = renderMarkdown(lines);
53+
var codeHtml = renderMarkdown(context, lines);
5454

5555
var postTemplate =
5656
context.input<Template>(Key('betwixt/asset/template/all_code.html'));
57-
58-
// TODO: Pass in error reporter that plumbs through build system.
59-
var html = await postTemplate.render((String property) =>
60-
templateData(context, property, data: {'code': codeHtml}));
61-
57+
var html =
58+
await renderTemplate(postTemplate, context, data: {'code': codeHtml});
6259
context.output(key, html);
6360
}
6461
}
Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:minibuild/minibuild.dart';
22

3+
import '../markdown.dart';
34
import '../model/post.dart';
45

56
/// Converts a Markdown file in the post directory to a [Post] object.
@@ -17,67 +18,74 @@ class PostBuilder extends Builder<StringAsset> {
1718
Future<void> build(
1819
BuildContext context, Key key, StringAsset markdownFile) async {
1920
var match = _titlePattern.firstMatch(key.toString());
20-
// TODO: Better error handling.
21-
if (match == null) throw Exception('Bad post title $key');
21+
if (match == null) {
22+
context
23+
.error('Post filename should look like "1234-56-78-post-title.md".');
24+
return;
25+
}
2226

2327
var year = int.parse(match[1]!);
2428
var month = int.parse(match[2]!);
2529
var day = int.parse(match[3]!);
2630
var titleUri = match[4]!;
2731

28-
var contents = await markdownFile.readString();
29-
var lines = contents.split('\n');
30-
if (lines.isEmpty) {
31-
// TODO: Better error handling.
32-
throw Exception('Empty post');
33-
}
32+
var lines = (await markdownFile.readString()).split('\n');
3433

35-
if (lines[0].trim() != '---') {
36-
// TODO: Better error handling.
37-
throw Exception('Missing frontmatter');
38-
}
39-
40-
var title = '';
34+
String? title;
4135
var tags = <String>[];
4236
var contentLines = <String>[];
4337

44-
for (var i = 1; i < lines.length; i++) {
45-
var line = lines[i].trim();
46-
if (line == '---') {
47-
// TODO: Slow.
48-
contentLines.addAll(lines.skip(i + 1));
49-
break;
50-
} else {
51-
var match = _metadataPattern.firstMatch(line);
52-
if (match != null) {
53-
var key = match[1]!;
54-
var value = match[2]!;
55-
switch (key) {
56-
case 'title':
57-
title = value;
58-
// TODO: Hack. Unquote if quoted.
59-
if (title.startsWith('"')) {
60-
title = title.substring(1, title.length - 1);
61-
title = title.replaceAll('\\"', '"');
62-
}
38+
// Read the frontmatter.
39+
if (lines.isNotEmpty && lines[0].trim() == '---') {
40+
for (var i = 1; i < lines.length; i++) {
41+
var line = lines[i].trim();
42+
if (line == '---') {
43+
// TODO: Slow.
44+
contentLines.addAll(lines.skip(i + 1));
45+
break;
46+
} else {
47+
if (_metadataPattern.firstMatch(line) case var match?) {
48+
var key = match[1]!;
49+
var value = match[2]!;
50+
switch (key) {
51+
case 'title':
52+
title = value;
53+
// TODO: Hack. Unquote if quoted.
54+
if (title.startsWith('"')) {
55+
title = title.substring(1, title.length - 1);
56+
title = title.replaceAll('\\"', '"');
57+
}
6358

64-
case 'tags':
65-
tags = value.split(' ').toList();
66-
tags.sort();
59+
case 'tags':
60+
tags = value.split(' ').toList();
61+
tags.sort();
6762

68-
default:
69-
// TODO: Better error handling.
70-
throw Exception('Unknown metadata: $key');
63+
default:
64+
context.error('Unknown frontmatter metadata key "$key" '
65+
'on line ${i + 1}:\n$line');
66+
}
67+
} else {
68+
context.error('Unparseable frontmatter on line ${i + 1}:\n$line');
7169
}
72-
} else {
73-
// TODO: Better error handling.
74-
throw Exception('Bad metadata: $line');
7570
}
7671
}
72+
} else {
73+
context.error('Missing frontmatter.');
7774
}
7875

76+
if (title == null) {
77+
context.error('Missing post title in frontmatter.');
78+
79+
// Use a temporary title so the rest of the build can continue.
80+
title = key.basenameWithoutExtension;
81+
}
82+
83+
var html = renderMarkdown(context, contentLines);
84+
7985
var postKey = Key.join('post', key.basenameWithoutExtension);
80-
context.output(postKey,
81-
Post(DateTime(year, month, day), title, titleUri, tags, contentLines));
86+
context.output(
87+
postKey,
88+
Post(DateTime(year, month, day), title, titleUri, tags, contentLines,
89+
html));
8290
}
8391
}

packages/journal/lib/src/builder/post_page_builder.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'dart:async';
33
import 'package:betwixt/betwixt.dart';
44
import 'package:minibuild/minibuild.dart';
55

6-
import '../model/template_data.dart';
6+
import '../model/template.dart';
77
import '../model/post.dart';
88

99
/// Builds a page for a post.
@@ -13,10 +13,8 @@ class PostPageBuilder extends Builder<Post> {
1313
var postTemplate =
1414
context.input<Template>(Key('betwixt/asset/template/post.html'));
1515

16-
// TODO: Pass in error reporter that plumbs through build system.
17-
var html = await postTemplate.render((String property) =>
18-
templateData(context, property, postKey: key, post: post));
19-
16+
var html =
17+
await renderTemplate(postTemplate, context, postKey: key, post: post);
2018
context.output(Key.join('build', post.url, 'index.html'), html);
2119
}
2220
}

0 commit comments

Comments
 (0)