diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 1d3bb5da1b..0000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "site-shared"] - path = site-shared - url = https://github.com/dart-lang/site-shared diff --git a/packages/camera/camera/README.md b/packages/camera/camera/README.md index 095da3cbbe..ddfbd880de 100644 --- a/packages/camera/camera/README.md +++ b/packages/camera/camera/README.md @@ -1,6 +1,6 @@ # Camera Plugin -<?code-excerpt path-base="excerpts/packages/camera_example"?> +<?code-excerpt path-base="example/lib"?> [](https://pub.dev/packages/camera) diff --git a/packages/camera/camera/example/build.excerpt.yaml b/packages/camera/camera/example/build.excerpt.yaml deleted file mode 100644 index e317efa11c..0000000000 --- a/packages/camera/camera/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true diff --git a/packages/file_selector/file_selector/README.md b/packages/file_selector/file_selector/README.md index 12efb64de7..abb2c34f66 100644 --- a/packages/file_selector/file_selector/README.md +++ b/packages/file_selector/file_selector/README.md @@ -1,6 +1,6 @@ # file_selector -<?code-excerpt path-base="excerpts/packages/file_selector_example"?> +<?code-excerpt path-base="example/lib"?> [](https://pub.dartlang.org/packages/file_selector) @@ -36,7 +36,7 @@ Please also take a look at our [example][example] app. #### Open a single file <?code-excerpt "open_image_page.dart (SingleOpen)"?> -``` dart +```dart const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: <String>['jpg', 'png'], @@ -48,7 +48,7 @@ final XFile? file = #### Open multiple files at once <?code-excerpt "open_multiple_images_page.dart (MultiOpen)"?> -``` dart +```dart const XTypeGroup jpgsTypeGroup = XTypeGroup( label: 'JPEGs', extensions: <String>['jpg', 'jpeg'], diff --git a/packages/file_selector/file_selector/example/build.excerpt.yaml b/packages/file_selector/file_selector/example/build.excerpt.yaml deleted file mode 100644 index e317efa11c..0000000000 --- a/packages/file_selector/file_selector/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true diff --git a/packages/flutter_adaptive_scaffold/README.md b/packages/flutter_adaptive_scaffold/README.md index e9324afbf1..133e192628 100644 --- a/packages/flutter_adaptive_scaffold/README.md +++ b/packages/flutter_adaptive_scaffold/README.md @@ -1,4 +1,4 @@ -<?code-excerpt path-base="excerpts/packages/flutter_adaptive_scaffold_example"?> +<?code-excerpt path-base="example/lib"?> # Adaptive Scaffold @@ -35,75 +35,74 @@ animation should use AdaptiveLayout. <?code-excerpt "adaptive_scaffold_demo.dart (Example)"?> ```dart - @override - Widget build(BuildContext context) { - // Define the children to display within the body at different breakpoints. - final List<Widget> children = <Widget>[ - for (int i = 0; i < 10; i++) - Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - color: const Color.fromARGB(255, 255, 201, 197), - height: 400, - ), - ) - ]; +@override +Widget build(BuildContext context) { + // Define the children to display within the body at different breakpoints. + final List<Widget> children = <Widget>[ + for (int i = 0; i < 10; i++) + Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + color: const Color.fromARGB(255, 255, 201, 197), + height: 400, + ), + ) + ]; - return AdaptiveScaffold( - // An option to override the default breakpoints used for small, medium, - // and large. - smallBreakpoint: const WidthPlatformBreakpoint(end: 700), - mediumBreakpoint: const WidthPlatformBreakpoint(begin: 700, end: 1000), - largeBreakpoint: const WidthPlatformBreakpoint(begin: 1000), - useDrawer: false, - selectedIndex: _selectedTab, - onSelectedIndexChange: (int index) { - setState(() { - _selectedTab = index; - }); - }, - destinations: const <NavigationDestination>[ - NavigationDestination( - icon: Icon(Icons.inbox_outlined), - selectedIcon: Icon(Icons.inbox), - label: 'Inbox', - ), - NavigationDestination( - icon: Icon(Icons.article_outlined), - selectedIcon: Icon(Icons.article), - label: 'Articles', - ), - NavigationDestination( - icon: Icon(Icons.chat_outlined), - selectedIcon: Icon(Icons.chat), - label: 'Chat', - ), - NavigationDestination( - icon: Icon(Icons.video_call_outlined), - selectedIcon: Icon(Icons.video_call), - label: 'Video', - ), - NavigationDestination( - icon: Icon(Icons.home_outlined), - selectedIcon: Icon(Icons.home), - label: 'Inbox', - ), - ], - body: (_) => GridView.count(crossAxisCount: 2, children: children), - smallBody: (_) => ListView.builder( - itemCount: children.length, - itemBuilder: (_, int idx) => children[idx], + return AdaptiveScaffold( + // An option to override the default breakpoints used for small, medium, + // and large. + smallBreakpoint: const WidthPlatformBreakpoint(end: 700), + mediumBreakpoint: const WidthPlatformBreakpoint(begin: 700, end: 1000), + largeBreakpoint: const WidthPlatformBreakpoint(begin: 1000), + useDrawer: false, + selectedIndex: _selectedTab, + onSelectedIndexChange: (int index) { + setState(() { + _selectedTab = index; + }); + }, + destinations: const <NavigationDestination>[ + NavigationDestination( + icon: Icon(Icons.inbox_outlined), + selectedIcon: Icon(Icons.inbox), + label: 'Inbox', ), - // Define a default secondaryBody. - secondaryBody: (_) => Container( - color: const Color.fromARGB(255, 234, 158, 192), + NavigationDestination( + icon: Icon(Icons.article_outlined), + selectedIcon: Icon(Icons.article), + label: 'Articles', ), - // Override the default secondaryBody during the smallBreakpoint to be - // empty. Must use AdaptiveScaffold.emptyBuilder to ensure it is properly - // overridden. - smallSecondaryBody: AdaptiveScaffold.emptyBuilder, - ); - } + NavigationDestination( + icon: Icon(Icons.chat_outlined), + selectedIcon: Icon(Icons.chat), + label: 'Chat', + ), + NavigationDestination( + icon: Icon(Icons.video_call_outlined), + selectedIcon: Icon(Icons.video_call), + label: 'Video', + ), + NavigationDestination( + icon: Icon(Icons.home_outlined), + selectedIcon: Icon(Icons.home), + label: 'Inbox', + ), + ], + body: (_) => GridView.count(crossAxisCount: 2, children: children), + smallBody: (_) => ListView.builder( + itemCount: children.length, + itemBuilder: (_, int idx) => children[idx], + ), + // Define a default secondaryBody. + secondaryBody: (_) => Container( + color: const Color.fromARGB(255, 234, 158, 192), + ), + // Override the default secondaryBody during the smallBreakpoint to be + // empty. Must use AdaptiveScaffold.emptyBuilder to ensure it is properly + // overridden. + smallSecondaryBody: AdaptiveScaffold.emptyBuilder, + ); } ``` diff --git a/packages/flutter_adaptive_scaffold/example/build.excerpt.yaml b/packages/flutter_adaptive_scaffold/example/build.excerpt.yaml deleted file mode 100644 index 5af61234d3..0000000000 --- a/packages/flutter_adaptive_scaffold/example/build.excerpt.yaml +++ /dev/null @@ -1,16 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true - \ No newline at end of file diff --git a/packages/flutter_adaptive_scaffold/example/lib/adaptive_scaffold_demo.dart b/packages/flutter_adaptive_scaffold/example/lib/adaptive_scaffold_demo.dart index 7ed4f2717b..b3ce9d0d70 100644 --- a/packages/flutter_adaptive_scaffold/example/lib/adaptive_scaffold_demo.dart +++ b/packages/flutter_adaptive_scaffold/example/lib/adaptive_scaffold_demo.dart @@ -124,5 +124,5 @@ class _MyHomePageState extends State<MyHomePage> { smallSecondaryBody: AdaptiveScaffold.emptyBuilder, ); } -// #enddocregion +// #enddocregion Example } diff --git a/packages/flutter_markdown/example/lib/demos/markdown_body_shrink_wrap_demo.dart b/packages/flutter_markdown/example/lib/demos/markdown_body_shrink_wrap_demo.dart index f48d6106cb..057e44015e 100644 --- a/packages/flutter_markdown/example/lib/demos/markdown_body_shrink_wrap_demo.dart +++ b/packages/flutter_markdown/example/lib/demos/markdown_body_shrink_wrap_demo.dart @@ -17,16 +17,16 @@ const String _data = ''' '''; const String _notes = ''' -# Shrink wrap demo +# Shrink wrap demo --- ## Overview This example demonstrates how `MarkdownBody`'s `shrinkWrap` property works. -- If `shrinkWrap` is `true`, `MarkdownBody` will take the minimum height that +- If `shrinkWrap` is `true`, `MarkdownBody` will take the minimum height that wraps its content. -- If `shrinkWrap` is `false`, `MarkdownBody` will expand to the maximum allowed +- If `shrinkWrap` is `false`, `MarkdownBody` will expand to the maximum allowed height. '''; diff --git a/packages/google_identity_services_web/README.md b/packages/google_identity_services_web/README.md index 8fc5c34dc7..65214000a7 100644 --- a/packages/google_identity_services_web/README.md +++ b/packages/google_identity_services_web/README.md @@ -1,5 +1,3 @@ -<?code-excerpt path-base="excerpts/packages/google_identity_services_web_example"?> - # google_identity_services_web A JS-interop layer for Google Identity's Sign In With Google SDK. @@ -29,7 +27,7 @@ script tag [as recommended](https://developers.google.com/identity/gsi/web/guide Place the `script` tag in the `<head>` of your site, next to the script tag that loads `flutter.js`, so the browser can downloaded both in parallel: -<?code-excerpt "../../web/index-with-script-tag.html (script-tag)"?> +<?code-excerpt "example/web/index-with-script-tag.html (script-tag)"?> ```html <head> <!-- ··· --> @@ -46,7 +44,7 @@ An alternative way, that downloads the SDK on demand, is to use the **`loadWebSdk`** function provided by the library. A simple location to embed this in a Flutter Web only app can be the `main.dart`: -<?code-excerpt "main.dart (use-loader)"?> +<?code-excerpt "example/lib/main.dart (use-loader)"?> ```dart import 'package:google_identity_services_web/loader.dart' as gis; // ··· diff --git a/packages/google_identity_services_web/example/build.excerpt.yaml b/packages/google_identity_services_web/example/build.excerpt.yaml deleted file mode 100644 index 8b673fd541..0000000000 --- a/packages/google_identity_services_web/example/build.excerpt.yaml +++ /dev/null @@ -1,19 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - - web/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true - generate_for: - - '**/*.dart' - - '**/*.html' diff --git a/packages/google_maps_flutter/google_maps_flutter/README.md b/packages/google_maps_flutter/google_maps_flutter/README.md index b495d0efce..6b89baacd4 100644 --- a/packages/google_maps_flutter/google_maps_flutter/README.md +++ b/packages/google_maps_flutter/google_maps_flutter/README.md @@ -1,6 +1,6 @@ # Google Maps for Flutter -<?code-excerpt path-base="excerpts/packages/google_maps_flutter_example"?> +<?code-excerpt path-base="example/lib"?> [](https://pub.dev/packages/google_maps_flutter) diff --git a/packages/google_maps_flutter/google_maps_flutter/example/build.excerpt.yaml b/packages/google_maps_flutter/google_maps_flutter/example/build.excerpt.yaml deleted file mode 100644 index 2102d25a19..0000000000 --- a/packages/google_maps_flutter/google_maps_flutter/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - # - '**/build/**' - # builders: - # code_excerpter|code_excerpter: - # enabled: true \ No newline at end of file diff --git a/packages/google_maps_flutter/google_maps_flutter_android/README.md b/packages/google_maps_flutter/google_maps_flutter_android/README.md index 4ce5aba583..836ab2c983 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/README.md +++ b/packages/google_maps_flutter/google_maps_flutter_android/README.md @@ -1,6 +1,6 @@ # google\_maps\_flutter\_android -<?code-excerpt path-base="excerpts/packages/google_maps_flutter_example"?> +<?code-excerpt path-base="example/lib"?> The Android implementation of [`google_maps_flutter`][1]. diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/build.excerpt.yaml b/packages/google_maps_flutter/google_maps_flutter_android/example/build.excerpt.yaml deleted file mode 100644 index e317efa11c..0000000000 --- a/packages/google_maps_flutter/google_maps_flutter_android/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index 5e2306cd40..73b9622bb8 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -1,5 +1,5 @@ # Image Picker plugin for Flutter -<?code-excerpt path-base="excerpts/packages/image_picker_example"?> +<?code-excerpt path-base="example/lib"?> [](https://pub.dev/packages/image_picker) @@ -122,7 +122,7 @@ implementations allow delegating to a camera handler by setting a `cameraDelegate` before using `image_picker`, such as in `main()`: <?code-excerpt "readme_excerpts.dart (CameraDelegate)"?> -``` dart +```dart import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; // ··· class MyCameraDelegate extends ImagePickerCameraDelegate { @@ -167,7 +167,7 @@ add a filesystem access ### Example <?code-excerpt "readme_excerpts.dart (Pick)"?> -``` dart +```dart final ImagePicker picker = ImagePicker(); // Pick an image. final XFile? image = await picker.pickImage(source: ImageSource.gallery); diff --git a/packages/image_picker/image_picker/example/build.excerpt.yaml b/packages/image_picker/image_picker/example/build.excerpt.yaml deleted file mode 100644 index e317efa11c..0000000000 --- a/packages/image_picker/image_picker/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true diff --git a/packages/image_picker/image_picker_android/README.md b/packages/image_picker/image_picker_android/README.md index 32bc0206ef..0e66803f4c 100755 --- a/packages/image_picker/image_picker_android/README.md +++ b/packages/image_picker/image_picker_android/README.md @@ -1,4 +1,4 @@ -<?code-excerpt path-base="excerpts/packages/image_picker_example"?> +<?code-excerpt path-base="example/lib"?> # image\_picker\_android diff --git a/packages/image_picker/image_picker_android/example/build.excerpt.yaml b/packages/image_picker/image_picker_android/example/build.excerpt.yaml deleted file mode 100644 index e317efa11c..0000000000 --- a/packages/image_picker/image_picker_android/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true diff --git a/packages/in_app_purchase/in_app_purchase_android/example/build.excerpt.yaml b/packages/in_app_purchase/in_app_purchase_android/example/build.excerpt.yaml deleted file mode 100644 index e317efa11c..0000000000 --- a/packages/in_app_purchase/in_app_purchase_android/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true diff --git a/packages/in_app_purchase/in_app_purchase_android/migration_guide.md b/packages/in_app_purchase/in_app_purchase_android/migration_guide.md index 48a2e47c10..8f0f69b55a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/migration_guide.md +++ b/packages/in_app_purchase/in_app_purchase_android/migration_guide.md @@ -1,4 +1,4 @@ -<?code-excerpt path-base="excerpts/packages/in_app_purchase_android_example"?> +<?code-excerpt path-base="example/lib"?> # Migration Guide from 0.2.x to 0.3.0 Starting November 2023, Android Billing Client V4 is no longer supported, diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md index 1fc9ce3f4f..01355ef852 100644 --- a/packages/local_auth/local_auth/README.md +++ b/packages/local_auth/local_auth/README.md @@ -1,6 +1,6 @@ # local_auth -<?code-excerpt path-base="excerpts/packages/local_auth_example"?> +<?code-excerpt path-base="example/lib"?> This Flutter plugin provides means to perform local, on-device authentication of the user. diff --git a/packages/local_auth/local_auth/example/build.excerpt.yaml b/packages/local_auth/local_auth/example/build.excerpt.yaml deleted file mode 100644 index e317efa11c..0000000000 --- a/packages/local_auth/local_auth/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true diff --git a/packages/path_provider/path_provider/README.md b/packages/path_provider/path_provider/README.md index 7bee646658..0c658f66a3 100644 --- a/packages/path_provider/path_provider/README.md +++ b/packages/path_provider/path_provider/README.md @@ -1,5 +1,5 @@ # path_provider -<?code-excerpt path-base="excerpts/packages/path_provider_example"?> +<?code-excerpt path-base="example/lib"?> [](https://pub.dev/packages/path_provider) diff --git a/packages/path_provider/path_provider/example/build.excerpt.yaml b/packages/path_provider/path_provider/example/build.excerpt.yaml deleted file mode 100644 index e317efa11c..0000000000 --- a/packages/path_provider/path_provider/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true diff --git a/packages/pigeon/example/README.md b/packages/pigeon/example/README.md index a310f46b13..078b40259a 100644 --- a/packages/pigeon/example/README.md +++ b/packages/pigeon/example/README.md @@ -1,4 +1,4 @@ -<?code-excerpt path-base="excerpts/packages/pigeon_example"?> +<?code-excerpt path-base="app"?> # Pigeon Examples The examples here will cover basic usage. For a more thorough set of examples, @@ -11,7 +11,7 @@ Begin by configuring pigeon at the top of the `.dart` input file. In actual use, you would include only the languages needed for your project. -<?code-excerpt "../../app/pigeons/messages.dart (config)"?> +<?code-excerpt "pigeons/messages.dart (config)"?> ```dart @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', @@ -49,7 +49,7 @@ host platform from Flutter. This is the Pigeon file that describes the interface that will be used to call from Flutter to the host-platform. -<?code-excerpt "../../app/pigeons/messages.dart (host-definitions)"?> +<?code-excerpt "pigeons/messages.dart (host-definitions)"?> ```dart enum Code { one, two } @@ -80,8 +80,8 @@ abstract class ExampleHostApi { This is the code that will use the generated Dart code to make calls from Flutter to the host platform. -<?code-excerpt "../../app/lib/main.dart (main-dart)"?> -```dart +<?code-excerpt "lib/main.dart (main-dart)"?> +```dart final ExampleHostApi _api = ExampleHostApi(); /// Calls host method `add` with provided arguments. @@ -115,7 +115,7 @@ Future<bool> sendMessage(String messageText) { This is the code that will use the generated Swift code to receive calls from Flutter. packages/pigeon/example/app/ios/Runner/AppDelegate.swift -<?code-excerpt "../../app/ios/Runner/AppDelegate.swift (swift-class)"?> +<?code-excerpt "ios/Runner/AppDelegate.swift (swift-class)"?> ```swift // This extension of Error is required to do use FlutterError in any Swift code. extension FlutterError: Error {} @@ -143,7 +143,7 @@ private class PigeonApiImplementation: ExampleHostApi { ``` ### Kotlin -<?code-excerpt "../../app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class)"?> +<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class)"?> ```kotlin private class PigeonApiImplementation: ExampleHostApi { override fun getHostLanguage(): String { @@ -168,7 +168,7 @@ private class PigeonApiImplementation: ExampleHostApi { ``` ### C++ -<?code-excerpt "../../app/windows/runner/flutter_window.cpp (cpp-class)"?> +<?code-excerpt "windows/runner/flutter_window.cpp (cpp-class)"?> ```c++ class PigeonApiImplementation : public ExampleHostApi { public: @@ -200,7 +200,7 @@ app from the host platform. ### Dart input -<?code-excerpt "../../app/pigeons/messages.dart (flutter-definitions)"?> +<?code-excerpt "pigeons/messages.dart (flutter-definitions)"?> ```dart @FlutterApi() abstract class MessageFlutterApi { @@ -213,8 +213,8 @@ abstract class MessageFlutterApi { This is the code that will use the generated Dart code to handle calls made to Flutter from the host platform. -<?code-excerpt "../../app/lib/main.dart (main-dart-flutter)"?> -```dart +<?code-excerpt "lib/main.dart (main-dart-flutter)"?> +```dart class _ExampleFlutterApi implements MessageFlutterApi { @override String flutterMethod(String? aString) { @@ -227,7 +227,7 @@ class _ExampleFlutterApi implements MessageFlutterApi { ### Swift -<?code-excerpt "../../app/ios/Runner/AppDelegate.swift (swift-class-flutter)"?> +<?code-excerpt "ios/Runner/AppDelegate.swift (swift-class-flutter)"?> ```swift private class PigeonFlutterApi { var flutterAPI: MessageFlutterApi @@ -246,7 +246,7 @@ private class PigeonFlutterApi { ### Kotlin -<?code-excerpt "../../app/android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class-flutter)"?> +<?code-excerpt "android/app/src/main/kotlin/dev/flutter/pigeon_example_app/MainActivity.kt (kotlin-class-flutter)"?> ```kotlin private class PigeonFlutterApi { @@ -266,7 +266,7 @@ private class PigeonFlutterApi { ### C++ -<?code-excerpt "../../app/windows/runner/flutter_window.cpp (cpp-method-flutter)"?> +<?code-excerpt "windows/runner/flutter_window.cpp (cpp-method-flutter)"?> ```c++ void TestPlugin::CallFlutterMethod( String aString, std::function<void(ErrorOr<int64_t> reply)> result) { diff --git a/packages/pigeon/example/build.excerpt.yaml b/packages/pigeon/example/build.excerpt.yaml deleted file mode 100644 index c0ce017a92..0000000000 --- a/packages/pigeon/example/build.excerpt.yaml +++ /dev/null @@ -1,23 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - - pigeons/** - - app/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true - generate_for: - - '**.dart' - - '**.swift' - - '**.kt' - - '**.cpp' - - '**.h' \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences/README.md b/packages/shared_preferences/shared_preferences/README.md index 35769d56d3..acb4933ea8 100644 --- a/packages/shared_preferences/shared_preferences/README.md +++ b/packages/shared_preferences/shared_preferences/README.md @@ -1,5 +1,5 @@ # Shared preferences plugin -<?code-excerpt path-base="excerpts/packages/shared_preferences_example"?> +<?code-excerpt path-base="example/lib"?> [](https://pub.dev/packages/shared_preferences) diff --git a/packages/shared_preferences/shared_preferences/example/build.excerpt.yaml b/packages/shared_preferences/shared_preferences/example/build.excerpt.yaml deleted file mode 100644 index e317efa11c..0000000000 --- a/packages/shared_preferences/shared_preferences/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true diff --git a/packages/standard_message_codec/README.md b/packages/standard_message_codec/README.md index b6c88e6000..ab736b611a 100644 --- a/packages/standard_message_codec/README.md +++ b/packages/standard_message_codec/README.md @@ -1,4 +1,4 @@ -<?code-excerpt path-base="excerpts/packages/standard_message_codec_examples"?> +<?code-excerpt path-base="example/lib"?> An efficient and schemaless binary format used by the Flutter SDK. diff --git a/packages/standard_message_codec/example/build.excerpt.yaml b/packages/standard_message_codec/example/build.excerpt.yaml deleted file mode 100644 index 5af61234d3..0000000000 --- a/packages/standard_message_codec/example/build.excerpt.yaml +++ /dev/null @@ -1,16 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true - \ No newline at end of file diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index 10c3db3f01..afd60143a5 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -1,4 +1,4 @@ -<?code-excerpt path-base="excerpts/packages/url_launcher_example"?> +<?code-excerpt path-base="example"?> # url_launcher @@ -16,7 +16,7 @@ To use this plugin, add `url_launcher` as a [dependency in your pubspec.yaml fil ### Example -<?code-excerpt "basic.dart (basic-example)"?> +<?code-excerpt "lib/basic.dart (basic-example)"?> ```dart import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -71,7 +71,7 @@ element must be added to your manifest as a child of the root element. Example: -<?code-excerpt "../../android/app/src/main/AndroidManifest.xml (android-queries)" plaster="none"?> +<?code-excerpt "android/app/src/main/AndroidManifest.xml (android-queries)" plaster="none"?> ```xml <!-- Provide required visibility configuration for API level 30 and above --> <queries> @@ -141,7 +141,7 @@ due to [a bug](https://github.com/dart-lang/sdk/issues/43838) in the way `Uri` encodes query parameters. Using `queryParameters` will result in spaces being converted to `+` in many cases. -<?code-excerpt "encoding.dart (encode-query-parameters)"?> +<?code-excerpt "lib/encoding.dart (encode-query-parameters)"?> ```dart String? encodeQueryParameters(Map<String, String> params) { return params.entries @@ -163,7 +163,7 @@ String? encodeQueryParameters(Map<String, String> params) { Encoding for `sms` is slightly different: -<?code-excerpt "encoding.dart (sms)"?> +<?code-excerpt "lib/encoding.dart (sms)"?> ```dart final Uri smsLaunchUri = Uri( scheme: 'sms', @@ -192,7 +192,7 @@ We recommend checking first whether the directory or file exists before calling Example: -<?code-excerpt "files.dart (file)"?> +<?code-excerpt "lib/files.dart (file)"?> ```dart final String filePath = testFile.absolute.path; final Uri uri = Uri.file(filePath); diff --git a/packages/url_launcher/url_launcher/example/build.excerpt.yaml b/packages/url_launcher/url_launcher/example/build.excerpt.yaml deleted file mode 100644 index c9a9c71ba1..0000000000 --- a/packages/url_launcher/url_launcher/example/build.excerpt.yaml +++ /dev/null @@ -1,20 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - - android/app/src/main/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - - 'android/app/src/main/res/**' - builders: - code_excerpter|code_excerpter: - enabled: true - generate_for: - - '**/*.dart' - - android/**/*.xml diff --git a/packages/video_player/video_player/README.md b/packages/video_player/video_player/README.md index cfe164cd65..167c565503 100644 --- a/packages/video_player/video_player/README.md +++ b/packages/video_player/video_player/README.md @@ -1,4 +1,4 @@ -<?code-excerpt path-base="excerpts/packages/video_player_example"?> +<?code-excerpt path-base="example/lib"?> # Video Player plugin for Flutter diff --git a/packages/video_player/video_player/example/build.excerpt.yaml b/packages/video_player/video_player/example/build.excerpt.yaml deleted file mode 100644 index c9a9c71ba1..0000000000 --- a/packages/video_player/video_player/example/build.excerpt.yaml +++ /dev/null @@ -1,20 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - - android/app/src/main/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - - 'android/app/src/main/res/**' - builders: - code_excerpter|code_excerpter: - enabled: true - generate_for: - - '**/*.dart' - - android/**/*.xml diff --git a/packages/video_player/video_player/test/web_vtt_test.dart b/packages/video_player/video_player/test/web_vtt_test.dart index b7a7bb51ce..dac6f49e31 100644 --- a/packages/video_player/video_player/test/web_vtt_test.dart +++ b/packages/video_player/video_player/test/web_vtt_test.dart @@ -154,7 +154,7 @@ WEBVTT 00:05.200 --> 00:06.000 align:start size:50% <v Roger Bingham><i>You know I'm so excited my glasses are falling off here.</i> -00:00:06.050 --> 00:00:06.150 +00:00:06.050 --> 00:00:06.150 <v Roger Bingham><i>I have a different time!</i> 00:06.200 --> 00:06.900 diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md index 5338b0eb2e..c5d1b81854 100644 --- a/packages/webview_flutter/webview_flutter/README.md +++ b/packages/webview_flutter/webview_flutter/README.md @@ -1,6 +1,6 @@ # WebView for Flutter -<?code-excerpt path-base="excerpts/packages/webview_flutter_example"?> +<?code-excerpt path-base="example/lib"?> [](https://pub.dev/packages/webview_flutter) @@ -227,4 +227,4 @@ Below is a non-exhaustive list of changes to the API: [WebViewController]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html [WebViewWidget]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html [NavigationDelegate]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/NavigationDelegate-class.html -[WebViewCookieManager]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewCookieManager-class.html \ No newline at end of file +[WebViewCookieManager]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewCookieManager-class.html diff --git a/packages/webview_flutter/webview_flutter/example/build.excerpt.yaml b/packages/webview_flutter/webview_flutter/example/build.excerpt.yaml deleted file mode 100644 index 46c1e75436..0000000000 --- a/packages/webview_flutter/webview_flutter/example/build.excerpt.yaml +++ /dev/null @@ -1,15 +0,0 @@ -targets: - $default: - sources: - include: - - lib/** - # Some default includes that aren't really used here but will prevent - # false-negative warnings: - - $package$ - - lib/$lib$ - exclude: - - '**/.*/**' - - '**/build/**' - builders: - code_excerpter|code_excerpter: - enabled: true \ No newline at end of file diff --git a/script/tool/README.md b/script/tool/README.md index 435394d807..f482a85c84 100644 --- a/script/tool/README.md +++ b/script/tool/README.md @@ -19,12 +19,6 @@ dart run script/tool/bin/flutter_plugin_tools.dart <args> Many commands require the Flutter-bundled version of Dart to be the first `dart` in the path. -### Extra Setup - -When updating sample code excerpts (`update-excerpts`) for the README.md files, -there is some [extra setup for -submodules](#update-readmemd-from-example-sources) that is necessary. - ## Commands Run with `--help` for a full list of commands and arguments, but the @@ -94,14 +88,16 @@ dart run script/tool/bin/flutter_plugin_tools.dart native-test --windows --packa ### Update README.md from Example Sources -`update-excerpts` requires sources that are in a submodule. If you didn't clone -with submodules, you will need to `git submodule update --init --recursive` -before running this command. - ```sh +# Update all .md files for all packages: +dart run script/tool/bin/flutter_plugin_tools.dart update-excerpts + +# Update the .md files only for one package: dart run script/tool/bin/flutter_plugin_tools.dart update-excerpts --packages package_name ``` +_See also: https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#readme-code_ + ### Update CHANGELOG and Version `update-release-info` will automatically update the version and `CHANGELOG.md` @@ -178,13 +174,3 @@ _everything_, including untracked or uncommitted files in version control. `publish` will first check the status of the local directory and refuse to publish if there are any mismatched files with version control present. - -## Updating the Tool - -For flutter/packages, just changing the source here is all that's needed. - -For changes that are relevant to flutter/packages, you will also need to: -- Update the tool's pubspec.yaml and CHANGELOG -- Publish the tool -- Update the pinned version in - [flutter/packages](https://github.com/flutter/packages/blob/main/.cirrus.yml) diff --git a/script/tool/lib/src/readme_check_command.dart b/script/tool/lib/src/readme_check_command.dart index 471fed57ab..a0d8d85019 100644 --- a/script/tool/lib/src/readme_check_command.dart +++ b/script/tool/lib/src/readme_check_command.dart @@ -172,20 +172,6 @@ class ReadmeCheckCommand extends PackageLoopingCommand { errorSummary = 'Missing language identifier for code block'; } - // If any blocks use code excerpts, make sure excerpting is configured - // for the package. - if (readmeLines.any((String line) => line.startsWith(excerptTagStart))) { - const String buildRunnerConfigFile = 'build.excerpt.yaml'; - if (!mainPackage.getExamples().any((RepositoryPackage example) => - example.directory.childFile(buildRunnerConfigFile).existsSync())) { - printError('code-excerpt tag found, but the package is not configured ' - 'for excerpting. Follow the instructions at\n' - '$_instructionWikiUrl\n' - 'for setting up a build.excerpt.yaml file.'); - errorSummary ??= 'Missing code-excerpt configuration'; - } - } - if (missingExcerptLines.isNotEmpty) { for (final int lineNumber in missingExcerptLines) { printError('${indentation}Dart code block at line $lineNumber is not ' @@ -193,8 +179,7 @@ class ReadmeCheckCommand extends PackageLoopingCommand { } printError( '\n${indentation}For each block listed above, add <?code-excerpt ...> ' - 'tag on the previous line, and ensure that a build.excerpt.yaml is ' - 'configured for the source example as explained at\n' + 'tag on the previous line, as explained at\n' '$_instructionWikiUrl'); errorSummary ??= 'Missing code-excerpt management for code block'; } diff --git a/script/tool/lib/src/update_excerpts_command.dart b/script/tool/lib/src/update_excerpts_command.dart index 9e270d6437..a3a56fcaf1 100644 --- a/script/tool/lib/src/update_excerpts_command.dart +++ b/script/tool/lib/src/update_excerpts_command.dart @@ -2,18 +2,20 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:io' as io; - import 'package:file/file.dart'; -import 'package:meta/meta.dart'; -import 'package:yaml/yaml.dart'; -import 'package:yaml_edit/yaml_edit.dart'; import 'common/output_utils.dart'; import 'common/package_looping_command.dart'; -import 'common/pub_utils.dart'; import 'common/repository_package.dart'; +class _UpdateResult { + const _UpdateResult(this.changed, this.errors); + final bool changed; + final List<String> errors; +} + +enum _ExcerptParseMode { normal, pragma, injecting } + /// A command to update .md code excerpts from code files. class UpdateExcerptsCommand extends PackageLoopingCommand { /// Creates a excerpt updater command instance. @@ -23,232 +25,295 @@ class UpdateExcerptsCommand extends PackageLoopingCommand { super.platform, super.gitDir, }) { - argParser.addFlag(_failOnChangeFlag, hide: true); - argParser.addFlag(_noCleanupFlag, - help: 'Skips the step of cleaning up the excerpt extraction output. ' - 'This can be useful when debugging extraction or checking paths to ' - 'reference in snippets.'); + argParser.addFlag( + _failOnChangeFlag, + help: 'Fail if the command does anything. ' + '(Used in CI to ensure excerpts are up to date.)', + ); } static const String _failOnChangeFlag = 'fail-on-change'; - static const String _noCleanupFlag = 'no-cleanup'; - - static const String _buildRunnerConfigName = 'excerpt'; - // The name of the build_runner configuration file that will be in an example - // directory if the package is set up to use `code-excerpt`. - static const String _buildRunnerConfigFile = - 'build.$_buildRunnerConfigName.yaml'; - - /// The relative directory path to put the extracted excerpt yaml files. - @visibleForTesting - static const String excerptOutputDir = 'excerpts'; - - // The filename to store the pre-modification copy of the pubspec. - static const String _originalPubspecFilename = - 'pubspec.plugin_tools_original.yaml'; @override final String name = 'update-excerpts'; @override final String description = 'Updates code excerpts in .md files, based ' - 'on code from code files, via code-excerpt'; + 'on code from code files, via <?code-excerpt?> pragmas.'; @override Future<PackageResult> runForPackage(RepositoryPackage package) async { - final Iterable<RepositoryPackage> configuredExamples = package - .getExamples() - .where((RepositoryPackage example) => - example.directory.childFile(_buildRunnerConfigFile).existsSync()); - - if (configuredExamples.isEmpty) { - return PackageResult.skip( - 'No $_buildRunnerConfigFile found in example(s).'); - } - - final Directory repoRoot = - packagesDir.fileSystem.directory((await gitDir).path); - - for (final RepositoryPackage example in configuredExamples) { - _addSubmoduleDependencies(example, repoRoot: repoRoot); - - try { - // Ensure that dependencies are available. - if (!await runPubGet(example, processRunner, platform)) { - return PackageResult.fail( - <String>['Unable to get script dependencies']); - } - - // Update the excerpts. - if (!await _extractSnippets(example)) { - return PackageResult.fail(<String>['Unable to extract excerpts']); - } - if (!await _injectSnippets(example, targetPackage: package)) { - return PackageResult.fail(<String>['Unable to inject excerpts']); - } - if (!await _injectSnippets(example, targetPackage: example)) { - return PackageResult.fail( - <String>['Unable to inject example excerpts']); - } - } finally { - // Clean up the pubspec changes and extracted excerpts directory. - _undoPubspecChanges(example); - final Directory excerptDirectory = - example.directory.childDirectory(excerptOutputDir); - if (excerptDirectory.existsSync()) { - if (getBoolArg(_noCleanupFlag)) { - final String relativeDir = - getRelativePosixPath(excerptDirectory, from: package.directory); - print( - '\n\nSKIPPING CLEANUP: Extraction output is in $relativeDir/'); - } else { - excerptDirectory.deleteSync(recursive: true); - } - } + final List<File> changedFiles = <File>[]; + final List<String> errors = <String>[]; + final List<File> markdownFiles = package.directory + .listSync(recursive: true) + .where((FileSystemEntity entity) { + return entity is File && + entity.basename != 'CHANGELOG.md' && + entity.basename.toLowerCase().endsWith('.md'); + }) + .cast<File>() + .toList(); + for (final File file in markdownFiles) { + final _UpdateResult result = _updateExcerptsIn(file); + if (result.changed) { + changedFiles.add(file); + } + if (result.errors.isNotEmpty) { + errors.addAll(result.errors); } } - if (getBoolArg(_failOnChangeFlag)) { - final String? stateError = await _validateRepositoryState(package); - if (stateError != null) { - printError('One or more .md files are out of sync with their source ' - 'excerpts.\n\n' - 'If you edited code in a .md file directly, you should instead ' - 'edit the example source files. If you edited source files, run ' - 'the repository tooling\'s "$name" command on this package, and ' - 'update your PR with the resulting changes.\n\n' - 'For more information, see ' - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#readme-code'); - return PackageResult.fail(<String>[stateError]); - } + if (errors.isNotEmpty) { + printError('Injecting excerpts failed:'); + printError(errors.join('\n')); + return PackageResult.fail(); + } + + if (getBoolArg(_failOnChangeFlag) && changedFiles.isNotEmpty) { + printError( + 'The following files have out of date excerpts:\n' + ' ${changedFiles.map((File file) => file.path).join("\n ")}\n' + '\n' + 'If you edited code in a .md file directly, you should instead edit the ' + 'files that contain the sources of the excerpts.\n' + 'If you did edit those source files, run the repository tooling\'s "$name" ' + 'command on this package, and update your PR with the resulting changes.\n' + '\n' + 'For more information, see ' + 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#readme-code', + ); + return PackageResult.fail(); } return PackageResult.success(); } - /// Runs the extraction step to create the excerpt files for the given - /// example, returning true on success. - Future<bool> _extractSnippets(RepositoryPackage example) async { - final int exitCode = await processRunner.runAndStream( - 'dart', - <String>[ - 'run', - 'build_runner', - 'build', - '--config', - _buildRunnerConfigName, - '--output', - excerptOutputDir, - '--delete-conflicting-outputs', - ], - workingDir: example.directory); - return exitCode == 0; + static const String _pragma = '<?code-excerpt'; + static final RegExp _basePattern = + RegExp(r'^ *<\?code-excerpt path-base="([^"]+)"\?>$'); + static final RegExp _injectPattern = RegExp( + r'^ *<\?code-excerpt "(?<path>[^ ]+) \((?<section>[^)]+)\)"(?: plaster="(?<plaster>[^"]*)")?\?>$', + ); + + _UpdateResult _updateExcerptsIn(File file) { + bool detectedChange = false; + final List<String> errors = <String>[]; + Directory pathBase = file.parent; + final StringBuffer output = StringBuffer(); + final StringBuffer existingBlock = StringBuffer(); + String? language; + String? excerpt; + _ExcerptParseMode mode = _ExcerptParseMode.normal; + int lineNumber = 0; + for (final String line in file.readAsLinesSync()) { + lineNumber += 1; + switch (mode) { + case _ExcerptParseMode.normal: + if (line.contains(_pragma)) { + RegExpMatch? match = _basePattern.firstMatch(line); + if (match != null) { + pathBase = + file.parent.childDirectory(path.normalize(match.group(1)!)); + } else { + match = _injectPattern.firstMatch(line); + if (match != null) { + final String excerptPath = + path.normalize(match.namedGroup('path')!); + final File excerptSourceFile = pathBase.childFile(excerptPath); + final String extension = path.extension(excerptSourceFile.path); + switch (extension) { + case '': + language = 'txt'; + break; + case '.kt': + language = 'kotlin'; + break; + case '.cc': + case '.cpp': + language = 'c++'; + break; + default: + language = extension.substring(1); + break; + } + final String section = match.namedGroup('section')!; + final String plaster = match.namedGroup('plaster') ?? '···'; + if (!excerptSourceFile.existsSync()) { + errors.add( + '${file.path}:$lineNumber: specified file "$excerptPath" (resolved to "${excerptSourceFile.path}") does not exist'); + } else { + excerpt = _extractExcerpt( + excerptSourceFile, section, plaster, language, errors); + } + mode = _ExcerptParseMode.pragma; + } else { + errors.add( + '${file.path}:$lineNumber: $_pragma?> pragma does not match expected syntax or is not alone on the line'); + } + } + } + output.writeln(line); + break; + case _ExcerptParseMode.pragma: + if (!line.startsWith('```')) { + errors.add( + '${file.path}:$lineNumber: expected code block but did not find one'); + mode = _ExcerptParseMode.normal; + } else { + if (line.startsWith('``` ')) { + errors.add( + '${file.path}:$lineNumber: code block was followed by a space character instead of the language (expected "$language")'); + mode = _ExcerptParseMode.injecting; + } else if (line != '```$language' && line != '```rfwtxt' && line != '```json') { + // We special-case rfwtxt and json because the rfw package extracts such sections from Dart files. + // If we get more special cases we should think about a more general solution. + errors.add( + '${file.path}:$lineNumber: code block has wrong language'); + } + mode = _ExcerptParseMode.injecting; + } + output.writeln(line); + break; + case _ExcerptParseMode.injecting: + if (line == '```') { + if (existingBlock.toString() != excerpt) { + detectedChange = true; + } + output.write(excerpt); + output.writeln(line); + mode = _ExcerptParseMode.normal; + language = null; + excerpt = null; + existingBlock.clear(); + } else { + existingBlock.writeln(line); + } + break; + } + } + if (detectedChange) { + if (errors.isNotEmpty) { + errors.add('${file.path}: skipped updating file due to errors'); + } else { + try { + file.writeAsStringSync(output.toString()); + } catch (e) { + errors.add( + '${file.path}: failed to update file (${e.runtimeType}: $e)'); + } + } + } + return _UpdateResult(detectedChange, errors); } - /// Runs the injection step to update [targetPackage]'s top-level .md files - /// with the latest excerpts from [example], returning true on success. - Future<bool> _injectSnippets( - RepositoryPackage example, { - required RepositoryPackage targetPackage, - }) async { - final List<String> relativeMdPaths = targetPackage.directory - .listSync() - .whereType<File>() - .where((File f) => - f.basename.toLowerCase().endsWith('.md') && - // Exclude CHANGELOG since it should never have excerpts. - f.basename != 'CHANGELOG.md') - .map((File f) => getRelativePosixPath(f, from: example.directory)) - .toList(); - if (relativeMdPaths.isEmpty) { - return true; + String _extractExcerpt(File excerptSourceFile, String section, + String plasterInside, String language, List<String> errors) { + final List<String> buffer = <String>[]; + bool extracting = false; + int lineNumber = 0; + int maxLength = 0; + bool found = false; + String prefix = ''; + String suffix = ''; + String padding = ''; + switch (language) { + case 'cc': + case 'c++': + case 'dart': + case 'js': + case 'kotlin': + case 'rfwtxt': + case 'swift': + prefix = '// '; + break; + case 'css': + prefix = '/* '; + suffix = ' */'; + break; + case 'html': + case 'xml': + prefix = '<!--'; + suffix = '-->'; + padding = ' '; + break; + case 'yaml': + prefix = '# '; + break; } - final int exitCode = await processRunner.runAndStream( - 'dart', - <String>[ - 'run', - 'code_excerpt_updater', - '--write-in-place', - '--yaml', - '--no-escape-ng-interpolation', - ...relativeMdPaths, - ], - workingDir: example.directory); - return exitCode == 0; - } - - /// Adds `code_excerpter` and `code_excerpt_updater` to [package]'s - /// `dev_dependencies` using path-based references to the submodule copies. - /// - /// This is done on the fly rather than being checked in so that: - /// - Just building examples don't require everyone to check out submodules. - /// - Examples can be analyzed/built even on versions of Flutter that these - /// submodules do not support. - void _addSubmoduleDependencies(RepositoryPackage package, - {required Directory repoRoot}) { - final String pubspecContents = package.pubspecFile.readAsStringSync(); - // Save aside a copy of the current pubspec state. This allows restoration - // to the previous state regardless of its git status at the time the script - // ran. - package.directory - .childFile(_originalPubspecFilename) - .writeAsStringSync(pubspecContents); - - // Update the actual pubspec. - final YamlEditor editablePubspec = YamlEditor(pubspecContents); - const String devDependenciesKey = 'dev_dependencies'; - final YamlNode root = editablePubspec.parseAt(<String>[]); - // Ensure that there's a `dev_dependencies` entry to update. - if ((root as YamlMap)[devDependenciesKey] == null) { - editablePubspec.update(<String>['dev_dependencies'], YamlMap()); + final String startRegionMarker = '$prefix#docregion $section$suffix'; + final String endRegionMarker = '$prefix#enddocregion $section$suffix'; + final String plaster = '$prefix$padding$plasterInside$padding$suffix'; + int? indentation; + for (final String excerptLine in excerptSourceFile.readAsLinesSync()) { + final String trimmedLine = excerptLine.trimLeft(); + lineNumber += 1; + if (extracting) { + if (trimmedLine == endRegionMarker) { + extracting = false; + indentation = excerptLine.length - trimmedLine.length; + } else { + if (trimmedLine == startRegionMarker) { + errors.add( + '${excerptSourceFile.path}:$lineNumber: saw "$startRegionMarker" pragma while already in a "$section" doc region'); + } + if (excerptLine.length > maxLength) { + maxLength = excerptLine.length; + } + if (!excerptLine.contains('$prefix#docregion ') && + !excerptLine.contains('$prefix#enddocregion ')) { + buffer.add(excerptLine); + } + } + } else { + if (trimmedLine == startRegionMarker) { + found = true; + extracting = true; + if (buffer.isNotEmpty && plasterInside != 'none') { + assert(indentation != null); + buffer.add('${" " * indentation!}$plaster'); + indentation = null; + } + } + } } - final Set<String> submoduleDependencies = <String>{ - 'code_excerpter', - 'code_excerpt_updater', - }; - final String relativeRootPath = - getRelativePosixPath(repoRoot, from: package.directory); - for (final String dependency in submoduleDependencies) { - editablePubspec.update(<String>[ - devDependenciesKey, - dependency - ], <String, String>{ - 'path': '$relativeRootPath/site-shared/packages/$dependency' - }); + if (extracting) { + errors + .add('${excerptSourceFile.path}: missing "$endRegionMarker" pragma'); } - package.pubspecFile.writeAsStringSync(editablePubspec.toString()); - } - - /// Restores the version of the pubspec that was present before running - /// [_addSubmoduleDependencies]. - void _undoPubspecChanges(RepositoryPackage package) { - package.directory - .childFile(_originalPubspecFilename) - .renameSync(package.pubspecFile.path); - } - - /// Checks the git state, returning an error string if any .md files have - /// changed. - Future<String?> _validateRepositoryState(RepositoryPackage package) async { - final io.ProcessResult checkFiles = await processRunner.run( - 'git', - <String>['ls-files', '--modified'], - workingDir: package.directory, - logOnError: true, - ); - if (checkFiles.exitCode != 0) { - return 'Unable to determine local file state'; + if (!found) { + errors.add( + '${excerptSourceFile.path}: did not find a "$startRegionMarker" pragma'); + return ''; } - - final String stdout = checkFiles.stdout as String; - final List<String> changedFiles = stdout.trim().split('\n'); - final Iterable<String> changedMDFiles = - changedFiles.where((String filePath) => filePath.endsWith('.md')); - if (changedMDFiles.isNotEmpty) { - return 'Snippets are out of sync in the following files: ' - '${changedMDFiles.join(', ')}'; + if (buffer.isEmpty) { + errors.add('${excerptSourceFile.path}: region "$section" is empty'); + return ''; } - - return null; + int indent = maxLength; + for (final String line in buffer) { + if (indent == 0) { + break; + } + if (line.isEmpty) { + continue; + } + for (int index = 0; index < line.length; index += 1) { + if (line[index] != ' ') { + if (index < indent) { + indent = index; + } + } + } + } + final StringBuffer excerpt = StringBuffer(); + for (final String line in buffer) { + if (line.isEmpty) { + excerpt.writeln(); + } else { + excerpt.writeln(line.substring(indent)); + } + } + return excerpt.toString(); } } diff --git a/script/tool/test/readme_check_command_test.dart b/script/tool/test/readme_check_command_test.dart index 938ea77e45..12e7a0ac2f 100644 --- a/script/tool/test/readme_check_command_test.dart +++ b/script/tool/test/readme_check_command_test.dart @@ -648,7 +648,6 @@ A B C final RepositoryPackage package = createFakePackage( 'a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath], ); package.readmeFile.writeAsStringSync(''' @@ -672,40 +671,6 @@ A B C ); }); - test('fails when excerpts are used but the package is not configured', - () async { - final RepositoryPackage package = - createFakePackage('a_package', packagesDir); - - package.readmeFile.writeAsStringSync(''' -Example: - -<?code-excerpt "main.dart (SomeSection)"?> -```dart -A B C -``` -'''); - - Error? commandError; - final List<String> output = await runCapturingPrint( - runner, <String>['readme-check', '--require-excerpts'], - errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA<ToolExit>()); - expect( - output, - containsAllInOrder(<Matcher>[ - contains('code-excerpt tag found, but the package is not configured ' - 'for excerpting. Follow the instructions at\n' - 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages\n' - 'for setting up a build.excerpt.yaml file.'), - contains('Missing code-excerpt configuration'), - ]), - ); - }); - test('fails on missing excerpt tag when requested', () async { final RepositoryPackage package = createFakePackage('a_package', packagesDir); diff --git a/script/tool/test/update_excerpts_command_test.dart b/script/tool/test/update_excerpts_command_test.dart index 677b0c002c..1e9022afaa 100644 --- a/script/tool/test/update_excerpts_command_test.dart +++ b/script/tool/test/update_excerpts_command_test.dart @@ -7,328 +7,92 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/update_excerpts_command.dart'; -import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'common/package_command_test.mocks.dart'; import 'mocks.dart'; import 'util.dart'; -void main() { +void runAllTests(MockPlatform platform) { late FileSystem fileSystem; late Directory packagesDir; - late RecordingProcessRunner processRunner; late CommandRunner<void> runner; setUp(() { - fileSystem = MemoryFileSystem(); + fileSystem = MemoryFileSystem( + style: platform.isWindows + ? FileSystemStyle.windows + : FileSystemStyle.posix); packagesDir = createPackagesDirectory(fileSystem: fileSystem); - final MockGitDir gitDir = MockGitDir(); - when(gitDir.path).thenReturn(packagesDir.parent.path); - processRunner = RecordingProcessRunner(); - final UpdateExcerptsCommand command = UpdateExcerptsCommand( - packagesDir, - processRunner: processRunner, - platform: MockPlatform(), - gitDir: gitDir, + runner = CommandRunner<void>('', '') + ..addCommand(UpdateExcerptsCommand( + packagesDir, + platform: platform, + processRunner: RecordingProcessRunner(), + gitDir: MockGitDir(), + )); + }); + + Future<void> testInjection(String before, String source, String after, + {bool failOnChange = false}) async { + final RepositoryPackage package = + createFakePackage('a_package', packagesDir); + package.readmeFile.writeAsStringSync(before); + package.directory.childFile('main.dart').writeAsStringSync(source); + Object? errorObject; + final List<String> output = await runCapturingPrint( + runner, + <String>[ + 'update-excerpts', + if (failOnChange) '--fail-on-change', + ], + errorHandler: (Object error) { + errorObject = error; + }, ); + if (errorObject != null) { + fail('Failed: $errorObject\n\nOutput from excerpt command:\n$output'); + } + expect(package.readmeFile.readAsStringSync(), after); + } - runner = CommandRunner<void>( - 'update_excerpts_command', 'Test for update_excerpts_command'); - runner.addCommand(command); - }); + test('succeeds when nothing has changed', () async { + const String readme = ''' +Example: - test('runs pub get before running scripts', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); - final Directory example = getExampleDir(package); - - await runCapturingPrint(runner, <String>['update-excerpts']); - - expect( - processRunner.recordedCalls, - containsAll(<ProcessCall>[ - ProcessCall('flutter', const <String>['pub', 'get'], example.path), - ProcessCall( - 'dart', - const <String>[ - 'run', - 'build_runner', - 'build', - '--config', - 'excerpt', - '--output', - UpdateExcerptsCommand.excerptOutputDir, - '--delete-conflicting-outputs', - ], - example.path), - ])); - }); - - test('runs when config is present', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); - final Directory example = getExampleDir(package); - - final List<String> output = - await runCapturingPrint(runner, <String>['update-excerpts']); - - expect( - processRunner.recordedCalls, - containsAll(<ProcessCall>[ - ProcessCall( - 'dart', - const <String>[ - 'run', - 'build_runner', - 'build', - '--config', - 'excerpt', - '--output', - UpdateExcerptsCommand.excerptOutputDir, - '--delete-conflicting-outputs', - ], - example.path), - ProcessCall( - 'dart', - const <String>[ - 'run', - 'code_excerpt_updater', - '--write-in-place', - '--yaml', - '--no-escape-ng-interpolation', - '../README.md', - ], - example.path), - ])); - - expect( - output, - containsAllInOrder(<Matcher>[ - contains('Ran for 1 package(s)'), - ])); - }); - - test('updates example readme when config is present', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath, 'example/README.md']); - final Directory example = getExampleDir(package); - - final List<String> output = - await runCapturingPrint(runner, <String>['update-excerpts']); - - expect( - processRunner.recordedCalls, - containsAll(<ProcessCall>[ - ProcessCall( - 'dart', - const <String>[ - 'run', - 'build_runner', - 'build', - '--config', - 'excerpt', - '--output', - UpdateExcerptsCommand.excerptOutputDir, - '--delete-conflicting-outputs', - ], - example.path), - ProcessCall( - 'dart', - const <String>[ - 'run', - 'code_excerpt_updater', - '--write-in-place', - '--yaml', - '--no-escape-ng-interpolation', - 'README.md', - ], - example.path), - ])); - - expect( - output, - containsAllInOrder(<Matcher>[ - contains('Ran for 1 package(s)'), - ])); - }); - - test('includes all top-level .md files', () async { - const String otherMdFileName = 'another_file.md'; - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath, otherMdFileName]); - final Directory example = getExampleDir(package); - - final List<String> output = - await runCapturingPrint(runner, <String>['update-excerpts']); - - expect( - processRunner.recordedCalls, - containsAll(<ProcessCall>[ - ProcessCall( - 'dart', - const <String>[ - 'run', - 'build_runner', - 'build', - '--config', - 'excerpt', - '--output', - UpdateExcerptsCommand.excerptOutputDir, - '--delete-conflicting-outputs', - ], - example.path), - ProcessCall( - 'dart', - const <String>[ - 'run', - 'code_excerpt_updater', - '--write-in-place', - '--yaml', - '--no-escape-ng-interpolation', - '../README.md', - '../$otherMdFileName', - ], - example.path), - ])); - - expect( - output, - containsAllInOrder(<Matcher>[ - contains('Ran for 1 package(s)'), - ])); - }); - - test('skips when no config is present', () async { - createFakePlugin('a_package', packagesDir); - - final List<String> output = - await runCapturingPrint(runner, <String>['update-excerpts']); - - expect(processRunner.recordedCalls, isEmpty); - - expect( - output, - containsAllInOrder(<Matcher>[ - contains('Skipped 1 package(s)'), - ])); - }); - - test('restores pubspec even if running the script fails', () async { - final RepositoryPackage package = createFakePlugin('a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['flutter'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(exitCode: 1), <String>['pub', 'get']) - ]; - - Error? commandError; - final List<String> output = await runCapturingPrint( - runner, <String>['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - // Check that it's definitely a failure in a step between making the changes - // and restoring the original. - expect(commandError, isA<ToolExit>()); - expect( - output, - containsAllInOrder(<Matcher>[ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to get script dependencies') - ])); - - final String examplePubspecContent = - package.getExamples().first.pubspecFile.readAsStringSync(); - expect(examplePubspecContent, isNot(contains('code_excerpter'))); - expect(examplePubspecContent, isNot(contains('code_excerpt_updater'))); - }); - - test('fails if pub get fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['flutter'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(exitCode: 1), <String>['pub', 'get']) - ]; - - Error? commandError; - final List<String> output = await runCapturingPrint( - runner, <String>['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA<ToolExit>()); - expect( - output, - containsAllInOrder(<Matcher>[ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to get script dependencies') - ])); - }); - - test('fails if extraction fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(exitCode: 1), <String>['run', 'build_runner']) - ]; - - Error? commandError; - final List<String> output = await runCapturingPrint( - runner, <String>['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA<ToolExit>()); - expect( - output, - containsAllInOrder(<Matcher>[ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to extract excerpts') - ])); - }); - - test('fails if injection fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); - - processRunner.mockProcessesForExecutable['dart'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(), <String>['run', 'build_runner']), - FakeProcessInfo( - MockProcess(exitCode: 1), <String>['run', 'code_excerpt_updater']), - ]; - - Error? commandError; - final List<String> output = await runCapturingPrint( - runner, <String>['update-excerpts'], errorHandler: (Error e) { - commandError = e; - }); - - expect(commandError, isA<ToolExit>()); - expect( - output, - containsAllInOrder(<Matcher>[ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to inject excerpts') - ])); +<?code-excerpt "main.dart (SomeSection)"?> +```dart +A B C +``` +'''; + const String source = ''' +FAIL +// #docregion SomeSection +A B C +// #enddocregion SomeSection +FAIL +'''; + await testInjection(readme, source, readme); }); test('fails if example injection fails', () async { - createFakePlugin('a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath, 'example/README.md']); + final RepositoryPackage package = + createFakePackage('a_package', packagesDir); + package.readmeFile.writeAsStringSync(''' +Example: - processRunner.mockProcessesForExecutable['dart'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(), <String>['run', 'build_runner']), - FakeProcessInfo(MockProcess(), <String>['run', 'code_excerpt_updater']), - FakeProcessInfo( - MockProcess(exitCode: 1), <String>['run', 'code_excerpt_updater']), - ]; +<?code-excerpt "main.dart (UnknownSection)"?> +```dart +A B C +``` +'''); + package.directory.childFile('main.dart').writeAsStringSync(''' +FAIL +// #docregion SomeSection +A B C +// #enddocregion SomeSection +FAIL +'''); Error? commandError; final List<String> output = await runCapturingPrint( @@ -338,22 +102,61 @@ void main() { expect(commandError, isA<ToolExit>()); expect( - output, - containsAllInOrder(<Matcher>[ - contains('The following packages had errors:'), - contains('a_package:\n' - ' Unable to inject example excerpts') - ])); + output, + containsAllInOrder(<Matcher>[ + contains('Injecting excerpts failed:'), + contains( + 'main.dart: did not find a "// #docregion UnknownSection" pragma'), + ]), + ); + }); + + test('updates files', () async { + await testInjection( + ''' +Example: + +<?code-excerpt "main.dart (SomeSection)"?> +```dart +X Y Z +``` +''', + ''' +FAIL +// #docregion SomeSection +A B C +// #enddocregion SomeSection +FAIL +''', + ''' +Example: + +<?code-excerpt "main.dart (SomeSection)"?> +```dart +A B C +``` +''', + ); }); test('fails if READMEs are changed with --fail-on-change', () async { - createFakePlugin('a_plugin', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); + final RepositoryPackage package = + createFakePackage('a_package', packagesDir); + package.readmeFile.writeAsStringSync(''' +Example: - const String changedFilePath = 'README.md'; - processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(stdout: changedFilePath)), - ]; +<?code-excerpt "main.dart (SomeSection)"?> +```dart +X Y Z +``` +'''); + package.directory.childFile('main.dart').writeAsStringSync(''' +FAIL +// #docregion SomeSection +A B C +// #enddocregion SomeSection +FAIL +'''); Error? commandError; final List<String> output = await runCapturingPrint( @@ -364,101 +167,259 @@ void main() { expect(commandError, isA<ToolExit>()); expect( - output, - containsAllInOrder(<Matcher>[ - contains( - 'One or more .md files are out of sync with their source excerpts'), - contains('Snippets are out of sync in the following files: ' - '$changedFilePath'), - ])); + output.join('\n'), + contains('The following files have out of date excerpts:'), + ); }); - test('passes if unrelated files are changed with --fail-on-change', () async { - createFakePlugin('a_plugin', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); + test('does not fail if READMEs are not changed with --fail-on-change', + () async { + const String readme = ''' +Example: - const String changedFilePath = 'packages/a_plugin/linux/CMakeLists.txt'; - processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(stdout: changedFilePath)), - ]; - - final List<String> output = await runCapturingPrint( - runner, <String>['update-excerpts', '--fail-on-change']); - - expect( - output, - containsAllInOrder(<Matcher>[ - contains('Ran for 1 package(s)'), - ])); +<?code-excerpt "main.dart (aa)"?> +```dart +A +``` +<?code-excerpt "main.dart (bb)"?> +```dart +B +``` +'''; + await testInjection( + readme, + ''' +// #docregion aa +A +// #enddocregion aa +// #docregion bb +B +// #enddocregion bb +''', + readme, + failOnChange: true, + ); }); - test('fails if git ls-files fails', () async { - createFakePlugin('a_plugin', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); + test('indents the plaster', () async { + await testInjection( + ''' +Example: - processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(exitCode: 1)) - ]; - Error? commandError; - final List<String> output = await runCapturingPrint( - runner, <String>['update-excerpts', '--fail-on-change'], - errorHandler: (Error e) { - commandError = e; - }); +<?code-excerpt "main.dart (SomeSection)"?> +```dart +``` +''', + ''' +// #docregion SomeSection +A + // #enddocregion SomeSection +// #docregion SomeSection +B +// #enddocregion SomeSection +''', + ''' +Example: - expect(commandError, isA<ToolExit>()); - expect( - output, - containsAllInOrder(<Matcher>[ - contains('Unable to determine local file state'), - ])); +<?code-excerpt "main.dart (SomeSection)"?> +```dart +A + // ··· +B +``` +''', + ); }); - test('cleans up excerpt output by default', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); - // Simulate the creation of the output directory. - final Directory excerptOutputDir = package - .getExamples() - .first - .directory - .childDirectory(UpdateExcerptsCommand.excerptOutputDir); - excerptOutputDir.createSync(recursive: true); + test('does not unindent blocks if plaster will not unindent', () async { + await testInjection( + ''' +Example: - const String changedFilePath = 'packages/a_plugin/linux/CMakeLists.txt'; - processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(stdout: changedFilePath)), - ]; +<?code-excerpt "main.dart (SomeSection)"?> +```dart +``` +''', + ''' +// #docregion SomeSection + A +// #enddocregion SomeSection +// #docregion SomeSection + B +// #enddocregion SomeSection +''', + ''' +Example: +<?code-excerpt "main.dart (SomeSection)"?> +```dart + A +// ··· + B +``` +''', + ); + }); + + test('unindents blocks', () async { + await testInjection( + ''' +Example: + +<?code-excerpt "main.dart (SomeSection)"?> +```dart +``` +''', + ''' + // #docregion SomeSection + A + // #enddocregion SomeSection + // #docregion SomeSection + B + // #enddocregion SomeSection +''', + ''' +Example: + +<?code-excerpt "main.dart (SomeSection)"?> +```dart +A +// ··· + B +``` +''', + ); + }); + + test('unindents blocks and plaster', () async { + await testInjection( + ''' +Example: + +<?code-excerpt "main.dart (SomeSection)"?> +```dart +``` +''', + ''' + // #docregion SomeSection + A + // #enddocregion SomeSection + // #docregion SomeSection + B + // #enddocregion SomeSection +''', + ''' +Example: + +<?code-excerpt "main.dart (SomeSection)"?> +```dart +A + // ··· + B +``` +''', + ); + }); + + test('relative path bases', () async { + final RepositoryPackage package = + createFakePackage('a_package', packagesDir); + package.readmeFile.writeAsStringSync(''' +<?code-excerpt "main.dart (a)"?> +```dart +``` +<?code-excerpt "test/main.dart (a)"?> +```dart +``` +<?code-excerpt "test/test/main.dart (a)"?> +```dart +``` +<?code-excerpt path-base="test"?> +<?code-excerpt "main.dart (a)"?> +```dart +``` +<?code-excerpt "../main.dart (a)"?> +```dart +``` +<?code-excerpt "test/main.dart (a)"?> +```dart +``` +<?code-excerpt path-base="/packages/a_package"?> +<?code-excerpt "main.dart (a)"?> +```dart +``` +<?code-excerpt "test/main.dart (a)"?> +```dart +``` +'''); + package.directory.childFile('main.dart').writeAsStringSync(''' +// #docregion a +X +// #enddocregion a +'''); + package.directory.childDirectory('test').createSync(); + package.directory + .childDirectory('test') + .childFile('main.dart') + .writeAsStringSync(''' +// #docregion a +Y +// #enddocregion a +'''); + package.directory + .childDirectory('test') + .childDirectory('test') + .createSync(); + package.directory + .childDirectory('test') + .childDirectory('test') + .childFile('main.dart') + .writeAsStringSync(''' +// #docregion a +Z +// #enddocregion a +'''); await runCapturingPrint(runner, <String>['update-excerpts']); - - expect(excerptOutputDir.existsSync(), false); - }); - - test('cleans up excerpt output by default', () async { - final RepositoryPackage package = createFakePackage( - 'a_package', packagesDir, - extraFiles: <String>[kReadmeExcerptConfigPath]); - // Simulate the creation of the output directory. - const String outputDirName = UpdateExcerptsCommand.excerptOutputDir; - final Directory excerptOutputDir = - package.getExamples().first.directory.childDirectory(outputDirName); - excerptOutputDir.createSync(recursive: true); - - const String changedFilePath = 'packages/a_plugin/linux/CMakeLists.txt'; - processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[ - FakeProcessInfo(MockProcess(stdout: changedFilePath)), - ]; - - final List<String> output = await runCapturingPrint( - runner, <String>['update-excerpts', '--no-cleanup']); - - expect( - output, - containsAllInOrder(<Matcher>[ - contains('Extraction output is in example/$outputDirName/'), - ])); - expect(excerptOutputDir.existsSync(), true); + expect(package.readmeFile.readAsStringSync(), ''' +<?code-excerpt "main.dart (a)"?> +```dart +X +``` +<?code-excerpt "test/main.dart (a)"?> +```dart +Y +``` +<?code-excerpt "test/test/main.dart (a)"?> +```dart +Z +``` +<?code-excerpt path-base="test"?> +<?code-excerpt "main.dart (a)"?> +```dart +Y +``` +<?code-excerpt "../main.dart (a)"?> +```dart +X +``` +<?code-excerpt "test/main.dart (a)"?> +```dart +Z +``` +<?code-excerpt path-base="/packages/a_package"?> +<?code-excerpt "main.dart (a)"?> +```dart +X +``` +<?code-excerpt "test/main.dart (a)"?> +```dart +Y +``` +'''); }); } + +void main() { + runAllTests(MockPlatform()); + runAllTests(MockPlatform(isWindows: true)); +} diff --git a/script/tool/test/util.dart b/script/tool/test/util.dart index f19355f289..4f38dd416a 100644 --- a/script/tool/test/util.dart +++ b/script/tool/test/util.dart @@ -23,13 +23,6 @@ import 'mocks.dart'; export 'package:flutter_plugin_tools/src/common/repository_package.dart'; -/// The relative path from a package to the file that is used to enable -/// README excerpting for a package. -// This is a shared constant to ensure that both readme-check and -// update-excerpt are looking for the same file, so that readme-check can't -// get out of sync with what actually drives excerpting. -const String kReadmeExcerptConfigPath = 'example/build.excerpt.yaml'; - const String _defaultDartConstraint = '>=2.14.0 <4.0.0'; const String _defaultFlutterConstraint = '>=2.5.0'; diff --git a/site-shared b/site-shared deleted file mode 160000 index 8c92e5bdfd..0000000000 --- a/site-shared +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8c92e5bdfdce14887605de6b5d9d0bd2615876b7