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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b010001..c1eeb36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.28.3
+* Support a new `{@youtube}` directive in documentation comments to embed
+ YouTube videos.
+
## 0.28.2
* Add empty CSS classes in spans around the names of entities so Dashing can pick
them up. (flutter/flutter#27654, #1929)
diff --git a/dartdoc_options.yaml b/dartdoc_options.yaml
index 0bacf62..797bade 100644
--- a/dartdoc_options.yaml
+++ b/dartdoc_options.yaml
@@ -1,4 +1,4 @@
dartdoc:
linkToSource:
root: '.'
- uriTemplate: 'https://github.com/dart-lang/dartdoc/blob/v0.28.2/%f%#L%l%'
+ uriTemplate: 'https://github.com/dart-lang/dartdoc/blob/v0.28.3/%f%#L%l%'
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 8e03022..baa36d9 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -3270,6 +3270,7 @@
_rawDocs = documentationComment ?? '';
_rawDocs = stripComments(_rawDocs) ?? '';
_rawDocs = _injectExamples(_rawDocs);
+ _rawDocs = _injectYouTube(_rawDocs);
_rawDocs = _injectAnimations(_rawDocs);
_rawDocs = _stripHtmlAndAddToIndex(_rawDocs);
}
@@ -3291,6 +3292,7 @@
// Must evaluate tools first, in case they insert any other directives.
_rawDocs = await _evaluateTools(_rawDocs);
_rawDocs = _injectExamples(_rawDocs);
+ _rawDocs = _injectYouTube(_rawDocs);
_rawDocs = _injectAnimations(_rawDocs);
_rawDocs = _stripMacroTemplatesAndAddToIndex(_rawDocs);
_rawDocs = _stripHtmlAndAddToIndex(_rawDocs);
@@ -4054,6 +4056,108 @@
}
}
+ /// Replace {@youtube ...} in API comments with some HTML to embed
+ /// a YouTube video.
+ ///
+ /// Syntax:
+ ///
+ /// {@youtube WIDTH HEIGHT URL}
+ ///
+ /// Example:
+ ///
+ /// {@youtube 560 315 https://www.youtube.com/watch?v=oHg5SJYRHA0}
+ ///
+ /// Which will embed a YouTube player into the page that plays the specified
+ /// video.
+ ///
+ /// The width and height must be positive integers specifying the dimensions
+ /// of the video in pixels. The height and width are used to calculate the
+ /// aspect ratio of the video; the video is always rendered to take up all
+ /// available horizontal space to accommodate different screen sizes on
+ /// desktop and mobile.
+ ///
+ /// The video URL must have the following format:
+ /// https://www.youtube.com/watch?v=oHg5SJYRHA0. This format can usually be
+ /// found in the address bar of the browser when viewing a YouTube video.
+ String _injectYouTube(String rawDocs) {
+ // Matches all youtube directives (even some invalid ones). This is so
+ // we can give good error messages if the directive is malformed, instead of
+ // just silently emitting it as-is.
+ final RegExp basicAnimationRegExp = new RegExp(r'''{@youtube\s+([^}]+)}''');
+
+ // Matches YouTube IDs from supported YouTube URLs.
+ final RegExp validYouTubeUrlRegExp =
+ new RegExp('https://www\.youtube\.com/watch\\?v=([^&]+)\$');
+
+ return rawDocs.replaceAllMapped(basicAnimationRegExp, (basicMatch) {
+ final ArgParser parser = new ArgParser();
+ final ArgResults args = _parseArgs(basicMatch[1], parser, 'youtube');
+ if (args == null) {
+ // Already warned about an invalid parameter if this happens.
+ return '';
+ }
+ final List<String> positionalArgs = args.rest.sublist(0);
+ if (positionalArgs.length != 3) {
+ warn(PackageWarning.invalidParameter,
+ message: 'Invalid @youtube directive, "${basicMatch[0]}"\n'
+ 'YouTube directives must be of the form "{@youtube WIDTH '
+ 'HEIGHT URL}"');
+ return '';
+ }
+
+ final int width = int.tryParse(positionalArgs[0]);
+ if (width == null || width <= 0) {
+ warn(PackageWarning.invalidParameter,
+ message: 'A @youtube directive has an invalid width, '
+ '"${positionalArgs[0]}". The width must be a positive integer.');
+ }
+
+ final int height = int.tryParse(positionalArgs[1]);
+ if (height == null || height <= 0) {
+ warn(PackageWarning.invalidParameter,
+ message: 'A @youtube directive has an invalid height, '
+ '"${positionalArgs[1]}". The height must be a positive integer.');
+ }
+
+ final Match url = validYouTubeUrlRegExp.firstMatch(positionalArgs[2]);
+ if (url == null) {
+ warn(PackageWarning.invalidParameter,
+ message: 'A @youtube directive has an invalid URL: '
+ '"${positionalArgs[2]}". Supported YouTube URLs have the '
+ 'follwing format: https://www.youtube.com/watch?v=oHg5SJYRHA0.');
+ return '';
+ }
+ final String youTubeId = url.group(url.groupCount);
+ final String aspectRatio = (height / width * 100).toStringAsFixed(2);
+
+ // Blank lines before and after, and no indenting at the beginning and end
+ // is needed so that Markdown doesn't confuse this with code, so be
+ // careful of whitespace here.
+ return '''
+
+<p style="position: relative;
+ padding-top: $aspectRatio%;">
+ <iframe src="https://www.youtube.com/embed/$youTubeId?rel=0"
+ frameborder="0"
+ allow="accelerometer;
+ autoplay;
+ encrypted-media;
+ gyroscope;
+ picture-in-picture"
+ allowfullscreen
+ style="position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;">
+ </iframe>
+</p>
+
+'''; // String must end at beginning of line, or following inline text will be
+ // indented.
+ });
+ }
+
/// Replace {@animation ...} in API comments with some HTML to manage an
/// MPEG 4 video as an animation.
///
diff --git a/lib/src/version.dart b/lib/src/version.dart
index ba71677..156b762 100644
--- a/lib/src/version.dart
+++ b/lib/src/version.dart
@@ -1,2 +1,2 @@
// Generated code. Do not modify.
-const packageVersion = '0.28.2';
+const packageVersion = '0.28.3';
diff --git a/pubspec.yaml b/pubspec.yaml
index acb8f14..1936e70 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: dartdoc
# Run `grind build` after updating.
-version: 0.28.2
+version: 0.28.3
author: Dart Team <misc@dartlang.org>
description: A documentation generator for Dart.
homepage: https://github.com/dart-lang/dartdoc
diff --git a/test/model_test.dart b/test/model_test.dart
index 6e2e8f5..450ffa8 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -897,6 +897,123 @@
});
});
+ group('YouTube Errors', () {
+ Class documentationErrors;
+ Method withYouTubeWrongParams;
+ Method withYouTubeBadWidth;
+ Method withYouTubeBadHeight;
+ Method withYouTubeInvalidUrl;
+ Method withYouTubeUrlWithAdditionalParameters;
+
+ setUpAll(() {
+ documentationErrors = errorLibrary.classes
+ .firstWhere((c) => c.name == 'DocumentationErrors')
+ ..documentation;
+ withYouTubeWrongParams = documentationErrors.allInstanceMethods
+ .firstWhere((m) => m.name == 'withYouTubeWrongParams')
+ ..documentation;
+ withYouTubeBadWidth = documentationErrors.allInstanceMethods
+ .firstWhere((m) => m.name == 'withYouTubeBadWidth')
+ ..documentation;
+ withYouTubeBadHeight = documentationErrors.allInstanceMethods
+ .firstWhere((m) => m.name == 'withYouTubeBadHeight')
+ ..documentation;
+ withYouTubeInvalidUrl = documentationErrors.allInstanceMethods
+ .firstWhere((m) => m.name == 'withYouTubeInvalidUrl')
+ ..documentation;
+ withYouTubeUrlWithAdditionalParameters = documentationErrors.allInstanceMethods
+ .firstWhere((m) => m.name == 'withYouTubeUrlWithAdditionalParameters')
+ ..documentation;
+ });
+
+ test("warns on youtube video with missing parameters", () {
+ expect(
+ packageGraphErrors.packageWarningCounter.hasWarning(
+ withYouTubeWrongParams,
+ PackageWarning.invalidParameter,
+ 'Invalid @youtube directive, "{@youtube https://youtu.be/oHg5SJYRHA0}"\n'
+ 'YouTube directives must be of the form "{@youtube WIDTH HEIGHT URL}"'),
+ isTrue);
+ });
+ test("warns on youtube video with non-integer width", () {
+ expect(
+ packageGraphErrors.packageWarningCounter.hasWarning(
+ withYouTubeBadWidth,
+ PackageWarning.invalidParameter,
+ 'A @youtube directive has an invalid width, "100px". The width '
+ 'must be a positive integer.'),
+ isTrue);
+ });
+ test("warns on youtube video with non-integer height", () {
+ expect(
+ packageGraphErrors.packageWarningCounter.hasWarning(
+ withYouTubeBadHeight,
+ PackageWarning.invalidParameter,
+ 'A @youtube directive has an invalid height, "100px". The height '
+ 'must be a positive integer.'),
+ isTrue);
+ });
+ test("warns on youtube video with invalid video URL", () {
+ expect(
+ packageGraphErrors.packageWarningCounter.hasWarning(
+ withYouTubeInvalidUrl,
+ PackageWarning.invalidParameter,
+ 'A @youtube directive has an invalid URL: '
+ '"http://host/path/to/video.mp4". Supported YouTube URLs have '
+ 'the follwing format: '
+ 'https://www.youtube.com/watch?v=oHg5SJYRHA0.'),
+ isTrue);
+ });
+ test("warns on youtube video with extra parameters in URL", () {
+ expect(
+ packageGraphErrors.packageWarningCounter.hasWarning(
+ withYouTubeUrlWithAdditionalParameters,
+ PackageWarning.invalidParameter,
+ 'A @youtube directive has an invalid URL: '
+ '"https://www.youtube.com/watch?v=yI-8QHpGIP4&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=5". '
+ 'Supported YouTube URLs have the follwing format: '
+ 'https://www.youtube.com/watch?v=oHg5SJYRHA0.'),
+ isTrue);
+ });
+ });
+
+ group('YouTube', () {
+ Class dog;
+ Method withYouTubeWatchUrl;
+ Method withYouTubeInOneLineDoc;
+ Method withYouTubeInline;
+
+ setUpAll(() {
+ dog = exLibrary.classes.firstWhere((c) => c.name == 'Dog');
+ withYouTubeWatchUrl = dog.allInstanceMethods
+ .firstWhere((m) => m.name == 'withYouTubeWatchUrl');
+ withYouTubeInOneLineDoc = dog.allInstanceMethods
+ .firstWhere((m) => m.name == 'withYouTubeInOneLineDoc');
+ withYouTubeInline = dog.allInstanceMethods
+ .firstWhere((m) => m.name == 'withYouTubeInline');
+ });
+
+ test("renders a YouTube video within the method documentation with correct aspect ratio", () {
+ expect(withYouTubeWatchUrl.documentation,
+ contains('<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"'));
+ // Video is 560x315, which means height is 56.25% of width.
+ expect(withYouTubeWatchUrl.documentation, contains('padding-top: 56.25%;'));
+ });
+ test("Doesn't place YouTube video in one line doc", () {
+ expect(
+ withYouTubeInOneLineDoc.oneLineDoc,
+ isNot(contains(
+ '<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"')));
+ expect(withYouTubeInOneLineDoc.documentation,
+ contains('<iframe src="https://www.youtube.com/embed/oHg5SJYRHA0?rel=0"'));
+ });
+ test("Handles YouTube video inline properly", () {
+ // Make sure it doesn't have a double-space before the continued line,
+ // which would indicate to Markdown to indent the line.
+ expect(withYouTubeInline.documentation, isNot(contains(' works')));
+ });
+ });
+
group('Animation Errors', () {
Class documentationErrors;
Method withInvalidNamedAnimation;
@@ -1790,7 +1907,7 @@
});
test('get methods', () {
- expect(Dog.publicInstanceMethods, hasLength(19));
+ expect(Dog.publicInstanceMethods, hasLength(22));
});
test('get operators', () {
@@ -1865,7 +1982,10 @@
'withNamedAnimation',
'withPrivateMacro',
'withQuotedNamedAnimation',
- 'withUndefinedMacro'
+ 'withUndefinedMacro',
+ 'withYouTubeInline',
+ 'withYouTubeInOneLineDoc',
+ 'withYouTubeWatchUrl',
]));
});
diff --git a/testing/test_package/lib/example.dart b/testing/test_package/lib/example.dart
index 7929db7..5344182 100644
--- a/testing/test_package/lib/example.dart
+++ b/testing/test_package/lib/example.dart
@@ -354,6 +354,23 @@
/// Don't define this: {@macro ThatDoesNotExist}
void withUndefinedMacro() {}
+ /// YouTube video method
+ ///
+ /// {@youtube 560 315 https://www.youtube.com/watch?v=oHg5SJYRHA0}
+ /// More docs
+ void withYouTubeWatchUrl() {}
+
+ /// YouTube video in one line doc {@youtube 100 100 https://www.youtube.com/watch?v=oHg5SJYRHA0}
+ ///
+ /// This tests to see that we do the right thing if the animation is in
+ /// the one line doc above.
+ void withYouTubeInOneLineDoc() {}
+
+ /// YouTube video inline in text.
+ ///
+ /// Tests to see that an inline {@youtube 100 100 https://www.youtube.com/watch?v=oHg5SJYRHA0} works as expected.
+ void withYouTubeInline() {}
+
/// Animation method
///
/// {@animation 100 100 http://host/path/to/video.mp4}
diff --git a/testing/test_package_doc_errors/lib/doc_errors.dart b/testing/test_package_doc_errors/lib/doc_errors.dart
index 7cb5ed3..6e7cdcd 100644
--- a/testing/test_package_doc_errors/lib/doc_errors.dart
+++ b/testing/test_package_doc_errors/lib/doc_errors.dart
@@ -4,6 +4,34 @@
// This is the place to put documentation that has errors in it:
// This package is expected to fail.
abstract class DocumentationErrors {
+ /// Malformed YouTube video method with wrong parameters
+ ///
+ /// {@youtube https://youtu.be/oHg5SJYRHA0}
+ /// More docs
+ void withYouTubeWrongParams() {}
+
+ /// Malformed YouTube video method with non-integer width
+ ///
+ /// {@youtube 100px 100 https://youtu.be/oHg5SJYRHA0}
+ /// More docs
+ void withYouTubeBadWidth() {}
+
+ /// Malformed YouTube video method with non-integer height
+ ///
+ /// {@youtube 100 100px https://youtu.be/oHg5SJYRHA0}
+ /// More docs
+ void withYouTubeBadHeight() {}
+
+ /// YouTube video with an invalid URL.
+ ///
+ /// {@youtube 100 100 http://host/path/to/video.mp4}
+ void withYouTubeInvalidUrl() {}
+
+ /// YouTube video with extra parameters in URL.
+ ///
+ /// {@youtube 100 100 https://www.youtube.com/watch?v=yI-8QHpGIP4&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=5}
+ void withYouTubeUrlWithAdditionalParameters() {}
+
/// Animation method with invalid name
///
/// {@animation 100 100 http://host/path/to/video.mp4 id=2isNot-A-ValidName}