Skip to content

Commit 75c571c

Browse files
goderbauerdevoncarew
authored andcommitted
Adding youtube directive for embedding YouTube videos (#1947)
* Adding youtube directive for embedding YouTube videos * ++ * ++ * Simplify URL format * fix test counter * bump pupspec and Changelog * typo * Improve regex + text * grind build * remove extra print * Always use all available horizontal space * Always use all available horizontal space * feedback
1 parent 3ced147 commit 75c571c

File tree

8 files changed

+278
-5
lines changed

8 files changed

+278
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.28.3
2+
* Support a new `{@youtube}` directive in documentation comments to embed
3+
YouTube videos.
4+
15
## 0.28.2
26
* Add empty CSS classes in spans around the names of entities so Dashing can pick
37
them up. (flutter/flutter#27654, #1929)

dartdoc_options.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
dartdoc:
22
linkToSource:
33
root: '.'
4-
uriTemplate: 'https://github.com/dart-lang/dartdoc/blob/v0.28.2/%f%#L%l%'
4+
uriTemplate: 'https://github.com/dart-lang/dartdoc/blob/v0.28.3/%f%#L%l%'

lib/src/model.dart

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3270,6 +3270,7 @@ abstract class ModelElement extends Canonicalization
32703270
_rawDocs = documentationComment ?? '';
32713271
_rawDocs = stripComments(_rawDocs) ?? '';
32723272
_rawDocs = _injectExamples(_rawDocs);
3273+
_rawDocs = _injectYouTube(_rawDocs);
32733274
_rawDocs = _injectAnimations(_rawDocs);
32743275
_rawDocs = _stripHtmlAndAddToIndex(_rawDocs);
32753276
}
@@ -3291,6 +3292,7 @@ abstract class ModelElement extends Canonicalization
32913292
// Must evaluate tools first, in case they insert any other directives.
32923293
_rawDocs = await _evaluateTools(_rawDocs);
32933294
_rawDocs = _injectExamples(_rawDocs);
3295+
_rawDocs = _injectYouTube(_rawDocs);
32943296
_rawDocs = _injectAnimations(_rawDocs);
32953297
_rawDocs = _stripMacroTemplatesAndAddToIndex(_rawDocs);
32963298
_rawDocs = _stripHtmlAndAddToIndex(_rawDocs);
@@ -4054,6 +4056,108 @@ abstract class ModelElement extends Canonicalization
40544056
}
40554057
}
40564058

