diff --git a/site-shared b/site-shared index b2ffb98eaf..7527a0e7d3 160000 --- a/site-shared +++ b/site-shared @@ -1 +1 @@ -Subproject commit b2ffb98eaf2d4022ebfc8ceb0af268f2e6449d13 +Subproject commit 7527a0e7d37ec92f8beeae67255788481e69ea68 diff --git a/src/content/gse/chapter_chapter_1.md b/src/content/gse/chapter_chapter_1.md new file mode 100644 index 0000000000..aed14d5537 --- /dev/null +++ b/src/content/gse/chapter_chapter_1.md @@ -0,0 +1,178 @@ +--- +title: "Chapter 1: Anatomy of a Dart program" +description: "Learn about `main` function, `pubspec.yaml`, `analysis_options.yaml`" +--- + +# Chapter 1: Anatomy of a Dart program +Learn about `main` function, `pubspec.yaml`, `analysis_options.yaml` + +[Video Placeholder] + +In this lesson, you'll get your first look at a Dart program. We'll cover the basic structure of a Dart project, including the `main` function (the entry point of your program) and the important configuration files: `pubspec.yaml` and `analysis_options.yaml`. By the end of this lesson, you'll be able to create, run, and understand the basic components of a Dart application. + +## Background / Key Concepts +* **`main` Function:** Every Dart program needs a `main` function. This is where your program starts executing. Think of it as the "ignition switch" for your app. +* **`pubspec.yaml`:** This file is the heart of your Dart project. It defines the project's name, version, dependencies (other packages your project needs), and other important metadata. It's like the manifest for your app. +* **`analysis_options.yaml`:** This file configures the Dart analyzer, a tool that helps you find potential problems in your code, enforce coding style, and improve code quality. It's like having a code reviewer built into your development environment. +* **Packages:** In Dart, code is organized into packages. A package is a reusable unit of code that can be shared across multiple projects. + +## Set up + +No setup is required for this chapter. We'll start from scratch by creating a new Dart project. + +## Tasks + +In this lesson, we'll create a new Dart project, explore its structure, and run the default application. + +### Create a new Dart project + +1. Open your terminal or command prompt. + +2. Navigate to the directory where you want to create your project. + +3. Run the following command: + + ```bash + dart create wikipedia + ``` + + This command uses the `dart create` tool to generate a basic Dart project named "wikipedia". This command scaffolds a CLI (Command Line Interface) application. It sets up the basic directory structure and files you need to get started. + + You should see output similar to this: + + ```bash + Creating project 'wikipedia'... + Running pub get in wikipedia... + Wrote wikipedia/analysis_options.yaml. + Wrote wikipedia/bin/wikipedia.dart. + Wrote wikipedia/lib/wikipedia.dart. + Wrote wikipedia/test/wikipedia_test.dart. + Wrote wikipedia/pubspec.yaml. + Wrote wikipedia/README.md. + + All done! + ``` + +4. Navigate into the newly created `wikipedia` directory: + + ```bash + cd wikipedia + ``` + +### Explore the project structure + +The `dart create` command has generated the following directory structure: + +```dart +wikipedia/ +├── analysis_options.yaml +├── bin +│ └── wikipedia.dart +├── lib +│ └── wikipedia.dart +├── pubspec.yaml +├── README.md +└── test + └── wikipedia_test.dart +``` + +Let's break down each file and directory: + +* **`analysis_options.yaml`:** We already discussed this file. This is where you configure the Dart analyzer. Open it and you'll see it includes `package:lints/recommended.yaml`. This enables a set of recommended "lints" (rules) that help you write better code. +* **`bin/`:** This directory contains the executable code for your application. In this case, `bin/wikipedia.dart` is the main entry point. +* **`lib/`:** This directory contains the library code for your application. This is where you'll put reusable code that can be shared across different parts of your application, or even across multiple projects. `lib/wikipedia.dart` is where our `calculate` function lives. +* **`pubspec.yaml`:** We also discussed this file. This declares metadata and dependancies for the project. +* **`README.md`:** This file contains a description of your project. It's good practice to keep this file updated with information about your application. +* **`test/`:** This directory contains the tests for your application. Writing tests is crucial for ensuring that your code works as expected. + +### Examine the `pubspec.yaml` file + +Open the `pubspec.yaml` file in your code editor. You should see something like this: + +```yaml +name: wikipedia +description: A sample command-line application. +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.7.0 + +# Add regular dependencies here. +dependencies: + # path: ^1.8.0 + +dev_dependencies: + lints: ^2.0.0 + test: ^1.16.0 +``` + +* **`name`:** The name of your project. +* **`description`:** A short description of your project. +* **`version`:** The version number of your project. +* **`environment`:** Specifies the Dart SDK version that your project requires. +* **`dependencies`:** Lists the packages that your project depends on. Notice the comment `# path: ^1.8.0`. If your project required a dependancy on the `path` package, you would uncomment this line. +* **`dev_dependencies`:** Lists the packages that are only needed during development, such as testing frameworks and linters. + +### Run the default application + +1. From the root directory of your project (`wikipedia`), run the following command: + + ```bash + dart run bin/wikipedia.dart + ``` + + This command tells Dart to execute the `bin/wikipedia.dart` file. + +2. You should see the following output: + + ```bash + Hello world: 42! + ``` + + Congratulations! You've successfully run your first Dart program! + +### Analyze the code + +Let's take a closer look at the code that generated this output. Open `bin/wikipedia.dart` in your code editor. You should see something like this: + +```dart +import 'package:wikipedia/wikipedia.dart' as wikipedia; + +void main(List arguments) { + print('Hello world: ${wikipedia.calculate()}!'); +} +``` + +* **`import 'package:wikipedia/wikipedia.dart' as wikipedia;`:** This line imports the code from `lib/wikipedia.dart`. The `as wikipedia` part gives the imported code a namespace, so you can refer to it as `wikipedia`. +* **`void main(List arguments) { ... }`:** This is the `main` function. It's the entry point of your program. The `List arguments` part allows you to pass command-line arguments to your program (we'll cover this later). +* **`print('Hello world: ${wikipedia.calculate()}!');`:** This line prints the text "Hello world: " followed by the result of calling the `calculate()` function (defined in `lib/wikipedia.dart`), followed by an exclamation point. The `${}` syntax is used for string interpolation, which allows you to embed the result of an expression directly into a string. + +Now, open `lib/wikipedia.dart`. You should see something like this: + +```dart +int calculate() { + return 6 * 7; +} +``` + +This code defines a simple function called `calculate()` that returns the product of 6 and 7 (which is 42). + +[Pop out placeholder: Try changing the value returned by the `calculate()` function and rerun the program. Observe the change in the output.] + +## Review + +In this lesson, you learned about the basic structure of a Dart project, including the `main` function, the `pubspec.yaml` and `analysis_options.yaml` files, and how to run a Dart program. You also got a glimpse of how code is organized into libraries and how to import code from other libraries. + +**Quiz Question:** + +Which file defines the dependencies of a Dart project? + +* [Option A] `analysis_options.yaml` +* [Option B] `pubspec.yaml` +* [Option C] `README.md` +* [Option D] `bin/wikipedia.dart` + +## Next lesson + +In the next lesson, we'll dive deeper into Dart syntax and start building a more interactive command-line application. You'll learn how to read user input, use control flow statements, work with collections, and more! \ No newline at end of file diff --git a/src/content/gse/chapter_chapter_10.md b/src/content/gse/chapter_chapter_10.md new file mode 100644 index 0000000000..668b189dc2 --- /dev/null +++ b/src/content/gse/chapter_chapter_10.md @@ -0,0 +1,147 @@ +--- +title: "Chapter 10: Testing" +description: "Learn about `package:test`, `group`, `test`, `expect`, and matchers." +--- + +# Chapter 10: Testing + +Learn about `package:test`, `group`, `test`, `expect`, and matchers. + +[Video Placeholder] + +In this lesson, we'll introduce you to the world of testing in Dart. Testing is a crucial part of software development, ensuring that your code behaves as expected and remains reliable over time. We'll be using the `package:test`, a powerful and flexible testing framework for Dart. You'll learn how to write unit tests, group related tests, use `expect` to assert that values match your expectations, and leverage matchers for more complex assertions. By the end of this lesson, you'll be able to write effective tests for your Dart code and ensure its robustness. + +## Background / Key Concepts +* **`package:test`:** A Dart package that provides a framework for writing and running tests. It includes features for organizing tests, making assertions, and reporting results. +* **Unit Tests:** Tests that focus on verifying the behavior of individual units of code, such as functions, methods, or classes. +* **`group()`:** A function from `package:test` that allows you to group related tests together, providing a clear structure for your test suite. +* **`test()`:** A function from `package:test` that defines an individual test case. Each test case should focus on verifying a specific aspect of your code's behavior. +* **`expect()`:** A function from `package:test` that asserts that a value matches your expectations. It takes two arguments: the actual value and the expected value (or a matcher). +* **Matchers:** Objects that define a set of criteria for matching values. `package:test` provides a variety of built-in matchers for common assertions, such as equality, inequality, string matching, and collection matching. + +## Set up +Make sure you have completed Chapter 9 and have a working Dart project set up with the `cli`, `command_runner`, and `wikipedia` packages. + +## Tasks +In this lesson, we'll add tests for the `Summary`, `TitleSet`, `Article`, and `SearchResults` classes in the `wikipedia` package. We'll use `group`, `test`, `expect`, and various matchers from the `package:test` to verify that the JSON deserialization methods in these classes are working correctly. + +### Update `wikipedia/pubspec.yaml` + +1. Ensure that the `test` dependancy is present in the `dev_dependencies` section of the `wikipedia/pubspec.yaml` file. +```yaml +dev_dependencies: + lints: ^5.0.0 + test: ^1.24.0 +``` +If you had to update this file, run `dart pub get` from the `wikipedia` directory. + +### Add Tests to `model_test.dart` +1. Open `wikipedia/test/model_test.dart` in your code editor. + +2. Replace the existing code with the following: + + ```dart + import 'dart:convert'; + import 'dart:io'; + + import 'package:test/test.dart'; + import 'package:wikipedia/src/model/article.dart'; + import 'package:wikipedia/src/model/search_results.dart'; + import 'package:wikipedia/src/model/summary.dart'; + + const String dartLangSummaryJson = './test/test_data/dart_lang_summary.json'; + const String catExtractJson = './test/test_data/cat_extract.json'; + const String openSearchResponse = './test/test_data/open_search_response.json'; + + // [Step 10 update] adds all tests + void main() { + group('deserialize example JSON responses from wikipedia API', () { + test('deserialize Dart Programming Language page summary example data from ' + 'json file into a Summary object', () async { + final String pageSummaryInput = + await File(dartLangSummaryJson).readAsString(); + final Map pageSummaryMap = + jsonDecode(pageSummaryInput) as Map; + final Summary summary = Summary.fromJson(pageSummaryMap); + expect(summary.titles.canonical, 'Dart_(programming_language)'); + }); + + test('deserialize Cat article example data from json file into ' + 'an Article object', () async { + final String articleJson = await File(catExtractJson).readAsString(); + final List articleAsMap = jsonDecode(articleJson); + final List
article = Article.listFromJson( + articleAsMap as Map, + ); + expect(article.first.title.toLowerCase(), 'cat'); + }); + + test('deserialize Open Search results example data from json file ' + 'into an SearchResults object', () async { + final String resultsString = + await File(openSearchResponse).readAsString(); + final List resultsAsList = + jsonDecode(resultsString) as List; + final SearchResults results = SearchResults.fromJson(resultsAsList); + expect(results.results.length, greaterThan(1)); + }); + }); + } + ``` + +3. **Explanation:** + + * **`import 'package:test/test.dart';`:** Imports the `package:test` library, providing access to the testing framework. + * **`group('deserialize example JSON responses from wikipedia API', () { ... });`:** Defines a group of tests related to deserializing JSON responses from the Wikipedia API. This helps organize the tests and provides a clear description of their purpose. + * **`test('deserialize Dart Programming Language page summary example data from ...', () async { ... });`:** Defines a test case for deserializing the Dart Programming Language page summary data. Each test case contains the following steps: + * **`final String pageSummaryInput = await File(dartLangSummaryJson).readAsString();`:** Reads the contents of the JSON file into a string. + * **`final Map pageSummaryMap = jsonDecode(pageSummaryInput) as Map;`:** Decodes the JSON string into a `Map`. + * **`final Summary summary = Summary.fromJson(pageSummaryMap);`:** Deserializes the JSON data into a `Summary` object using the `Summary.fromJson` method. + * **`expect(summary.titles.canonical, 'Dart_(programming_language)');`:** Asserts that the `canonical` property of the `titles` object in the `Summary` object is equal to `'Dart_(programming_language)'`. This verifies that the JSON data was correctly deserialized. + * **`expect(article.first.title.toLowerCase(), 'cat');`:** Asserts that the article title is "cat" (case-insensitive) + * **`expect(results.results.length, greaterThan(1));`:** Asserts that the search results contains more than one result + +### Run the Tests +1. Open your terminal or command prompt. + +2. Navigate to the root directory of the `wikipedia` package. + +3. Run the following command: + + ```bash + dart test test/model_test.dart + ``` + + This command tells Dart to execute the tests defined in the `wikipedia/test/model_test.dart` file. + + You should see output similar to this: + + ```bash + 00:02 +3: All tests passed! + ``` + + This indicates that all three tests passed successfully. + +[Pop out placeholder: Experiment with different matchers. For example, try using `expect(summary.pageid, isA())` to assert that the `pageid` property is an integer.] + +## Review + +In this lesson, you learned how to: + +* Use the `package:test` to write unit tests in Dart. +* Use the `group` function to organize related tests into logical groups. +* Use the `test` function to define individual test cases. +* Use the `expect` function to assert that values match your expectations. +* Use various matchers from `package:test` to perform more complex assertions. + +**Quiz Question:** + +What is the purpose of the `expect` function in `package:test`? +* [Option A] To define a group of related tests. +* [Option B] To define an individual test case. +* [Option C] To assert that a value matches your expectations. +* [Option D] To import a package. + +## Next lesson + +In the next lesson, we'll implement the Wikipedia API calls and integrate them into our command-line application. We'll learn how to make HTTP requests, handle responses, and parse the data into our data model classes. \ No newline at end of file diff --git a/src/content/gse/chapter_chapter_11.md b/src/content/gse/chapter_chapter_11.md new file mode 100644 index 0000000000..a6a06e2610 --- /dev/null +++ b/src/content/gse/chapter_chapter_11.md @@ -0,0 +1,596 @@ +--- +title: "Chapter 11: HTTP" +description: "Learn about Wikipedia API calls." +--- + +# Chapter 11: HTTP +Learn about Wikipedia API calls. + +[Video Placeholder] + +In this lesson, we'll implement the core functionality of our Wikipedia CLI application: making calls to the Wikipedia API. We'll leverage the `http` package to fetch data from the API endpoints and integrate these calls into our existing command structure. By the end of this lesson, you'll be able to search for articles, retrieve article summaries, and fetch full article content directly from the command line. This lesson will complete the core functionality of the Wikipedia CLI. + +## Background / Key Concepts +* **Wikipedia API:** A set of web services that allow developers to access and retrieve data from Wikipedia. We'll be using several API endpoints to search for articles, retrieve summaries, and fetch full article content. +* **`package:http`:** A Dart package that provides a simple and powerful way to make HTTP requests. We've already introduced this, but now we'll use it more extensively. +* **API Endpoints:** Specific URLs that expose different functionalities of the Wikipedia API. Each endpoint accepts certain parameters and returns data in a specific format (usually JSON). +* **HTTP Methods:** Standard methods for interacting with web servers. We'll primarily use the `GET` method to retrieve data from the Wikipedia API. +* **Asynchronous Operations:** Making network requests is an asynchronous operation. We'll use `async/await` to handle these operations and prevent our application from blocking. + +## Set up +Make sure you have completed Chapter 10 and have a working Dart project set up with the `cli`, `command_runner`, and `wikipedia` packages. + +## Tasks +In this lesson, we'll implement the Wikipedia API calls in the `wikipedia` package and integrate them into the `cli` package. This will involve modifying the `wikipedia` package to include API calling functions, and updating the command runner files. + +### Implement Wikipedia API Calls in `wikipedia` +1. Open `wikipedia/lib/src/api/summary.dart` in your code editor. + +2. Replace the existing code with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'dart:convert'; + import 'dart:io'; + + import 'package:http/http.dart' as http; + + import '../model/summary.dart'; + + // [Step 11 update] + Future getRandomArticleSummary() async { + final http.Client client = http.Client(); + try { + final Uri url = Uri.https( + 'en.wikipedia.org', + '/api/rest_v1/page/random/summary', + ); + final http.Response response = await client.get(url); + if (response.statusCode == 200) { + final Map jsonData = + jsonDecode(response.body) as Map; + return Summary.fromJson(jsonData); + } else { + throw HttpException( + '[WikipediaDart.getRandomArticle] ' + 'statusCode=${response.statusCode}, body=${response.body}', + ); + } + } on FormatException { + // todo: log exceptions + rethrow; + } finally { + client.close(); + } + } + + // The title must match exactly + Future getArticleSummaryByTitle(String articleTitle) async { + final http.Client client = http.Client(); + try { + final Uri url = Uri.https( + 'en.wikipedia.org', + '/api/rest_v1/page/summary/$articleTitle', + ); + final http.Response response = await client.get(url); + if (response.statusCode == 200) { + final Map jsonData = + jsonDecode(response.body) as Map; + return Summary.fromJson(jsonData); + } else { + throw HttpException( + '[WikipediaDart.getArticleSummary] ' + 'statusCode=${response.statusCode}, body=${response.body}', + ); + } + } on FormatException { + // todo: log exceptions + rethrow; + } finally { + client.close(); + } + } + ``` + + * This code defines two functions for retrieving article summaries from the Wikipedia API: + * `getRandomArticleSummary()`: Retrieves a summary of a random article. + * `getArticleSummaryByTitle(String articleTitle)`: Retrieves a summary of an article with the specified title. + * Both functions use the `http` package to make `GET` requests to the Wikipedia API endpoints. + * The `jsonDecode` function is used to parse the JSON response into a `Map`. + * The `Summary.fromJson` method is used to deserialize the JSON data into a `Summary` object. + * Error handling is included to check for HTTP status codes other than 200 (OK) and to rethrow any `FormatException` that might occur during JSON parsing. + * `client.close()` is included in the `finally{}` block, which ensures that the client is closed after the operation is complete. + * `rethrow` allows the errors to bubble up to the call stack, allowing the `CommandRunner` to use the error handler if it exists. + +3. Open `wikipedia/lib/src/api/search.dart` in your code editor. + +4. Replace the existing code with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'dart:convert'; + import 'dart:io'; + + import 'package:http/http.dart' as http; + + import '../model/search_results.dart'; + + // [Step 11 update] + Future search(String searchTerm) async { + final http.Client client = http.Client(); + try { + final Uri url = Uri.https( + 'en.wikipedia.org', + '/w/api.php', + { + 'action': 'opensearch', + 'format': 'json', + 'search': searchTerm, + }, + ); + final http.Response response = await client.get(url); + if (response.statusCode == 200) { + final List jsonData = jsonDecode(response.body) as List; + return SearchResults.fromJson(jsonData); + } else { + throw HttpException( + '[WikimediaApiClient.getArticleByTitle] ' + 'statusCode=${response.statusCode}, ' + 'body=${response.body}', + ); + } + } on FormatException { + rethrow; + } finally { + client.close(); + } + } + ``` + + * This code defines the `search` function for searching Wikipedia articles. + * This function uses the `http` package to make `GET` requests to the Wikipedia API endpoints. + * The `jsonDecode` function is used to parse the JSON response into a `List`. + * The `SearchResults.fromJson` method is used to deserialize the JSON data into a `SearchResults` object. + * Error handling is included to check for HTTP status codes other than 200 (OK) and to rethrow any `FormatException` that might occur during JSON parsing. + * `client.close()` is included in the `finally{}` block, which ensures that the client is closed after the operation is complete. + +5. Open `wikipedia/lib/src/api/get_article.dart` in your code editor. + +6. Replace the existing code with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'dart:convert'; + import 'dart:io'; + + import 'package:http/http.dart' as http; + + import '../model/article.dart'; + + // [Step 11 update] + Future> getArticleByTitle(String title) async { + final http.Client client = http.Client(); + try { + final Uri url = Uri.https( + 'en.wikipedia.org', + '/w/api.php', + { + // order matters - explaintext must come after prop + 'action': 'query', + 'format': 'json', + 'titles': title.trim(), + 'prop': 'extracts', + 'explaintext': '', + }, + ); + final http.Response response = await client.get(url); + if (response.statusCode == 200) { + final Map jsonData = + jsonDecode(response.body) as Map; + return Article.listFromJson(jsonData); + } else { + throw HttpException( + '[ApiClient.getArticleByTitle] ' + 'statusCode=${response.statusCode}, ' + 'body=${response.body}', + ); + } + } on FormatException { + // TODO: log + rethrow; + } finally { + client.close(); + } + } + ``` + + * This code defines the `getArticleByTitle` function for getting a full wikipedia article. + * This function uses the `http` package to make `GET` requests to the Wikipedia API endpoints. + * The `jsonDecode` function is used to parse the JSON response into a `Map`. + * The `Article.listFromJson` method is used to deserialize the JSON data into a `List
`. + * Error handling is included to check for HTTP status codes other than 200 (OK) and to rethrow any `FormatException` that might occur during JSON parsing. + * `client.close()` is included in the `finally{}` block, which ensures that the client is closed after the operation is complete. + +7. Add the following exports to `wikipedia/lib/wikipedia.dart`: + + ```dart + export 'src/api/get_article.dart'; + export 'src/api/search.dart'; + export 'src/api/summary.dart'; + export 'src/model/article.dart'; + export 'src/model/search_results.dart'; + export 'src/model/summary.dart'; + export 'src/model/title_set.dart'; + ``` + +### Update CLI to Implement Wikipedia API Calls + +1. Open `cli/lib/cli.dart` and replace it with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + // [Step 12 update] Export these files, plus the files are all brand new + export 'src/commands/get_article.dart'; + export 'src/commands/search.dart'; + export 'src/logger.dart'; + ``` +2. Create `cli/lib/src/commands/get_article.dart` in your code editor. + +3. Replace the existing code with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'dart:async'; + import 'dart:io'; + + import 'package:command_runner/command_runner.dart'; + import 'package:logging/logging.dart'; + import 'package:wikipedia/wikipedia.dart'; + + class GetArticleCommand extends Command { + GetArticleCommand({required this.logger}); + + final Logger logger; + + @override + String get description => 'Read an article from Wikipedia'; + + @override + String get name => 'article'; + + @override + String get help => 'Gets an article by exact canonical wikipedia title.'; + + @override + String get defaultValue => 'cat'; + + @override + String get valueHelp => 'STRING'; + + @override + FutureOr run(ArgResults args) async { + try { + var title = args.commandArg ?? defaultValue; + final List
articles = await getArticleByTitle(title); + // API returns a list of articles, but we only care about the closest hit. + final article = articles.first; + final buffer = StringBuffer('\n=== ${article.title} ===\n\n'); + buffer.write(article.extract.split(' ').take(500).join(' ')); + return buffer.toString(); + } on HttpException catch (e) { + logger + ..warning(e.message) + ..warning(e.uri) + ..info(usage); + return e.message; + } on FormatException catch (e) { + logger + ..warning(e.message) + ..warning(e.source) + ..info(usage); + return e.message; + } + } + } + ``` + + * This code defines a `GetArticleCommand` class that implements the `Command` abstract class from the `command_runner` package. + * The `run` method retrieves the article title from the command-line arguments and calls the `getArticleByTitle` function from the `wikipedia` package. + * The `run` method prints the title and first 500 words to the CLI output. + +4. Create `cli/lib/src/commands/search.dart` in your code editor. + +5. Replace the existing code with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'dart:async'; + import 'dart:io'; + + import 'package:command_runner/command_runner.dart'; + import 'package:logging/logging.dart'; + import 'package:wikipedia/wikipedia.dart'; + + class SearchCommand extends Command { + SearchCommand({required this.logger}) { + addFlag( + 'im-feeling-lucky', + help: + 'If true, prints the summary of the top article that the search returns.', + ); + } + + final Logger logger; + + @override + String get description => 'Search for Wikipedia articles.'; + + @override + String get name => 'search'; + + @override + String get valueHelp => 'STRING'; + + @override + String get help => + 'Prints a list of links to Wikipedia articles that match the given term.'; + + @override + FutureOr run(ArgResults args) async { + if (requiresArgument && + (args.commandArg == null || args.commandArg!.isEmpty)) { + return 'Please include a search term'; + } + + final buffer = StringBuffer('Search results:'); + try { + final SearchResults results = await search(args.commandArg!); + + if (args.flag('im-feeling-lucky')) { + final title = results.results.first.title; + final Summary article = await getArticleSummaryByTitle(title); + buffer.writeln('Lucky you!'); + buffer.writeln(article.titles.normalized.titleText); + if (article.description != null) { + buffer.writeln(article.description); + } + buffer.writeln(article.extract); + buffer.writeln(); + buffer.writeln('All results:'); + } + + for (var result in results.results) { + buffer.writeln('${result.title} - ${result.url}'); + } + return buffer.toString(); + } on HttpException catch (e) { + logger + ..warning(e.message) + ..warning(e.uri) + ..info(usage); + return e.message; + } on FormatException catch (e) { + logger + ..warning(e.message) + ..warning(e.source) + ..info(usage); + return e.message; + } + } + } + ``` + + * This code defines a `SearchCommand` class that implements the `Command` abstract class from the `command_runner` package. + * The `run` method retrieves the search term from the command-line arguments and calls the `search` function from the `wikipedia` package. + * The `run` method iterates through the search results and prints each result in the buffer, so that it can be returned to the CLI output. + +6. Open `cli/bin/cli.dart` and replace it with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'package:cli/cli.dart'; + import 'package:command_runner/command_runner.dart'; + + // [Step 12 update] Adds errorLogger, updates the `onError` callback, + // adds two ..addCommand calls + void main(List arguments) async { + final errorLogger = initFileLogger('errors'); + final app = + CommandRunner( + onOutput: (String output) async { + await write(output); + }, + onError: (Object error) { + if (error is Error) { + errorLogger.severe( + '[Error] ${error.toString()}\n${error.stackTrace}', + ); + throw error; + } + if (error is Exception) { + errorLogger.warning(error); + } + }, + ) + ..addCommand(HelpCommand()) + ..addCommand(SearchCommand(logger: errorLogger)) + ..addCommand(GetArticleCommand(logger: errorLogger)); + + app.run(arguments); + } + ``` +7. Create `cli/lib/src/logger.dart` in your code editor. + +8. Add the following code to the file: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'dart:io'; + + import 'package:logging/logging.dart'; + + /// Creates a logger that logs to a txt file + Logger initFileLogger(String name) { + hierarchicalLoggingEnabled = true; + final logger = Logger(name); + final now = DateTime.now(); + + final segments = Platform.script.path.split('/'); + final projectDir = segments.sublist(0, segments.length - 2).join('/'); + final dir = Directory('$projectDir/logs'); + if (!dir.existsSync()) dir.createSync(); + final logFile = File( + '${dir.path}/${now.year}_${now.month}_${now.day}_$name.txt', + ); + + logger.level = Level.ALL; + logger.onRecord.listen((record) { + final msg = + '[${record.time} - ${record.loggerName}] ${record.level.name}: ${record.message}'; + logFile.writeAsStringSync('$msg \n', mode: FileMode.append); + }); + + return logger; + } + ``` + + * Because this logger writes to a file, it would be bad to include this logic directly in the `run` method. + +### Run the Updated Application + +1. Open your terminal or command prompt. + +2. Navigate to the root directory of your project (`cli`). + +3. Run the following commands and observe the output: + + ```bash + dart run bin/cli.dart search dart + ``` + + Output: + + ```text + Search results:Dart - https://en.wikipedia.org/wiki/Dart + Darth Vader - https://en.wikipedia.org/wiki/Darth_Vader + Dartmouth College - https://en.wikipedia.org/wiki/Dartmouth_College + Darts - https://en.wikipedia.org/wiki/Darts + Darth Maul - https://en.wikipedia.org/wiki/Darth_Maul + Dartford Crossing - https://en.wikipedia.org/wiki/Dartford_Crossing + Dart (programming language) - https://en.wikipedia.org/wiki/Dart_(programming_language) + Dartmouth College fraternities and sororities - https://en.wikipedia.org/wiki/Dartmouth_College_fraternities_and_sororities + Dartmoor - https://en.wikipedia.org/wiki/Dartmoor + Dartmouth, Massachusetts - https://en.wikipedia.org/wiki/Dartmouth,_Massachusetts + ``` + + ```bash + dart run bin/cli.dart article cat + ``` + + Output: + + ```text + + === Cat === + + The cat (Felis catus), also referred to as the domestic cat, is a small domesticated carnivorous mammal. It is the only domesticated species of the family Felidae. Advances in archaeology and genetics have shown that the domestication of the cat occurred in the Near East around 7500 BC. It is commonly kept as a pet and farm cat, but also ranges freely as a feral cat avoiding human contact. It is valued by humans for companionship and its ability to kill vermin. Its retractable claws are adapted to killing small prey species such as mice and rats. It has a strong, flexible body, quick reflexes, and sharp teeth, and its night vision and sense of smell are well developed. It is a social species, but a solitary hunter and a crepuscular predator. Cat communication includes vocalizations—including meowing, purring, trilling, hissing, growling, and grunting—as well as body language. It can hear sounds too faint or too high in frequency for human ears, such as those made by small mammals. It secretes and perceives pheromones. + Female domestic cats can have kittens from spring to late autumn in temperate zones and throughout the year in equatorial regions, with litter sizes often ranging from two to five kittens. Domestic cats are bred and shown at events as registered pedigreed cats, a hobby known as cat fancy. Animal population control of cats may be achieved by spaying and neutering, but their proliferation and the abandonment of pets has resulted in large numbers of feral cats worldwide, contributing to the extinction of bird, mammal, and reptile species. + As of 2017, the domestic cat was the second most popular pet in the United States, with 95.6 million cats owned and around 42 million households owning at least one cat. In the United Kingdom, 26% of adults have a cat, with an estimated population of 10.9 million pet cats as of 2020. As of 2021, there were an estimated 220 million owned and 480 million stray cats in the world. + + == Etymology and naming == + The origin of the English word cat, Old English catt, is thought to be the Late Latin word cattus, which was first used at the beginning of the 6th century. The Late Latin word may be derived from an unidentified African language. The Nubian word kaddîska 'wildcat' and Nobiin kadís are possible sources or cognates. + The forms might also have derived from an ancient Germanic word that was absorbed into Latin and then into Greek, Syriac, and Arabic. The word may be derived from Germanic and Northern European languages, and ultimately be borrowed from Uralic, cf. Northern Sámi gáđfi, 'female stoat', and Hungarian hölgy, 'lady, female stoat'; from Proto-Uralic *käčwä, 'female (of a furred animal)'. + The English puss, extended as pussy and pussycat, is attested from the 16th century and may have been introduced from Dutch poes or from Low German puuskatte, related to Swedish kattepus, or Norwegian pus, pusekatt. Similar forms exist in Lithuanian puižė and Irish puisín or puiscín. The etymology of this word is unknown, but it may have arisen from a sound used to attract a cat. + A male cat is called a tom or tomcat (or a gib, if neutered). A female is called a queen (or sometimes a molly, if spayed). A juvenile cat is referred to as a kitten. In Early Modern English, the word kitten was interchangeable with the now-obsolete word catling. A group of cats can be referred to as a clowder, a glaring, or a colony. + + == Taxonomy == + The scientific name Felis catus was proposed by Carl Linnaeus in 1758 for a domestic cat. Felis catus domesticus was proposed by Johann Christian Polycarp Erxleben in 1777. Felis daemon proposed by Konstantin Satunin in 1904 was a black cat from the Transcaucasus, later identified as a domestic cat. + In 2003, the International Commission on Zoological Nomenclature ruled that the domestic cat is a distinct species, namely Felis catus. In 2007, the modern domesticated subspecies F. silvestris catus sampled worldwide was considered to have probably descended from the African wildcat (F. lybica), following results of phylogenetic research. In 2017, the IUCN Cat Classification Taskforce followed the recommendation of the ICZN in regarding the domestic cat as a distinct species, Felis catus. + + == Evolution == + + The domestic cat is a member of the Felidae, a family that had a common ancestor about 10 to 15 million years ago. The evolutionary radiation of the Felidae began in Asia during the Miocene around 8.38 to 14.45 million years ago. Analysis of mitochondrial DNA of all Felidae species indicates a radiation at 6.46 to 16.76 million years ago. The genus Felis genetically diverged from other Felidae around 6 to 7 million years ago. Results of phylogenetic research shows that the wild members of this genus evolved through sympatric or parapatric speciation, whereas the domestic cat evolved through artificial selection. The domestic cat and its closest wild ancestor are diploid and both possess 38 chromosomes and roughly 20,000 genes. + + === Domestication === + + It was long thought that the domestication of the cat began in ancient Egypt, where cats were venerated from around 3100 BC. However, the earliest known indication for the taming of an African wildcat was excavated close by a human Neolithic grave in Shillourokambos, southern Cyprus, dating to about 7500–7200 BC. Since there is no evidence of native mammalian fauna on Cyprus, the inhabitants of this Neolithic village most likely brought the cat and other wild mammals to the island from the Middle Eastern mainland. Scientists therefore assume that African wildcats were attracted to early human settlements in the Fertile Crescent by rodents, in particular, the house mouse (Mus musculus), and were tamed by Neolithic farmers. This mutual relationship between early farmers and tamed cats lasted thousands of years. As agricultural practices spread, so did tame and domesticated cats. Wildcats of Egypt contributed to the maternal gene pool of the domestic cat at a later time. + The earliest known evidence for the occurrence of the domestic cat in Greece dates to around 1200 BC. Greek, Phoenician, Carthaginian and Etruscan traders introduced domestic cats to southern Europe. By the 5th century BC, they were familiar animals around settlements in Magna Graecia and Etruria. During the Roman Empire, they were introduced to Corsica and Sardinia before the beginning of the 1st century AD. By the end of the Western Roman Empire in the 5th century, the Egyptian domestic cat lineage had arrived in a Baltic Sea port in northern Germany. + The leopard cat (Prionailurus bengalensis) was tamed independently in China around 5500 BC. This line of partially domesticated cats leaves no trace in the domestic cat populations of today. + During domestication, cats have undergone only minor changes in anatomy and behavior, and they are still capable of surviving in the wild. Several natural behaviors and characteristics of wildcats may have pre-adapted them for domestication as pets. These traits include their small size, social nature, obvious body language, love of play, and high intelligence. Since they practice rigorous grooming habits and have an instinctual drive to bury and hide their urine and feces, they are generally much less messy than other domesticated animals. Captive Leopardus cats may also display affectionate behavior toward humans but were not domesticated. House cats often mate with feral cats. Hybridization between domestic and other Felinae species is also possible, producing hybrids such as the Kellas cat in Scotland. + Development of cat breeds started in the mid 19th century. An analysis of the domestic cat genome revealed that the ancestral wildcat genome was significantly altered in the process of domestication, as specific mutations were selected to develop cat breeds. Most breeds are founded on random-bred domestic cats. Genetic diversity of these breeds varies between regions, and is lowest in purebred populations, which show more than 20 deleterious genetic disorders. + + == Characteristics == + + === Size === + The domestic cat has a smaller skull and shorter bones than the European wildcat. It averages about 46 cm (18 in) in head-to-body length and 23–25 cm (9.1–9.8 in) in height, with about 30 cm (12 in) long tails. Males are larger than females. Adult domestic cats typically weigh 4–5 kg (8.8–11.0 lb). + + === Skeleton === + Cats have seven cervical vertebrae (as do most mammals); 13 thoracic vertebrae (humans have 12); seven lumbar vertebrae (humans have five); three sacral vertebrae (as do most mammals, but humans have five); and a variable number of caudal vertebrae in the tail (humans have only three to five vestigial caudal vertebrae, fused into an internal coccyx).: 11  The extra lumbar and thoracic vertebrae account for the cat's spinal mobility and flexibility. Attached to the spine are 13 ribs, the shoulder, and the pelvis.: 16  Unlike human arms, cat forelimbs are attached to the shoulder by free-floating clavicle bones which allow them to pass their body through any space into which they can fit their head. + + === Skull === + + The cat skull is unusual among mammals in having very large eye sockets and a powerful specialized jaw.: 35  Within the jaw, cats have teeth adapted for killing prey and tearing meat. When it overpowers its prey, a cat delivers a lethal neck bite with its two long canine teeth, inserting them between two of the prey's vertebrae and severing its spinal cord, causing irreversible paralysis and death. Compared to other felines, domestic cats have narrowly spaced canine teeth relative to the size of their jaw, which is an adaptation to their preferred prey of small rodents, which have small vertebrae. + The premolar and first molar together compose the carnassial pair on each side of the mouth, which efficiently shears meat into small pieces, like a pair of scissors. These are vital in feeding, since cats' small molars cannot chew food effectively, and cats are largely incapable of mastication.: 37  Cats tend to have better teeth than most humans, with decay generally less likely because of a thicker protective layer of enamel, a less damaging saliva, less retention of food particles between teeth, and a diet mostly devoid of sugar. Nonetheless, they are subject to occasional tooth loss and infection. + + === Claws === + + Cats have protractible and retractable claws. In their normal, relaxed position, the claws are sheathed with the skin and fur around the paw's toe pads. This keeps the claws sharp by preventing wear from contact with the ground and allows for the silent stalking of prey. The claws on the forefeet are typically sharper than those on the hindfeet. Cats can voluntarily extend their claws on one or more paws. They may extend their claws in hunting or self-defense, climbing, kneading, or for extra traction on soft surfaces. Cats shed the outside layer of their claw sheaths when scratching rough surfaces. + Most cats have five claws on their front paws and four on their rear paws. The dewclaw is proximal to the other claws. More proximally is a protrusion which appears to be a sixth "finger". This special feature of the front paws on the inside of the wrists has no function in normal walking but is thought to be an antiskidding device used while jumping. Some cat breeds are prone to having extra digits ("polydactyly"). Polydactylous cats occur along North America's northeast coast and in Great Britain. + + === Ambulation === + The cat is digitigrade. It walks on the toes, with the bones of the feet making up the lower part of the visible leg. Unlike most mammals, it uses a "pacing" gait and moves both legs on one side of the body before the legs on the other side. It registers directly by placing each hind paw close to the track of the corresponding fore paw, minimizing noise and visible tracks. This also provides sure footing for hind paws when navigating rough terrain. As it speeds up from walking to trotting, its gait changes to a "diagonal" gait: The diagonally opposite hind and fore legs move simultaneously. + + === Balance === + + Cats are generally fond of sitting in high places or perching. A higher place may serve as a concealed site from which to hunt; domestic cats strike prey by pouncing from a perch such as a tree branch. Another possible explanation is that height gives the cat a better observation point, allowing it to survey its territory. A cat falling from heights of up to 3 m (9.8 ft) can right itself and land on its paws. + During a fall from a high place, a cat reflexively twists its body and rights itself to land on its feet using its acute sense of balance and flexibility. This reflex is known as the cat righting reflex. A cat always rights itself in the same way during a fall, if it has enough time to do so, which is the case in falls of 90 cm (3.0 ft) or more. How cats are able to right themselves when falling has been investigated as the "falling cat problem". + + === Coats === + + The cat family (Felidae) can pass down many colors and patterns to their offspring. The domestic cat genes MC1R and ASIP allow color variety in their coats. The feline ASIP gene consists of three coding exons. Three novel microsatellite markers linked to ASIP were isolated from a domestic cat BAC clone containing this gene to perform linkage analysis on 89 domestic cats segregated for melanism. The domestic cat family demonstrated a cosegregation between the ASIP allele and coat black coloration. + + == Senses == + + === Vision === + + Cats have excellent night vision and can see at one sixth the light level required for human vision.: 43  This is partly the result of cat eyes having a tapetum lucidum, which reflects any light that passes through the retina back into the eye, thereby increasing the eye's sensitivity to dim light. Large pupils are an adaptation to dim light. The domestic cat has slit pupils, which allow it to focus bright light without chromatic aberration. At low light, a cat's pupils expand to cover most of the exposed surface of its eyes. The domestic cat has rather poor color vision and only two types of cone cells, optimized for sensitivity to blue and yellowish green; its ability to distinguish between red and green is limited. A response to middle wavelengths from a system other \ No newline at end of file diff --git a/src/content/gse/chapter_chapter_2.md b/src/content/gse/chapter_chapter_2.md new file mode 100644 index 0000000000..5931ab0494 --- /dev/null +++ b/src/content/gse/chapter_chapter_2.md @@ -0,0 +1,199 @@ +--- +title: "Chapter 2: Basic Dart Syntax" +description: "Learn about Control flow (`if/else`), collections (`List`, `isEmpty`, `.first`, `for-in`), variables, `const`, null checks, functions, strings, interpolation, `stdin`." +--- + +# Chapter 2: Basic Dart Syntax +Learn about Control flow (`if/else`), collections (`List`, `isEmpty`, `.first`, `for-in`), variables, `const`, null checks, functions, strings, interpolation, `stdin`. + +[Video Placeholder] + +In this lesson, you'll learn the fundamentals of Dart syntax, enabling you to build interactive command-line applications. We will cover control flow using `if/else` statements, working with collections like `List`, handling user input via `stdin`, defining functions, and manipulating strings. We'll put these concepts into practice by building a command-line tool that responds to user input and provides basic functionality. + +## Background / Key Concepts +* **Variables:** Variables are named storage locations in memory that hold data. In Dart, you can declare variables using `var`, `final`, or `const`. +* **`const`:** Used to define compile-time constants. Their values must be known at compile time and cannot be changed during runtime. +* **Control Flow (`if/else`):** `if/else` statements allow you to execute different blocks of code based on whether a condition is true or false. +* **Null Checks:** Dart has a strong type system that helps prevent null pointer exceptions. You can use the null-aware operator `?` and the null-assertion operator `!` to handle nullable variables. +* **Collections (`List`):** A `List` is an ordered collection of items. You can access elements by their index, add or remove elements, and iterate over the list. +* **`isEmpty`:** A method on `List` that returns `true` if the list contains no elements. +* **`.first`:** A property on `List` that returns the first element of the list. +* **`for-in` Loop:** A convenient way to iterate over the elements of a collection. +* **Functions:** Functions are reusable blocks of code that perform a specific task. They can accept arguments (inputs) and return values (outputs). +* **Strings:** A sequence of characters. Dart provides rich string manipulation capabilities, including interpolation. +* **Interpolation:** A way to embed expressions directly into strings using the `${}` syntax. +* **`stdin`:** A standard input stream that allows you to read data from the command line. + +## Set up +Make sure you have completed Chapter 1 and have a working Dart project set up. We will be modifying the `bin/cli.dart` file in this chapter. The name of your project may be different than `cli`, so be sure to adjust for that as you proceed. + +## Tasks +In this lesson, we'll modify our Dart program to read user input, implement basic command-line arguments, and add a placeholder command. + +### Update `pubspec.yaml` + +1. Navigate to the `cli` directory (or the name of your project) +2. Open your `pubspec.yaml` file. +3. Edit the `name:` field to be `cli`. +4. The top of the file should look like this: + + ```yaml + name: cli + description: A sample command-line application. + version: 1.0.0 + resolution: workspace + publish_to: none + ``` + +### Implement Command-Line Arguments + +1. Open `cli/bin/cli.dart` in your code editor. + +2. Replace the existing code with the following: + + ```dart + import 'dart:io'; + + const version = '0.0.1'; + + void main(List arguments) { + if (arguments.isNotEmpty && arguments.first == 'version') { + print('Dart Wikipedia version $version'); + } else if (arguments.isNotEmpty && arguments.first == 'help') { + printUsage(); + } else if (arguments.isNotEmpty && arguments.first == 'wikipedia') { + // contrived + final inputArgs = arguments.length > 1 ? arguments.sublist(1) : null; + runApp(inputArgs); + } else { + printUsage(); + } + } + + void printUsage() { + print( + "The following commands are valid: 'help', 'version', 'wikipedia '", + ); + } + + void runApp(List? arguments) { + late String? articleTitle; + if (arguments == null || arguments.isEmpty) { + print('Please provide an article title.'); + articleTitle = stdin.readLineSync(); + return; + } else { + articleTitle = arguments.join(', '); + } + + print('Looking up articles about $articleTitle. Please wait.'); + for (var arg in arguments) { + print('Here ya go!'); + print('(Pretend this an article about $arg)'); + } + } + ``` + +3. **Explanation:** + * **`import 'dart:io';`:** Imports the `dart:io` library, which provides access to input/output streams, including `stdin`. + * **`const version = '0.0.1';`:** Defines a constant variable `version` to store the application's version number. `const` ensures the value is known at compile time. + * **`main(List arguments)`:** The `main` function now accepts a `List` called `arguments`. This list contains the arguments passed to the program from the command line. + * **`if (arguments.isNotEmpty && arguments.first == 'version') { ... }`:** This `if` statement checks if the `arguments` list is not empty and if the first argument is equal to `'version'`. If both conditions are true, it prints the version number using string interpolation: `print('Dart Wikipedia version $version');`. + * **`else if (arguments.isNotEmpty && arguments.first == 'help') { ... }`:** Similarly, this `else if` statement checks for the `'help'` command and calls the `printUsage()` function. + * **`else { printUsage(); }`:** If no known command is passed, the `else` block is run, which prints the usage instructions. + * **`void printUsage() { ... }`:** This function prints the valid commands that the application accepts. + * **`void runApp(List? arguments) { ... }`:** + * This function now takes a nullable `List` called `arguments` as input. + * `late String? articleTitle;`: This declares a late-initialized nullable String named `articleTitle`. Late initialization means that the variable will be assigned a value before it's used, but not necessarily at the point of declaration. + * **`if (arguments == null || arguments.isEmpty) { ... }`:** This `if` statement checks if the `arguments` list is `null` or empty. If either condition is true, it prompts the user to enter an article title using `stdin.readLineSync()`. + * `stdin.readLineSync()`: This reads a line of text from the console. Because this operation can return `null` we need to guard against that. + * **`else { articleTitle = arguments.join(', '); }`:** If arguments are provided, the code joins them into a single string, separated by commas. + * The code then prints a message indicating that it's looking up articles about the provided title. It also iterates through the arguments, printing a placeholder message for each. + +### Run the Updated Application + +1. Open your terminal or command prompt. + +2. Navigate to the root directory of your project (`cli`). + +3. Run the following commands and observe the output: + + ```bash + dart run bin/cli.dart version + ``` + + Output: + + ```bash + Dart Wikipedia version 0.0.1 + ``` + + ```bash + dart run bin/cli.dart help + ``` + + Output: + + ```bash + The following commands are valid: 'help', 'version', 'wikipedia ' + ``` + + ```bash + dart run bin/cli.dart wikipedia + ``` + + Output: + + ```bash + Please provide an article title. + // The program will now wait for you to enter input + ``` + + Enter an article title (e.g., "Dart Programming") and press Enter. + + ```bash + Looking up articles about Dart Programming. Please wait. + Here ya go! + (Pretend this an article about Dart Programming) + ``` + + ```bash + dart run bin/cli.dart wikipedia Dart is fun + ``` + + Output: + + ```bash + Looking up articles about Dart is fun. Please wait. + Here ya go! + (Pretend this an article about Dart) + Here ya go! + (Pretend this an article about is) + Here ya go! + (Pretend this an article about fun) + ``` + +[Pop out placeholder: Experiment with different command-line arguments and observe how the program responds. Try entering no arguments, or invalid commands to observe the `printUsage()` output.] + +## Review +In this lesson, you learned how to: + +* Read command-line arguments using the `arguments` list in the `main` function. +* Use `if/else` statements to implement control flow based on user input. +* Define constant variables using `const`. +* Read user input from the command line using `stdin.readLineSync()`. +* Work with Lists. +* Interpolate strings using `${}`. +* Use functions to organize and reuse code. + +**Quiz Question:** + +What is the purpose of the `stdin.readLineSync()` function in Dart? + +* [Option A] To print text to the console. +* [Option B] To read a line of text from the command line. +* [Option C] To write data to a file. +* [Option D] To perform mathematical calculations. + +## Next lesson +In the next lesson, we'll introduce asynchronous programming and learn how to fetch data from the internet using the `http` package. This will allow us to retrieve real data from the Wikipedia API and display it in our command-line application. \ No newline at end of file diff --git a/src/content/gse/chapter_chapter_3.md b/src/content/gse/chapter_chapter_3.md new file mode 100644 index 0000000000..f800ed11a5 --- /dev/null +++ b/src/content/gse/chapter_chapter_3.md @@ -0,0 +1,168 @@ +--- +title: "Chapter 3: Intro to Async and HTTP" +description: "Learn about `async/await`, `Future`, `package:http`, `import` statements." +--- + +# Chapter 3: Intro to Async and HTTP +Learn about `async/await`, `Future`, `package:http`, `import` statements. + +[Video Placeholder] + +In this lesson, we'll dive into the world of asynchronous programming in Dart and learn how to interact with web APIs. We'll cover the concepts of `async/await` and `Future`, which are essential for handling operations that take time to complete, such as fetching data from a server. We'll also introduce the `package:http`, which allows us to make HTTP requests. By the end of this lesson, you'll be able to fetch a summary of a Wikipedia article from the Wikipedia API and print the raw JSON response to the console. + +## Background / Key Concepts +* **Asynchronous Programming:** Asynchronous programming allows your program to continue executing other tasks while waiting for a long-running operation to complete. This prevents your program from freezing or becoming unresponsive. +* **`Future`:** A `Future` represents a value that will be available at some point in the future. It's like a promise that a result will be delivered later. +* **`async/await`:** These keywords provide a way to write asynchronous code in a more synchronous style. The `async` keyword marks a function as asynchronous, and the `await` keyword pauses the execution of the function until a `Future` completes. +* **`package:http`:** A popular Dart package that provides a simple and powerful way to make HTTP requests. +* **`import` statements:** Used to include code from other libraries or packages into your current file. This allows you to reuse existing code and avoid writing everything from scratch. + +## Set up +Make sure you have completed Chapter 2 and have a working Dart project set up. We will be modifying the `cli/bin/cli.dart` file in this chapter. We will also be adding the `http` package as a dependency. + +## Tasks +In this lesson, we'll add the `http` package to our project, modify our Dart program to fetch data from the Wikipedia API, and print the raw JSON response to the console. + +### Add the `http` package as a dependency + +1. Open your `cli/pubspec.yaml` file. + +2. Add `http: ^1.3.0` to the `dependencies` section: + + ```yaml + dependencies: + http: ^1.3.0 + ``` + + The `^1.3.0` syntax means that you're compatible with any version starting with 1.3.0 and up to (but not including) 2.0.0. + +3. Run `dart pub get` in your terminal from the `cli` directory to download the new dependency. This command fetches all the packages listed in your `pubspec.yaml` file. + +### Modify `cli/bin/cli.dart` + +1. Open `cli/bin/cli.dart` in your code editor. + +2. Replace the existing code with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'dart:io'; + + import 'package:http/http.dart' as http; + + const version = '0.0.1'; + + void main(List arguments) { + if (arguments.isNotEmpty && arguments.first == 'version') { + print('Dart Wikipedia version $version'); + } else if (arguments.isNotEmpty && arguments.first == 'help') { + printUsage(); + } else if (arguments.isNotEmpty && arguments.first == 'wikipedia') { + // contrived + final inputArgs = arguments.length > 1 ? arguments.sublist(1) : null; + runApp(inputArgs); + } else { + printUsage(); + } + } + + void printUsage() { + print( + "The following commands are valid: 'help', 'version', 'wikipedia '", + ); + } + + void runApp(List? arguments) async { + late String? articleTitle; + if (arguments == null || arguments.isEmpty) { + print('Please provide an article title.'); + articleTitle = stdin.readLineSync(); + return; + } else { + articleTitle = arguments.join(', '); + } + + print('Looking up articles about $articleTitle. Please wait.'); + + // Code from here to end of file is different + var article = await getWikipediaArticle(articleTitle); + print(article); + } + + Future getWikipediaArticle(String articleTitle) async { + final http.Client client = http.Client(); + final Uri url = Uri.https( + 'en.wikipedia.org', + '/api/rest_v1/page/summary/$articleTitle', + ); + final http.Response response = await client.get(url); + if (response.statusCode == 200) { + return response.body; + } + + return 'Error: failed to fetch article $articleTitle'; + } + ``` + +3. **Explanation:** + * **`import 'package:http/http.dart' as http;`:** This line imports the `http` package, which we added as a dependency in the previous step. The `as http` part gives the imported package a namespace, so you can refer to it as `http`. This avoids potential naming conflicts with other libraries. + * **`void runApp(List? arguments) async { ... }`:** The `runApp` function is now marked as `async`. This allows us to use the `await` keyword inside the function. + * **`var article = await getWikipediaArticle(articleTitle);`:** This line calls the `getWikipediaArticle` function, which fetches the article summary from the Wikipedia API. The `await` keyword pauses the execution of the `runApp` function until the `Future` returned by `getWikipediaArticle` completes. The result of the `Future` (the article summary) is then stored in the `article` variable. + * **`Future getWikipediaArticle(String articleTitle) async { ... }`:** This function is also marked as `async` and returns a `Future`, indicating that it will eventually return a string. + * **`final http.Client client = http.Client();`:** Creates a new `http.Client` object. This client is used to make HTTP requests. + * **`final Uri url = Uri.https( ... );`:** Constructs a `Uri` object representing the URL of the Wikipedia API endpoint. + * `'en.wikipedia.org'`: The host (domain) of the API. + * `'/api/rest_v1/page/summary/$articleTitle'`: The path to the specific resource (article summary). The `$articleTitle` part is string interpolation, which inserts the article title into the URL. + * **`final http.Response response = await client.get(url);`:** This line makes an HTTP GET request to the Wikipedia API using the `client.get()` method. The `await` keyword pauses the execution of the `getWikipediaArticle` function until the `Future` returned by `client.get()` completes. The result of the `Future` (the HTTP response) is then stored in the `response` variable. + * **`if (response.statusCode == 200) { ... }`:** This `if` statement checks if the HTTP status code of the response is 200 (OK). If it is, the function returns the body of the response (which is the JSON data). + * **`return response.body;`:** Returns the body of the HTTP response, which contains the JSON data representing the article summary. + * **`return 'Error: failed to fetch article $articleTitle';`:** If the HTTP status code is not 200, the function returns an error message. + +### Run the Updated Application + +1. Open your terminal or command prompt. + +2. Navigate to the root directory of your project (`cli`). + +3. Run the following command and observe the output: + + ```bash + dart run bin/cli.dart wikipedia Dart + ``` + + Output (will vary depending on the article): + + ```bash + Looking up articles about Dart. Please wait. + {"type":"standard","title":"Dart","displaytitle":"Dart","namespace":{"id":0,"key":""},"wikibase_item":"Q186097","titles":{"canonical":"Dart","normalized":"Dart","display":"Dart"},"pageid":16784,"thumbnail":{"height":200,"width":320,"url":"https://upload.wikimedia.org/wikipedia/commons/thumb/7/7e/Dart-logo.png/320px-Dart-logo.png","source":"https://upload.wikimedia.org/wikipedia/commons/thumb/7/7e/Dart-logo.png/320px-Dart-logo.png"},"originalimage":{"height":600,"width":960,"url":"https://upload.wikimedia.org/wikipedia/commons/7/7e/Dart-logo.png","source":"https://upload.wikimedia.org/wikipedia/commons/7/7e/Dart-logo.png"},"lang":"en","dir":"ltr","revision":"1211298259","content_urls":{"desktop":{"page":"https://en.wikipedia.org/wiki/Dart","revisions":"https://en.wikipedia.org/wiki/Special:History/Dart","edit":"https://en.wikipedia.org/w/index.php?action=edit&title=Dart"},"mobile":{"page":"https://en.m.wikipedia.org/wiki/Dart","revisions":"https://en.m.wikipedia.org/wiki/Special:History/Dart","edit":"https://en.m.wikipedia.org/w/index.php?action=edit&title=Dart"}},"extract":"Dart is a structured web programming language developed by Google. It is used to build web, server, desktop, and mobile applications. Dart is an object-oriented, class-based, garbage-collected language with C-style syntax. It supports interfaces, mixins, abstract classes, refined generics, and type inference.","extract_html":"

Dart is a structured web programming language developed by Google. It is used to build web, server, desktop, and mobile applications. Dart is an object-oriented, class-based, garbage-collected language with C-style syntax. It supports interfaces, mixins, abstract classes, refined generics, and type inference.

"} + ``` + +[Pop out placeholder: Experiment with different article titles. Try entering titles that don't exist to see the error message.] + +## Review +In this lesson, you learned how to: + +* Use `async/await` to write asynchronous code in a more synchronous style. +* Work with `Future` objects to represent values that will be available in the future. +* Add external packages to your project using `pubspec.yaml` and `dart pub get`. +* Import packages using `import` statements. +* Make HTTP requests using the `package:http`. +* Fetch data from a web API. +* Handle HTTP responses and check for errors. + +**Quiz Question:** + +What is the purpose of the `await` keyword in Dart? + +* [Option A] To define a constant variable. +* [Option B] To pause the execution of a function until a `Future` completes. +* [Option C] To create a new object. +* [Option D] To import a package. + +## Next lesson +In the next lesson, we'll learn how to organize our code into libraries and packages, making our project more modular and maintainable. We'll create a separate package for our command-line logic and import it into our main application. \ No newline at end of file diff --git a/src/content/gse/chapter_chapter_4.md b/src/content/gse/chapter_chapter_4.md new file mode 100644 index 0000000000..6b390ab09e --- /dev/null +++ b/src/content/gse/chapter_chapter_4.md @@ -0,0 +1,179 @@ +--- +title: "Chapter 4: Packages and Libraries" +description: "Learn about Libraries and `export` statements." +--- + +# Chapter 4: Packages and Libraries +Learn about Libraries and `export` statements. + +[Video Placeholder] + +In this lesson, you'll learn how to refactor your code into reusable components using Dart libraries and packages. We'll focus on creating a separate package for our command-line logic, which will help organize our project and make it more maintainable. By the end of this lesson, you'll understand how to create a new package, move code into it, and import it into your main application. You'll also learn about the `export` statement, which allows you to control which parts of your library are visible to other parts of your application. + +## Background / Key Concepts +* **Packages:** A package is a reusable unit of code that can be shared across multiple projects. Packages typically contain libraries, executables, tests, and other assets. +* **Libraries:** A library is a collection of related code, such as functions, classes, and variables, that can be imported and used in other parts of your application. Libraries help organize code and promote reusability. +* **`export` statement:** The `export` keyword in Dart allows you to re-export symbols (classes, functions, variables, etc.) from one library to another. This means that you can create a library that acts as a facade, exposing a subset of symbols from other libraries. +* **Code Organization:** Structuring your code into well-defined packages and libraries makes it easier to understand, maintain, and test. It also promotes code reuse, which can save time and effort in the long run. +* **Modularity:** Breaking down a large application into smaller, independent modules (packages and libraries) improves its overall structure and reduces dependencies. + +## Set up +Make sure you have completed Chapter 3 and have a working Dart project set up. We will be creating a new package called `command_runner` in this chapter, and modifying both the root `pubspec.yaml` file and `cli/bin/cli.dart` file. + +## Tasks +In this lesson, we'll create a new `command_runner` package, move the command-line handling logic from `cli/bin/cli.dart` into the new package, and update `cli/bin/cli.dart` to import and use the new package. + +### Create a new Dart package +1. Open your terminal or command prompt. + +2. Navigate to the root directory of your project (the directory containing the `cli` directory). + +3. Run the following command: + + ```bash + dart create -t package command_runner + ``` + + This command uses the `dart create` tool to generate a basic Dart package named "command_runner". The `-t package` flag specifies that we want to create a package, not a standalone application. + + You should see output similar to this: + + ```bash + Creating package 'command_runner'... + Running pub get in command_runner... + Wrote command_runner/analysis_options.yaml. + Wrote command_runner/lib/command_runner.dart. + Wrote command_runner/test/command_runner_test.dart. + Wrote command_runner/pubspec.yaml. + Wrote command_runner/README.md. + + All done! + ``` + +### Update the workspace `pubspec.yaml` +1. Open the `pubspec.yaml` file in the root of your workspace. +2. Add the following workspace entry. + + ```yaml + workspace: + - cli + - command_runner + ``` + +### Move the CommandRunner logic + +1. Create the directory `command_runner/lib/src`. + +2. Create the file `command_runner/lib/src/command_runner_base.dart`. + +3. Add the following code to the file. + + ```dart + class CommandRunner { + Future run(List input) async { + print(input); + } + } + ``` + +4. Open `command_runner/lib/command_runner.dart` and add the following code. + + ```dart + /// Support for doing something awesome. + /// + /// More dartdocs go here. + library; + + export 'src/command_runner_base.dart'; + + // TODO: Export any libraries intended for clients of this package. + ``` + + * **`library;`**: Declares the current file as a library. This is generally good practice. + * **`export 'src/command_runner_base.dart';`**: Re-exports the contents of `src/command_runner_base.dart`. This line makes the `CommandRunner` class available to anyone who imports the `command_runner` package. This is a core concept when making packages, since code that is not exported is, by definition, private. + +### Update `cli/pubspec.yaml` to depend on `command_runner` + +1. Open `cli/pubspec.yaml` in your code editor. + +2. Add a dependency on the `command_runner` package in the `dependencies` section. Since `command_runner` is part of the same workspace, we can use a `path` dependency. + + ```yaml + dependencies: + http: ^1.3.0 + command_runner: + path: ../command_runner + ``` + + This tells Dart that the `cli` package depends on the `command_runner` package, and that the `command_runner` package can be found in the `../command_runner` directory (relative to the `cli/pubspec.yaml` file). + +3. Run `dart pub get` in your terminal from the `cli` directory to update the dependencies. + +### Update `cli/bin/cli.dart` to use the `command_runner` package + +1. Open `cli/bin/cli.dart` in your code editor. + +2. Replace the existing code with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'package:command_runner/command_runner.dart'; + + const version = '0.0.1'; + + void main(List arguments) { + var runner = CommandRunner(); + runner.run(arguments); + } + ``` + + * **`import 'package:command_runner/command_runner.dart';`:** This line imports the `command_runner` package, which we created in the previous steps. + * **`var runner = CommandRunner();`:** This line creates an instance of the `CommandRunner` class from the `command_runner` package. + * **`runner.run(arguments);`:** This line calls the `run` method on the `CommandRunner` instance, passing in the command-line arguments. + +### Run the Updated Application + +1. Open your terminal or command prompt. + +2. Navigate to the root directory of your project (`cli`). + +3. Run the following command and observe the output:\ + + ```bash + dart run bin/cli.dart hello world + ``` + + Output: + + ```bash + [hello, world] + ``` + +[Pop out placeholder: Experiment with different command-line arguments and observe the output. The output should be the same as before, but now the command-line handling logic is in a separate package.] + +## Review +In this lesson, you learned how to: + +* Create a new Dart package using the `dart create` tool. +* Move code into a package. +* Depend on a package using the `pubspec.yaml` file and `dart pub get`. +* Import a package using the `import` statement. +* Use the `export` statement to control which parts of a library are visible to other parts of your application. +* Organize code into packages and libraries to improve code structure and maintainability. + +**Quiz Question:** + +What is the purpose of the `export` keyword in Dart? + +* [Option A] To define a constant variable. +* [Option B] To re-export symbols from one library to another. +* [Option C] To create a new object. +* [Option D] To import a package. + +## Next lesson +In the next lesson, we'll dive deeper into object-oriented programming in Dart and learn how to create more complex and flexible command-line applications. We'll introduce concepts like `sealed` and `abstract` classes, generics, inheritance, and overrides. \ No newline at end of file diff --git a/src/content/gse/chapter_chapter_5.md b/src/content/gse/chapter_chapter_5.md new file mode 100644 index 0000000000..4d7597f88b --- /dev/null +++ b/src/content/gse/chapter_chapter_5.md @@ -0,0 +1,470 @@ +--- +title: "Chapter 5: Object-Oriented Dart" +description: "Learn about `sealed` and `abstract` classes, generics, inheritance (`extends`), overrides, `FutureOr`, and `enum`." +--- + +# Chapter 5: Object-Oriented Dart +Learn about `sealed` and `abstract` classes, generics, inheritance (`extends`), overrides, `FutureOr`, and `enum`. + +[Video Placeholder] + +In this lesson, we'll be diving into the world of object-oriented programming (OOP) in Dart. We'll build a framework for creating well-architected command-line interface (CLI) apps. This involves creating classes for `CommandRunner`, `Command`, `Argument`, `ArgResults`, `Option`, and `HelpCommand`. We'll explore powerful Dart features like `sealed` and `abstract` classes, generics, inheritance, overrides, `FutureOr`, and `enum`. By the end of this lesson, you'll have a solid foundation for building robust and maintainable CLI applications in Dart. + +## Background / Key Concepts + +* **Object-Oriented Programming (OOP):** A programming paradigm based on "objects," which contain data (fields) and code (methods) to manipulate that data. OOP promotes code reusability, modularity, and maintainability. +* **Classes:** Blueprints for creating objects. They define the properties (fields) and behaviors (methods) that objects of that class will have. +* **Objects:** Instances of a class. They are concrete entities that have their own state (values of their fields) and can perform actions (call their methods). +* **`sealed` Classes:** Restrict which classes can extend or implement them. This provides more control over inheritance and helps prevent unexpected subtypes. Useful for modeling closed sets of types. +* **`abstract` Classes:** Classes that cannot be instantiated directly. They often define a common interface for their subclasses, forcing them to implement certain methods. +* **Generics:** Allow you to write code that can work with different types of data without having to write separate code for each type. This promotes code reuse and type safety. +* **Inheritance (`extends`):** Enables you to create new classes based on existing classes, inheriting their properties and behaviors. This promotes code reuse and allows you to create a hierarchy of classes. +* **Overrides:** Allows a subclass to provide a specific implementation of a method that is already defined in its superclass. This allows you to customize the behavior of inherited methods. +* **`FutureOr`:** A type that can be either a `Future` or a `T`. It's useful for functions that can return a value immediately or asynchronously. +* **`enum`:** A special type that represents a fixed set of constant values. Enums can improve code readability and type safety. + +## Set up + +Make sure you have completed Chapter 4 and have a working Dart project set up with the `cli` and `command_runner` packages. + +## Tasks + +In this lesson, we'll define the core classes for our command-line framework within the `command_runner` package. This includes `Argument`, `Option`, `Command`, `ArgResults`, and `CommandRunner`. We'll also create a `HelpCommand` to display usage information. + +### Define the `Argument` Classes + +1. Open `command_runner/lib/src/arguments.dart` in your code editor. + +2. Replace the existing code with the following: + + ```dart + /* + * // Copyright 2025 The Dart and Flutter teams. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ + + import 'dart:async'; + import 'dart:collection'; + + import '../command_runner.dart'; + + // [Step 5 updates] entire file + enum OptionType { flag, option } + + sealed class Argument { + String get name; + String? get help; + + // In the case of flags, the default value is a bool + // In other options and commands, the default value is String + // NB: flags are just Option objects that don't take arguments + Object? get defaultValue; + String? get valueHelp; + + String get usage; + } + + class Option extends Argument { + Option( + this.name, { + required this.type, + this.help, + this.abbr, + this.defaultValue, + this.valueHelp, + }); + + @override + final String name; + + final OptionType type; + + @override + final String? help; + + final String? abbr; + + @override + final Object? defaultValue; + + @override + final String? valueHelp; + + @override + String get usage { + if (abbr != null) { + return '-$abbr,--$name: $help'; + } + + return '--$name: $help'; + } + } + + abstract class Command extends Argument { + @override + String get name; + + String get description; + + bool get requiresArgument => false; + + late CommandRunner runner; + + @override + String? help; + + @override + String? defaultValue; + + @override + String? valueHelp; + + final List