diff --git a/building/config.json b/building/config.json index f2114aa5..9302a3e9 100644 --- a/building/config.json +++ b/building/config.json @@ -562,6 +562,13 @@ "title": "Approaches", "blurb": "Learn how to write approaches for exercises" }, + { + "uuid": "362853a0-dc4a-43ee-abbb-d4a14cb5b5bb", + "slug": "tracks/articles", + "path": "building/tracks/articles.md", + "title": "Articles", + "blurb": "Learn how to write articles for exercises" + }, { "uuid": "8db530bb-e11b-4497-b088-5b4c997e09a2", "slug": "tracks/presentation", diff --git a/building/configlet/lint.md b/building/configlet/lint.md index 7c7e4699..6859afa2 100644 --- a/building/configlet/lint.md +++ b/building/configlet/lint.md @@ -322,6 +322,102 @@ The `config.json` file should have the following checks: - The `"test_runner"` key is optional - The `"test_runner"` value must be a boolean +### Rule: exercises/{concept|practice}/<slug>/.approaches/config.json is valid + +- The file's presence is optional, unless there is a `introduction.md` or a sibling directory +- The file must be valid JSON +- The JSON root must be an object +- The `"introduction.authors"` key is optional +- The `"introduction.authors"` value must be an array +- The `"introduction.authors"` values must be non-blank strings¹ +- The `"introduction.authors"` values must not have duplicates +- The `"introduction.authors"` values are treated case-insensitively +- If the `"introduction.authors"` array is non-empty, there must be a non-empty `introduction.md` file +- The `"introduction.contributors"` key is optional +- The `"introduction.contributors"` value must be an array +- The `"introduction.contributors"` values must be non-blank strings¹ +- The `"introduction.contributors"` values must not have duplicates +- The `"introduction.contributors"` values are treated case-insensitively +- If the `"introduction.contributors"` array is non-empty, there must be a non-empty `introduction.md` file +- Users can only be listed in either the `"introduction.authors"` or `"introduction.contributors"` array (no overlap) +- The `"approaches"` key is optional, unless there is a sibling directory present (which contains the approach' files) +- The `"approaches"` value must be an array of objects +- The `"approaches[].uuid"` key is required +- The `"approaches[].uuid"` value must be a unique version 4 UUID string⁶ +- The `"approaches[].uuid"` value for each concept must never change +- The `"approaches[].slug"` key is required +- The `"approaches[].slug"` value must be a kebab-case string² with length <= 255 +- The `"approaches[].slug"` value must have a corresponding non-empty `<slug>/content.md` file +- The `"approaches[].slug"` value must have a corresponding non-empty `<slug>/snippet.txt` file +- The `"approaches[].name"` key is required +- The `"approaches[].name"` value must be a Title Case string³ with length <= 255 +- The `"approaches[].blurb"` key is required +- The `"approaches[].blurb"` value must be a non-blank string¹ with length <= 350 +- The `"approaches[].authors"` key is required +- The `"approaches[].authors"` value must be a non-empty array +- The `"approaches[].authors"` values must be non-blank strings¹ +- The `"approaches[].authors"` values must not have duplicates +- The `"approaches[].authors"` values are treated case-insensitively +- The `"approaches[].contributors"` key is optional +- The `"approaches[].contributors"` value must be an array +- The `"approaches[].contributors"` values must be non-blank strings¹ +- The `"approaches[].contributors"` values must not have duplicates +- The `"approaches[].contributors"` values are treated case-insensitively +- Users can only be listed in either the `"approaches[].authors"` or `"approaches[].contributors"` array (no overlap) + +### Rule: exercises/{concept|practice}/<slug>/.approaches/<approach-slug>/content.md is valid + +- The file's presence is required if a matching `"approaches[].slug"` entry exists in the `.approaches/config.json` file +- The Markdown must conform to the [Markdown standards](/docs/building/markdown/markdown) +- Links must be absolute (relative links are not allowed) + +### Rule: exercises/{concept|practice}/<slug>/.approaches/<approach-slug>/snippet.txt is valid + +- The file's presence is required if a matching `"approaches[].slug"` entry exists in the `.approaches/config.json` file +- The snippet must have at most 8 lines + +### Rule: exercises/{concept|practice}/<slug>/.articles/config.json is valid + +- The file's presence is optional, unless there is a sibling directory +- The file must be valid JSON +- The JSON root must be an object +- The `"articles"` key is optional, unless there is a sibling directory present (which contains the article' files) +- The `"articles"` value must be an array of objects +- The `"articles[].uuid"` key is required +- The `"articles[].uuid"` value must be a unique version 4 UUID string⁶ +- The `"articles[].uuid"` value for each concept must never change +- The `"articles[].slug"` key is required +- The `"articles[].slug"` value must be a kebab-case string² with length <= 255 +- The `"articles[].slug"` value must have a corresponding non-empty `<slug>/content.md` file +- The `"articles[].slug"` value must have a corresponding non-empty `<slug>/snippet.md` file +- The `"articles[].name"` key is required +- The `"articles[].name"` value must be a Title Case string³ with length <= 255 +- The `"articles[].blurb"` key is required +- The `"articles[].blurb"` value must be a non-blank string¹ with length <= 350 +- The `"articles[].authors"` key is required +- The `"articles[].authors"` value must be a non-empty array +- The `"articles[].authors"` values must be non-blank strings¹ +- The `"articles[].authors"` values must not have duplicates +- The `"articles[].authors"` values are treated case-insensitively +- The `"articles[].contributors"` key is optional +- The `"articles[].contributors"` value must be an array +- The `"articles[].contributors"` values must be non-blank strings¹ +- The `"articles[].contributors"` values must not have duplicates +- The `"articles[].contributors"` values are treated case-insensitively +- Users can only be listed in either the `"articles[].authors"` or `"articles[].contributors"` array (no overlap) + +### Rule: exercises/{concept|practice}/<slug>/.articles/<article-slug>/content.md is valid + +- The file's presence is required if a matching `"articles[].slug"` entry exists in the `.articles/config.json` file +- The Markdown must conform to the [Markdown standards](/docs/building/markdown/markdown) +- Links must be absolute (relative links are not allowed) + +### Rule: exercises/{concept|practice}/<slug>/.articles/<article-slug>/snippet.md is valid + +- The file's presence is required if a matching `"articles[].slug"` entry exists in the `.articles/config.json` file +- The Markdown must conform to the [Markdown standards](/docs/building/markdown/markdown) +- The snippet must have at most 8 lines (leading and trailing code fence markers are ignored) + ### Rule: exercises/shared/.docs/debug.md is valid - The file's presence is optional diff --git a/building/tracks/README.md b/building/tracks/README.md index 67cef492..2d477cfe 100644 --- a/building/tracks/README.md +++ b/building/tracks/README.md @@ -20,8 +20,13 @@ Tracks have two types of exercises: - Concept exercises: they are designed to teach one or more concepts to a student. Check the [documentation](/docs/building/tracks/concept-exercises) for more information. - Practice exercises: they are designed to practice learned concepts. Check the [documentation](/docs/building/tracks/practice-exercises) for more information. -Exercises can have approaches associated with them, which describe the different ways in which an exercise can be solved. -Check the [documentation](/docs/building/tracks/approaches) for more information. +### Dig deeper + +Each exercise has an optional Dig Deeper section that can contain: + +- [Approaches](/docs/building/tracks/approaches): different ways in which the exercise can be solved +- [Articles](/docs/building/tracks/articles): describe interesting aspects of the exercise +- Community videos: videos that showcase the exercise, usually by having someone solve the exercise from scratch ## Shared files diff --git a/building/tracks/approaches.md b/building/tracks/approaches.md index d2bd943f..1a2a70be 100644 --- a/building/tracks/approaches.md +++ b/building/tracks/approaches.md @@ -9,7 +9,6 @@ An approach should explore how an exercise can be solved a certain way. - Its contents should either: - Explore an idiomatic approach - Explore a non-idiomatic, but interesting approach - - Contain a meta discussion (e.g. comparing the performance of approaches) - Start with a (full) code sample - Liberally use code samples in the rest of the document - Feel free to dig deep into the topic @@ -17,6 +16,8 @@ An approach should explore how an exercise can be solved a certain way. - The snippet should showcase the core of the approach - A maximum of 8 lines can be used +If you'd like to compare different approaches, please write an [articles](/docs/building/tracks/articles). + ## Approaches overview - Give context to the problem diff --git a/building/tracks/articles.md b/building/tracks/articles.md new file mode 100644 index 00000000..9ec30efc --- /dev/null +++ b/building/tracks/articles.md @@ -0,0 +1,19 @@ +# Articles + +Each exercise can have articles associated with them, which explore some interesting aspect of the exercise. + +## Topics + +Potential topics an article could explore: + +- Comparing the performance of different approaches +- Anything interesting you can come up with! + +## General considerations + +- If your article is based on some code you've written, consider committing that code (within the article directory) + - An example being an article on performance, for which benchmarking code was written + +## What exercises to write articles for? + +Any exercise, as long as there is something interesting to explore. diff --git a/building/tracks/concept-exercises.md b/building/tracks/concept-exercises.md index 11e51f66..fc8cacb0 100644 --- a/building/tracks/concept-exercises.md +++ b/building/tracks/concept-exercises.md @@ -56,6 +56,14 @@ These files describe approaches for the exercise. - `.approaches/<approach-slug>/content.md`: description of the approach (optional) - `.approaches/<approach-slug>/snippet.txt`: snippet showcasing the approach (optional) +### Article files + +These files describe articles for the exercise. + +- `.articles/config.json`: metadata for the articles (optional) +- `.articles/<article-slug>/content.md`: description of the article (optional) +- `.articles/<article-slug>/snippet.md`: snippet showcasing the article (optional) + ### Exercise files The language-specific files, like the implementation and test files. The names of these files are track-specific. @@ -72,11 +80,16 @@ exercises └── concept └── cars-assemble ├── .approaches - | ├── performance (approach) + | ├── for-loop | | ├── content.md | | └── snippet.txt | ├── config.json | └── introduction.md + ├── .articles + | ├── performance + | | ├── content.md + | | └── snippet.md + | └── config.json ├── .docs | ├── introduction.md | ├── instructions.md @@ -403,7 +416,7 @@ If readability is your primary concern (and it usually should be), the LINQ-base **Presence:** Optional (required when an approach introduction or approach exists) -This file contains meta information on the exercise: +This file contains meta information on the exercise's approaches: - `introduction`: The GitHub username(s) of the exercise approach introduction's author(s) (optional) @@ -432,6 +445,96 @@ This file contains meta information on the exercise: "approaches": [ { "uuid": "448fb2b4-18ab-4e55-aa54-ad4ed6d5f7f6", + "slug": "span", + "title": "Use Span<T>", + "blurb": "Use Span<T> to efficiently reverse a string.", + "authors": ["erikschierboom"] + } + ] +} +``` + +--- + +### File: `.approaches/<approach-slug>/content.md` + +**Purpose:** Detailed description of the approach + +**Presence:** Optional (required for approaches) + +This file contains a detailed description of the approach. +Check the [documentation](/docs/building/tracks/approaches) for more information on what should go in this file. + +#### Example + +````markdown +# Span + +```csharp +Span<char> chars = stackalloc char[input.Length]; +for (var i = 0; i < input.Length; i++) +{ + chars[input.Length - 1 - i] = input[i]; +} +return new string(chars); +``` + +This `Span<T>` approach uses a `for` loop. +```` + +--- + +### File: `.approaches/<approach-slug>/snippet.txt` + +**Purpose:** Snippet showcasing the approach + +**Presence:** Optional (required for approaches) + +This file contains a small snippet that showcases the approach. +The snippet is shown on an exercise's dig deeper page. + +Its number of lines must be <= 8. + +Check the [documentation](/docs/building/tracks/approaches) for more information on what should go in this file. + +#### Example + +```csharp +Span<char> chars = stackalloc char[input.Length]; +for (var i = 0; i < input.Length; i++) +{ + chars[input.Length - 1 - i] = input[i]; +} +return new string(chars); +``` + +--- + +### File: `.article/config.json` + +**Purpose:** Metadata for the articles + +**Presence:** Optional (required when an article exists) + +This file contains meta information on the exercise's articles: + +- `articles`: An array listing the detailed articles (optional) + - `uuid`: a V4 UUID that uniquely identifies the article. The UUID must be unique both within the track as well as across all tracks, and must never change + - `slug`: the article's slug, which is a lowercased, kebab-case string. The slug must be unique across all article slugs within the track. Its length must be <= 255. + - `title`: the article's title. Its length must be <= 255. + - `blurb`: A short description of this article. Its length must be <= 350. Markdown is _not_ supported (required) + - `authors`: The GitHub username(s) of the exercise article's author(s) (required) + - Including reviewers if their reviews substantially change the exercise article (to the extent where it feels like "you got there together") + - `contributors`: The GitHub username(s) of the exercise article's contributor(s) (optional) + - Including reviewers if their reviews are meaningful/actionable/actioned. + +#### Example + +```json +{ + "articles": [ + { + "uuid": "6db71962-62d5-448b-a980-c20ae41013ed", "slug": "performance", "title": "Optimizing performance", "blurb": "Explore how to most efficiently reverse a string and what the trade-offs are.", @@ -443,14 +546,14 @@ This file contains meta information on the exercise: --- -### File: `.approaches/<approach-slug>/content.md` +### File: `.articles/<article-slug>/content.md` **Purpose:** Detailed description of the approach **Presence:** Optional (required for approaches) This file contains a detailed description of the approach. -Check the [documentation](/docs/building/tracks/approaches) for more information on what should go in this file. +Check the [documentation](/docs/building/tracks/articles) for more information on what should go in this file. #### Example @@ -469,26 +572,26 @@ In this document, we'll find out which approach is the most performant one. --- -### File: `.approaches/<approach-slug>/snippet.txt` +### File: `.articles/<article-slug>/snippet.txt` **Purpose:** Snippet showcasing the approach -**Presence:** Optional (required for approaches) +**Presence:** Optional (required for articles) -This file contains a small snippet that showcases the approach. -The snippet is shown on an exercise's approaches overview page. +This file contains a small snippet that showcases the article. +The snippet is shown on an exercise's dig deeper page. Its number of lines must be <= 8. +Check the [documentation](/docs/building/tracks/articles) for more information on what should go in this file. + #### Example -```csharp -Span<char> chars = stackalloc char[input.Length]; -for (var i = 0; i < input.Length; i++) -{ - chars[input.Length - 1 - i] = input[i]; -} -return new string(chars); +```markdown +| Method | Mean | Allocated | +| -----: | --------: | --------: | +| Linq | 29.133 ns | 80 B | +| Array | 4.806 ns | - | ``` --- diff --git a/building/tracks/practice-exercises.md b/building/tracks/practice-exercises.md index 7e51bae0..183a69d7 100644 --- a/building/tracks/practice-exercises.md +++ b/building/tracks/practice-exercises.md @@ -74,6 +74,14 @@ These files describe approaches for the exercise. - `.approaches/<approach-slug>/content.md`: description of the approach (optional) - `.approaches/<approach-slug>/snippet.txt`: snippet showcasing the approach (optional) +### Article files + +These files describe articles for the exercise. + +- `.articles/config.json`: metadata for the articles (optional) +- `.articles/<article-slug>/content.md`: description of the article (optional) +- `.articles/<article-slug>/snippet.md`: snippet showcasing the article (optional) + ### Exercise files The language-specific files, like the implementation and test files. The names of these files are track-specific. @@ -90,11 +98,16 @@ exercises └── practice └── isogram ├── .approaches - | ├── performance (approach) + | ├── for-loop | | ├── content.md | | └── snippet.txt | ├── config.json | └── introduction.md + ├── .articles + | ├── performance + | | ├── content.md + | | └── snippet.md + | └── config.json ├── .docs | ├── introduction.md | ├── instructions.md @@ -297,78 +310,29 @@ Note that: - The order of authors and contributors is not significant and has no meaning. - `language_versions` is a free-form string that tracks are free to use and interpret as they like. ---- - -### File: `.approaches/introduction.md` - -**Purpose:** Introduction to the most common approaches for the exercise - -**Presence:** Optional - -This file describes the most common approaches for the exercise. -Check the [documentation](/docs/building/tracks/approaches) for more information on what should go in this file. +the articles -#### Example - -````markdown -# Introduction - -The key to this exercise is to deal with C# strings being immutable, which means that a `string`'s value cannot be changed. -Therefore, to reverse a string you'll need to create a _new_ `string`. - -## Using LINQ - -```csharp -public static string Reverse(string input) -{ - return new string(input.Reverse().ToArray()); -} -``` +**Presence:** Optional (required when an article exists) -For more information, check the [LINQ approach][approach-linq]. +This file contains meta information on the exercise's articles: -## Which approach to use? - -If readability is your primary concern (and it usually should be), the LINQ-based approach is hard to beat. -```` - ---- - -### File: `.approaches/config.json` - -**Purpose:** Metadata for the approaches - -**Presence:** Optional (required when an approach introduction or approach exists) - -This file contains meta information on the exercise: - -- `introduction`: The GitHub username(s) of the exercise approach introduction's author(s) (optional) - - - `authors`: The GitHub username(s) of the exercise approach introduction's author(s) (required) - - Including reviewers if their reviews substantially change the exercise approach introduction (to the extent where it feels like "you got there together") - - `contributors`: The GitHub username(s) of the exercise approach introduction's contributor(s) (optional) - - Including reviewers if their reviews are meaningful/actionable/actioned. - -- `approaches`: An array listing the detailed approaches (optional) - - `uuid`: a V4 UUID that uniquely identifies the approach. The UUID must be unique both within the track as well as across all tracks, and must never change - - `slug`: the approach's slug, which is a lowercased, kebab-case string. The slug must be unique across all approach slugs within the track. Its length must be <= 255. - - `title`: the approach's title. Its length must be <= 255. - - `blurb`: A short description of this approach. Its length must be <= 350. Markdown is _not_ supported (required) - - `authors`: The GitHub username(s) of the exercise approach's author(s) (required) - - Including reviewers if their reviews substantially change the exercise approach (to the extent where it feels like "you got there together") - - `contributors`: The GitHub username(s) of the exercise approach's contributor(s) (optional) +- `articles`: An array listing the detailed articles (optional) + - `uuid`: a V4 UUID that uniquely identifies the article. The UUID must be unique both within the track as well as across all tracks, and must never change + - `slug`: the article's slug, which is a lowercased, kebab-case string. The slug must be unique across all article slugs within the track. Its length must be <= 255. + - `title`: the article's title. Its length must be <= 255. + - `blurb`: A short description of this article. Its length must be <= 350. Markdown is _not_ supported (required) + - `authors`: The GitHub username(s) of the exercise article's author(s) (required) + - Including reviewers if their reviews substantially change the exercise article (to the extent where it feels like "you got there together") + - `contributors`: The GitHub username(s) of the exercise article's contributor(s) (optional) - Including reviewers if their reviews are meaningful/actionable/actioned. #### Example ```json { - "introduction": { - "authors": ["erikschierboom"] - }, - "approaches": [ + "articles": [ { - "uuid": "448fb2b4-18ab-4e55-aa54-ad4ed6d5f7f6", + "uuid": "6db71962-62d5-448b-a980-c20ae41013ed", "slug": "performance", "title": "Optimizing performance", "blurb": "Explore how to most efficiently reverse a string and what the trade-offs are.", @@ -380,14 +344,14 @@ This file contains meta information on the exercise: --- -### File: `.approaches/<approach-slug>/content.md` +### File: `.articles/<article-slug>/content.md` **Purpose:** Detailed description of the approach **Presence:** Optional (required for approaches) This file contains a detailed description of the approach. -Check the [documentation](/docs/building/tracks/approaches) for more information on what should go in this file. +Check the [documentation](/docs/building/tracks/articles) for more information on what should go in this file. #### Example @@ -406,26 +370,26 @@ In this document, we'll find out which approach is the most performant one. --- -### File: `.approaches/<approach-slug>/snippet.txt` +### File: `.articles/<article-slug>/snippet.txt` **Purpose:** Snippet showcasing the approach -**Presence:** Optional (required for approaches) +**Presence:** Optional (required for articles) -This file contains a small snippet that showcases the approach. -The snippet is shown on an exercise's approaches overview page. +This file contains a small snippet that showcases the article. +The snippet is shown on an exercise's dig deeper page. Its number of lines must be <= 8. +Check the [documentation](/docs/building/tracks/articles) for more information on what should go in this file. + #### Example -```csharp -Span<char> chars = stackalloc char[input.Length]; -for (var i = 0; i < input.Length; i++) -{ - chars[input.Length - 1 - i] = input[i]; -} -return new string(chars); +```markdown +| Method | Mean | Allocated | +| -----: | --------: | --------: | +| Linq | 29.133 ns | 80 B | +| Array | 4.806 ns | - | ``` ---