4059+
/// Replace {@youtube ...} in API comments with some HTML to embed
4060+
/// a YouTube video.
4061+
///
4062+
/// Syntax:
4063+
///
4064+
/// {@youtube WIDTH HEIGHT URL}
4065+
///
4066+
/// Example:
4067+
///
4068+
/// {@youtube 560 315 https://www.youtube.com/watch?v=oHg5SJYRHA0}
4069+
///
4070+
/// Which will embed a YouTube player into the page that plays the specified
4071+
/// video.
4072+
///
4073+
/// The width and height must be positive integers specifying the dimensions
4074+
/// of the video in pixels. The height and width are used to calculate the
4075+
/// aspect ratio of the video; the video is always rendered to take up all
4076+
/// available horizontal space to accommodate different screen sizes on
4077+
/// desktop and mobile.
4078+
///
4079+
/// The video URL must have the following format:
4080+
/// https://www.youtube.com/watch?v=oHg5SJYRHA0. This format can usually be
4081+
/// found in the address bar of the browser when viewing a YouTube video.
4082+
String _injectYouTube(String rawDocs) {
4083+
// Matches all youtube directives (even some invalid ones). This is so
4084+
// we can give good error messages if the directive is malformed, instead of
4085+
// just silently emitting it as-is.
4086+
final RegExp basicAnimationRegExp = new RegExp(r'''{@youtube\s+([^}]+)}''');
4087+
4088+
// Matches YouTube IDs from supported YouTube URLs.
4089+
final RegExp validYouTubeUrlRegExp =
4090+
new RegExp('https://www\.youtube\.com/watch\\?v=([^&]+)\$');
4091+
4092+
return rawDocs.replaceAllMapped(basicAnimationRegExp, (basicMatch) {
4093+
final ArgParser parser = new ArgParser();
4094+
final ArgResults args = _parseArgs(basicMatch[1], parser, 'youtube');
4095+
if (args == null) {
4096+
// Already warned about an invalid parameter if this happens.
4097+
return '';
4098+
}
4099+
final List<String> positionalArgs = args.rest.sublist(0);
4100+
if (positionalArgs.length != 3) {
4101+
warn(PackageWarning.invalidParameter,
4102+
message: 'Invalid @youtube directive, "${basicMatch[0]}"\n'
4103+
'YouTube directives must be of the form "{@youtube WIDTH '
4104+
'HEIGHT URL}"');
4105+
return '';
4106+
}
4107+
4108+
final int width = int.tryParse(positionalArgs[0]);
4109+
if (width == null || width <= 0) {
4110+
warn(PackageWarning.invalidParameter,
4111+
message: 'A @youtube directive has an invalid width, '
4112+
'"${positionalArgs[0]}". The width must be a positive integer.');
4113+
}
4114+
4115+
final int height = int.tryParse(positionalArgs[1]);
4116+
if (height == null || height <= 0) {
4117+
warn(PackageWarning.invalidParameter,
4118+
message: 'A @youtube directive has an invalid height, '
4119+
'"${positionalArgs[1]}". The height must be a positive integer.');
4120+
}
4121+
4122+
final Match url = validYouTubeUrlRegExp.firstMatch(positionalArgs[2]);
4123+
if (url == null) {
4124+
warn(PackageWarning.invalidParameter,
4125+
message: 'A @youtube directive has an invalid URL: '
4126+
'"${positionalArgs[2]}". Supported YouTube URLs have the '
4127+
'follwing format: https://www.youtube.com/watch?v=oHg5SJYRHA0.');
4128+
return '';
4129+
}
4130+
final String youTubeId = url.group(url.groupCount);
4131+
final String aspectRatio = (height / width * 100).toStringAsFixed(2);
4132+
4133+
// Blank lines before and after, and no indenting at the beginning and end
4134+
// is needed so that Markdown doesn't confuse this with code, so be
4135+
// careful of whitespace here.
4136+
return '''
4137+
4138+
<p style="position: relative;
4139+
padding-top: $aspectRatio%;">
4140+
<iframe src="https://www.youtube.com/embed/$youTubeId?rel=0"
4141+
frameborder="0"
4142+
allow="accelerometer;
4143+
autoplay;
4144+
encrypted-media;
4145+
gyroscope;
4146+
picture-in-picture"
4147+
allowfullscreen
4148+
style="position: absolute;
4149+
top: 0;
4150+
left: 0;
4151+
width: 100%;
4152+
height: 100%;">
4153+
</iframe>
4154+
</p>
4155+
4156+
'''; // String must end at beginning of line, or following inline text will be
4157+
// indented.
4158+
});
4159+
}
4160+
40574161
/// Replace &#123;@animation ...&#125; in API comments with some HTML to manage an
40584162
/// MPEG 4 video as an animation.
40594163
///

lib/src/version.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// Generated code. Do not modify.
2-
const packageVersion = '0.28.2';
2+
const packageVersion = '0.28.3';

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: dartdoc
22
# Run `grind build` after updating.
3-
version: 0.28.2
3+
version: 0.28.3
44
author: Dart Team <[email protected]>
55
description: A documentation generator for Dart.
66
homepage: https://github.com/dart-lang/dartdoc

test/model_test.dart

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,123 @@ void main() {
897897
});
898898
});
899899

