@@ -2878,6 +2878,7 @@ abstract class ModelElement extends Canonicalization
28782878 _rawDocs = computeDocumentationComment ?? '' ;
28792879 _rawDocs = stripComments (_rawDocs) ?? '' ;
28802880 _rawDocs = _injectExamples (_rawDocs);
2881+ _rawDocs = _injectAnimations (_rawDocs);
28812882 _rawDocs = _stripMacroTemplatesAndAddToIndex (_rawDocs);
28822883 }
28832884 return _rawDocs;
@@ -3527,24 +3528,156 @@ abstract class ModelElement extends Canonicalization
35273528 });
35283529 }
35293530
3530- /// Replace {@macro ...} in API comments with the contents of the macro
3531+ /// Replace {@animation ...} in API comments with some HTML to manage an
3532+ /// MPEG 4 video as an animation.
35313533 ///
35323534 /// Syntax:
35333535 ///
3534- /// {@macro NAME}
3536+ /// {@animation NAME WIDTH HEIGHT URL}
3537+ ///
3538+ /// Example:
3539+ ///
3540+ /// {@animation my_video 300 300 https://example.com/path/to/video.mp4}
3541+ ///
3542+ /// Which will render the HTML necessary for embedding a simple click-to-play
3543+ /// HTML5 video player with no controls.
3544+ ///
3545+ /// The NAME should be a unique name that is a valid javascript identifier,
3546+ /// and will be used as the id for the video tag.
3547+ ///
3548+ /// The width and height must be integers specifying the dimensions of the
3549+ /// video file in pixels.
3550+ String _injectAnimations (String rawDocs) {
3551+ // Matches all animation directives (even some invalid ones). This is so
3552+ // we can give good error messages if the directive is malformed, instead of
3553+ // just silently emitting it as-is.
3554+ final RegExp basicAnimationRegExp =
3555+ new RegExp (r'''{@animation\s+([^}]+)}''' );
3556+
3557+ // Animations have four parameters, and the last one can be surrounded by
3558+ // quotes (which are ignored). This RegExp is used to validate the directive
3559+ // for the correct number of parameters.
3560+ final RegExp animationRegExp =
3561+ new RegExp (r'''{@animation\s+([^}\s]+)\s+([^}\s]+)\s+([^}\s]+)'''
3562+ r'''\s+['"]?([^}]+)['"]?}''' );
3563+
3564+ // Matches valid javascript identifiers.
3565+ final RegExp validNameRegExp = new RegExp (r'^[a-zA-Z_][a-zA-Z0-9_]*$' );
3566+
3567+ // Keeps names unique.
3568+ final Set <String > uniqueNames = new Set <String >();
3569+
3570+ return rawDocs.replaceAllMapped (basicAnimationRegExp, (basicMatch) {
3571+ final Match match = animationRegExp.firstMatch (basicMatch[0 ]);
3572+ if (match == null ) {
3573+ warn (PackageWarning .invalidParameter,
3574+ message: 'Invalid @animation directive: ${basicMatch [0 ]}\n '
3575+ 'Animation directives must be of the form: {@animation NAME '
3576+ 'WIDTH HEIGHT URL}' );
3577+ return '' ;
3578+ }
3579+ String name = match[1 ];
3580+ if (! validNameRegExp.hasMatch (name)) {
3581+ warn (PackageWarning .invalidParameter,
3582+ message: 'An animation has an invalid name: $name . The name can '
3583+ 'only contain letters, numbers and underscores.' );
3584+ return '' ;
3585+ } else {
3586+ if (uniqueNames.contains (name)) {
3587+ warn (PackageWarning .invalidParameter,
3588+ message:
3589+ 'An animation has a non-unique name: $name . Animation names '
3590+ 'must be unique.' );
3591+ return '' ;
3592+ }
3593+ uniqueNames.add (name);
3594+ }
3595+ int width;
3596+ try {
3597+ width = int .parse (match[2 ]);
3598+ } on FormatException {
3599+ warn (PackageWarning .invalidParameter,
3600+ message: 'An animation has an invalid width ($name ): ${match [2 ]}. The '
3601+ 'width must be an integer.' );
3602+ return '' ;
3603+ }
3604+ int height;
3605+ try {
3606+ height = int .parse (match[3 ]);
3607+ } on FormatException {
3608+ warn (PackageWarning .invalidParameter,
3609+ message: 'An animation has an invalid height ($name ): ${match [3 ]}. The '
3610+ 'height must be an integer.' );
3611+ return '' ;
3612+ }
3613+ Uri movieUrl;
3614+ try {
3615+ movieUrl = Uri .parse (match[4 ]);
3616+ } on FormatException catch (e) {
3617+ warn (PackageWarning .invalidParameter,
3618+ message: 'An animation URL could not be parsed ($name ): ${match [4 ]}\n $e ' );
3619+ return '' ;
3620+ }
3621+ final String overlayName = '${name }_play_button_' ;
3622+
3623+ // Blank lines before and after, and no indenting at the beginning and end
3624+ // is needed so that Markdown doesn't confuse this with code, so be
3625+ // careful of whitespace here.
3626+ return '''
3627+
3628+ <div style="position: relative;">
3629+ <div id="${overlayName }"
3630+ onclick="if ($name .paused) {
3631+ $name .play();
3632+ this.style.display = 'none';
3633+ } else {
3634+ $name .pause();
3635+ this.style.display = 'block';
3636+ }"
3637+ style="position:absolute;
3638+ width:${width }px;
3639+ height:${height }px;
3640+ z-index:100000;
3641+ background-position: center;
3642+ background-repeat: no-repeat;
3643+ background-image: url(static-assets/play_button.svg);">
3644+ </div>
3645+ <video id="$name "
3646+ style="width:${width }px; height:${height }px;"
3647+ onclick="if (this.paused) {
3648+ this.play();
3649+ $overlayName .style.display = 'none';
3650+ } else {
3651+ this.pause();
3652+ $overlayName .style.display = 'block';
3653+ }" loop>
3654+ <source src="$movieUrl " type="video/mp4"/>
3655+ </video>
3656+ </div>
3657+
3658+ ''' ; // String must end at beginning of line, or following inline text will be
3659+ // indented.
3660+ });
3661+ }
3662+
3663+ /// Replace {@macro ...} in API comments with the contents of the macro
3664+ ///
3665+ /// Syntax:
3666+ ///
3667+ /// {@macro NAME}
35353668 ///
35363669 /// Example:
35373670 ///
35383671 /// You define the template in any comment for a documentable entity like:
35393672 ///
3540- /// { @template foo}
3673+ /// { @template foo}
35413674 /// Foo contents!
3542- /// { @endtemplate}
3675+ /// { @endtemplate}
35433676 ///
35443677 /// and them somewhere use it like this:
35453678 ///
35463679 /// Some comments
3547- /// { @macro foo}
3680+ /// { @macro foo}
35483681 /// More comments
35493682 ///
35503683 /// Which will render
@@ -3564,13 +3697,13 @@ abstract class ModelElement extends Canonicalization
35643697 });
35653698 }
35663699
3567- /// Parse { @template ...} in API comments and store them in the index on the package.
3700+ /// Parse { @template ...} in API comments and store them in the index on the package.
35683701 ///
35693702 /// Syntax:
35703703 ///
3571- /// { @template NAME}
3704+ /// { @template NAME}
35723705 /// The contents of the macro
3573- /// { @endtemplate}
3706+ /// { @endtemplate}
35743707 ///
35753708 String _stripMacroTemplatesAndAddToIndex (String rawDocs) {
35763709 final templateRegExp = new RegExp (
@@ -4133,6 +4266,9 @@ class PackageGraph extends Canonicalization
41334266 // so bracket with a triple quote for defense.
41344267 warningMessage = 'generic type handled as HTML: """${message }"""' ;
41354268 break ;
4269+ case PackageWarning .invalidParameter:
4270+ warningMessage = 'invalid parameter to dartdoc directive: ${message }' ;
4271+ break ;
41364272 }
41374273
41384274 List <String > messageParts = [warningMessage];
@@ -5434,7 +5570,14 @@ class PackageBuilder {
54345570 // TODO(jcollins-g): explode this into detailed command line options.
54355571 if (config.showWarnings) {
54365572 for (PackageWarning kind in PackageWarning .values) {
5437- warningOptions.warn (kind);
5573+ switch (kind) {
5574+ case PackageWarning .invalidParameter:
5575+ warningOptions.error (kind);
5576+ break ;
5577+ default :
5578+ warningOptions.warn (kind);
5579+ break ;
5580+ }
54385581 }
54395582 }
54405583 return warningOptions;
0 commit comments