900+
group('YouTube Errors', () {
901+
Class documentationErrors;
902+
Method withYouTubeWrongParams;
903+
Method withYouTubeBadWidth;
904+
Method withYouTubeBadHeight;
905+
Method withYouTubeInvalidUrl;
906+
Method withYouTubeUrlWithAdditionalParameters;
907+
908+
setUpAll(() {
909+
documentationErrors = errorLibrary.classes
910+
.firstWhere((c) => c.name == 'DocumentationErrors')
911+
..documentation;
912+
withYouTubeWrongParams = documentationErrors.allInstanceMethods
913+
.firstWhere((m) => m.name == 'withYouTubeWrongParams')
914+
..documentation;
915+
withYouTubeBadWidth = documentationErrors.allInstanceMethods
916+
.firstWhere((m) => m.name == 'withYouTubeBadWidth')
917+
..documentation;
918+
withYouTubeBadHeight = documentationErrors.allInstanceMethods
919+
.firstWhere((m) => m.name == 'withYouTubeBadHeight')
920+
..documentation;
921+
withYouTubeInvalidUrl = documentationErrors.allInstanceMethods
922+
.firstWhere((m) => m.name == 'withYouTubeInvalidUrl')
923+
..documentation;
924+
withYouTubeUrlWithAdditionalParameters = documentationErrors.allInstanceMethods
925+
.firstWhere((m) => m.name == 'withYouTubeUrlWithAdditionalParameters')
926+
..documentation;
927+
});
928+
929+
test("warns on youtube video with missing parameters", () {
930+
expect(
931+
packageGraphErrors.packageWarningCounter.hasWarning(
932+
withYouTubeWrongParams,
933+
PackageWarning.invalidParameter,
934+
'Invalid @youtube directive, "{@youtube https://youtu.be/oHg5SJYRHA0}"\n'
935+
'YouTube directives must be of the form "{@youtube WIDTH HEIGHT URL}"'),
936+
isTrue);
937+
});
938+
test("warns on youtube video with non-integer width", () {
939+
expect(
940+
packageGraphErrors.packageWarningCounter.hasWarning(
941+
withYouTubeBadWidth,
942+
PackageWarning.invalidParameter,
943+
'A @youtube directive has an invalid width, "100px". The width '
944+
'must be a positive integer.'),
945+
isTrue);
946+
});
947+
test("warns on youtube video with non-integer height", () {
948+
expect(
949+
packageGraphErrors.packageWarningCounter.hasWarning(
950+
withYouTubeBadHeight,
951+
PackageWarning.invalidParameter,
952+
'A @youtube directive has an invalid height, "100px". The height '
953+
'must be a positive integer.'),
954+
isTrue);
955+
});
956+
test("warns on youtube video with invalid video URL", () {
957+
expect(
958+
packageGraphErrors.packageWarningCounter.hasWarning(
959+
withYouTubeInvalidUrl,
960+
PackageWarning.invalidParameter,
961+
'A @youtube directive has an invalid URL: '
962+
'"http://host/path/to/video.mp4". Supported YouTube URLs have '
963+
'the follwing format: '
964+
'https://www.youtube.com/watch?v=oHg5SJYRHA0.'),
965+
isTrue);
966+
});
967+
test("warns on youtube video with extra parameters in URL", () {
968+
expect(
969+
packageGraphErrors.packageWarningCounter.hasWarning(
970+
withYouTubeUrlWithAdditionalParameters,
971+
PackageWarning.invalidParameter,
972+
'A @youtube directive has an invalid URL: '
973+
'"https://www.youtube.com/watch?v=yI-8QHpGIP4&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=5". '
974+
'Supported YouTube URLs have the follwing format: '
975+
'https://www.youtube.com/watch?v=oHg5SJYRHA0.'),
976+
isTrue);
977+
});
978+
});
979+
980+
group('YouTube', () {
981+
Class dog;
982+
Method withYouTubeWatchUrl;
983+
Method withYouTubeInOneLineDoc;
984+
Method withYouTubeInline;
985+
986+
setUpAll(() {
987+
dog = exLibrary.classes.firstWhere((c) => c.name == 'Dog');
988+
withYouTubeWatchUrl = dog.allInstanceMethods
989+
.firstWhere((m) => m.name == 'withYouTubeWatchUrl');
990+
withYouTubeInOneLineDoc = dog.allInstanceMethods
991+
.firstWhere((m) => m.name == 'withYouTubeInOneLineDoc');
992+
withYouTubeInline = dog.allInstanceMethods
993+
.firstWhere((m) => m.name == 'withYouTubeInline');
994+
});
995+
996+
test("renders a YouTube video within the method documentation with correct aspect ratio", () {
997+
expect(withYouTubeWatchUrl.documentation,
998+
contains('<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"'));
999+
// Video is 560x315, which means height is 56.25% of width.
1000+
expect(withYouTubeWatchUrl.documentation, contains('padding-top: 56.25%;'));
1001+
});
1002+
test("Doesn't place YouTube video in one line doc", () {
1003+
expect(
1004+
withYouTubeInOneLineDoc.oneLineDoc,
1005+
isNot(contains(
1006+
'<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"')));
1007+
expect(withYouTubeInOneLineDoc.documentation,
1008+
contains('<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"'));
1009+
});
1010+
test("Handles YouTube video inline properly", () {
1011+
// Make sure it doesn't have a double-space before the continued line,
1012+
// which would indicate to Markdown to indent the line.
1013+
expect(withYouTubeInline.documentation, isNot(contains(' works')));
1014+
});
1015+
});
1016+
9001017
group('Animation Errors', () {
9011018
Class documentationErrors;
9021019
Method withInvalidNamedAnimation;
@@ -1790,7 +1907,7 @@ void main() {
17901907
});
17911908

17921909
test('get methods', () {
1793-
expect(Dog.publicInstanceMethods, hasLength(19));
1910+
expect(Dog.publicInstanceMethods, hasLength(22));
17941911
});
17951912

17961913
test('get operators', () {
@@ -1865,7 +1982,10 @@ void main() {
18651982
'withNamedAnimation',
18661983
'withPrivateMacro',
18671984
'withQuotedNamedAnimation',
1868-
'withUndefinedMacro'
1985+
'withUndefinedMacro',
1986+
'withYouTubeInline',
1987+
'withYouTubeInOneLineDoc',
1988+
'withYouTubeWatchUrl',
18691989
]));
18701990
});
18711991

testing/test_package/lib/example.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,23 @@ class Dog implements Cat, E {
354354
/// Don't define this: {@macro ThatDoesNotExist}
355355
void withUndefinedMacro() {}
356356

357+
/// YouTube video method
358+
///
359+
/// {@youtube 560 315 https://www.youtube.com/watch?v=oHg5SJYRHA0}
360+
/// More docs
361+
void withYouTubeWatchUrl() {}
362+
363+
/// YouTube video in one line doc {@youtube 100 100 https://www.youtube.com/watch?v=oHg5SJYRHA0}
364+
///
365+
/// This tests to see that we do the right thing if the animation is in
366+
/// the one line doc above.
367+
void withYouTubeInOneLineDoc() {}
368+
369+
/// YouTube video inline in text.
370+
///
371+
/// Tests to see that an inline {@youtube 100 100 https://www.youtube.com/watch?v=oHg5SJYRHA0} works as expected.
372+
void withYouTubeInline() {}
373+
357374
/// Animation method
358375
///
359376
/// {@animation 100 100 http://host/path/to/video.mp4}

testing/test_package_doc_errors/lib/doc_errors.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@ library doc_errors;
44
// This is the place to put documentation that has errors in it:
55
// This package is expected to fail.
66
abstract class DocumentationErrors {
7+
/// Malformed YouTube video method with wrong parameters
8+
///
9+
/// {@youtube https://youtu.be/oHg5SJYRHA0}
10+
/// More docs
11+
void withYouTubeWrongParams() {}
12+
13+
/// Malformed YouTube video method with non-integer width
14+
///
15+
/// {@youtube 100px 100 https://youtu.be/oHg5SJYRHA0}
16+
/// More docs
17+
void withYouTubeBadWidth() {}
18+
19+
/// Malformed YouTube video method with non-integer height
20+
///
21+
/// {@youtube 100 100px https://youtu.be/oHg5SJYRHA0}
22+
/// More docs
23+
void withYouTubeBadHeight() {}
24+
25+
/// YouTube video with an invalid URL.
26+
///
27+
/// {@youtube 100 100 http://host/path/to/video.mp4}
28+
void withYouTubeInvalidUrl() {}
29+
30+
/// YouTube video with extra parameters in URL.
31+
///
32+
/// {@youtube 100 100 https://www.youtube.com/watch?v=yI-8QHpGIP4&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=5}
33+
void withYouTubeUrlWithAdditionalParameters() {}
34+
735
/// Animation method with invalid name
836
///
937
/// {@animation 100 100 http://host/path/to/video.mp4 id=2isNot-A-ValidName}

0 commit comments

Comments
 (0)