Формат челленджа: папка, этапы, вопросы и тесты

Челлендж — папка в репозитории с файлом challenge.md, этапами в stages/, вопросами и тестами в checks/ и заготовкой в starter/. Всё это обычные markdown-файлы и код — их пишут руками или правят после генерации.

Обновлено Скопировать как Markdown
В этой статье

Папка#

Челлендж — любая папка репозитория, в которой лежит challenge.md. Челленджей в одном репозитории может быть сколько угодно, корень репозитория — тоже папка. Челленджи читаются с ветки по умолчанию.

parser/
  challenge.md              название, описание, уровень, теги
  stages/
    01-zachem.md            этап без проверок — теория
    02-voprosy.md           этап с вопросами
    03-kod.md               этап с кодом и тестами
  checks/                   ответы и тесты
    02-voprosy.md           вопросы этапа 02
    03-kod/test_parser.py   тесты этапа 03
  starter/                  с чего начинается репозиторий попытки
    parser.py
  • stages/ — этапы. Порядок этапов — порядок имён файлов, поэтому имена начинаются с номера с ведущим нулём: 01-…, 02-…, 10-…. Имя без .md — адрес этапа: /{владелец}/{репозиторий}/learn/{папка}/stages/02-voprosy. Челлендж в корне репозитория пишется в адресе как -.
  • checks/ — файлы вопросов с отмеченными ответами и тесты. На экране этапа они не показываются и в репозиторий попытки не копируются. Но это обычные файлы репозитория: в публичном репозитории их может открыть любой — как ответы в конце учебника.
  • starter/ — файлы, с которых начинается репозиторий попытки. Нужна, только если в челлендже есть этапы с проверкой файлов.

Папка с challenge.md внутри starter/ или checks/ другого челленджа — его содержимое, а не отдельный челлендж.

challenge.md#

Шапка плоская — только строки и списки, без вложенных объектов:

поле что в нём
title название — обязательно
summary одна строка для списка челленджей
language язык текстов: ru, en
level beginner, intermediate или advanced — на экране «для начала», «средний», «продвинутый»
tags список [git, основы] или одно слово
source путь к материалу, из которого сделан челлендж, от папки челленджа: ../git.md. Файл обязан существовать; на странице челленджа — ссылка «Материал, из которого он сделан»

Тело — вступление: зачем этот челлендж и что получится в конце. Его показывает страница челленджа.

Этап#

Этап — файл stages/NN-имя.md: шапка и текст задания или теории в markdown. В шапке три поля описывают этап:

поле что в нём
title название этапа — обязательно
summary короткое описание
minutes сколько минут займёт, целым числом — в списке этапов «5 мин»

Все остальные ключи шапки — проверки. Незнакомый ключ — ошибка формата.

ключ как записать этап засчитан, когда
quiz quiz: checks/02-voprosy.md на каждый вопрос файла ответили верно; неверные возвращаются в конце круга
self_check self_check: [пункт, пункт] или список строк - пункт отмечены все пункты — проверяешь себя сам
files files: [konspekt.md, src/parser.py] файлы есть и не пустые
contains contains: и строки путь: текст или путь: ["текст", "текст"] в файле есть каждый текст, регистр не важен
min_words min_words: и строки путь: 150 в файле не меньше стольких слов, шапка файла не считается
numeric numeric: и строки path: answer.md, answer: 42, tolerance: 0.5 последнее число в файле совпало с ответом в пределах допуска; допуск по умолчанию 0, запятая и точка равноправны
tests tests: checks/03-kod/test_parser.py тесты на Python (.py) или JavaScript (.js) прошли в песочнице

Как это складывается:

  • Этап без проверок — теория. Его засчитывает кнопка Прочитано, дальше.
  • Проверки сочетаются. Этап засчитан, когда пройдены все его части: вопросы, самопроверка и файлы. На экране этапа это список «Что проверяется».
  • Два рода путей. quiz и tests указывают на файлы челленджа — от его папки, и файл обязан существовать. files, contains, min_words и numeric указывают на файлы в репозитории попытки — от его корня. Путь не может выходить за корень через ...
  • Проверки по файлам (files, contains, min_words, numeric, tests) читают репозиторий попытки на последнем коммите. Челлендж с такими этапами получает при старте репозиторий попытки, см. Челленджи.
  • Вид этапа в списке берётся из проверок: «Задание» — есть проверки по файлам, иначе «Вопросы» — есть quiz, иначе «Самопроверка», иначе «Теория».

Шапка этапа с проверкой файлов, как в конспекте по методу Корнелла:

---
title: Напиши конспект
files: [konspekt.md]
contains:
  konspekt.md: ["## Заметки", "## Вопросы", "## Итог"]
min_words:
  konspekt.md: 150
---

Расчёт с ответом числом и самопроверка:

---
title: Сколько времени это заняло
files: [skorost.md]
numeric:
  path: skorost.md
  answer: 250
  tolerance: 1
---
---
title: Проверь конспект сам
self_check:
  - В каждом блоке заметок — мысль своими словами, а не цитата
  - Итог умещается в пять предложений и называет главное
---

Файл вопросов#

Вопросы этапа лежат в отдельном markdown-файле в checks/, а шапка этапа ссылается на него: quiz: checks/02-voprosy.md. Файл читается в любом редакторе как обычная заметка.

  • Вопрос — заголовок второго уровня ## …. Заголовок первого уровня и текст до первого вопроса — вступление, вопросом он не считается.
  • В конце заголовка — id в фигурных скобках: ## Что такое HEAD? {#head}. id — латиница в нижнем регистре, цифры, - и _, до 64 знаков, уникален на весь челлендж. По id живут прогресс попытки и расписание повторений: текст вопроса править можно, смена id начинает вопрос с нуля.
  • Вид вопроса узнаётся по форме ответа. Его можно назвать явно классом рядом с id: {#head .card}.
  • Тело читается с конца. Последние строки > … — пояснение, которое показывается после ответа. Перед ними — блок ответа. Всё выше — текст вопроса, поэтому нумерованный список или код в условии с ответом не путаются. В одном блоке ответа — один вид строк: варианты, порядок, пары или ответы = ….
вид как записать что делает пользователь
выбор (single) варианты - [ ] текст, верный один — - [x] текст выбирает один вариант
несколько ответов (multiple) отмечено несколько - [x] или класс .multiple отмечает все подходящие; часть верных — «Частично верно»
порядок (order) нумерованный список 1. в верном порядке расставляет перемешанные пункты
пары (pairs) строки - слева => справа, не меньше двух соединяет; правая колонка перемешана
пропуски (blanks) ___ в тексте вопроса и по строке = ответ на каждый пропуск вписывает слова
число (numeric) = 42 или = 2.5 ± 0.1 (можно +-) вводит число; запятая или точка
короткий ответ (text) = ответ | другой вариант пишет ответ; регистр, ё/е, кавычки и точка в конце не важны
карточка (card) класс .card, под заголовком — обратная сторона текстом вспоминает, открывает ответ и сам оценивает себя

У вариантов их должно быть не меньше двух и хотя бы один отмечен верным; у порядка — не меньше двух пунктов; у карточки нет блока ответа, только текст обратной стороны.

Тесты#

Этап с кодом ссылается на файл тестов в checks/, а файлы, с которых начинается работа, кладутся в starter/ и в шапке перечисляются в files.

  • Тесты — обычный скрипт на Python или JavaScript, язык определяется по расширению. Этап засчитан, если скрипт отработал до конца без ошибки: удобно писать assert с сообщением. Выход раньше конца файла — например, sys.exit(0) в файле решения — не засчитывается.
  • Файлы попытки подключаются по имени (from wordcount import count_words) или по пути (from src.parser import parse). В песочницу уходят файлы попытки того же языка.
  • Прогон идёт в песочнице с ограничением по времени: бесконечный цикл — провал, а не зависание. Вывод тестов показывается под заданием.

Как видны ошибки формата#

Челлендж с ошибками не начинается. Во вкладке Учиться репозитория у него стоит «В файлах челленджа ошибок: N», а на странице челленджа — список: файл, строка и что не так. Например:

parser/stages/03-kod.md: в шапке этапа нет `title`
parser/stages/03-kod.md: неизвестная проверка `test`; есть: quiz, self_check, files, contains, min_words, numeric, tests
parser/stages/02-voprosy.md: `quiz` ссылается на `checks/02.md`, а такого файла в челлендже нет
parser/checks/02-voprosy.md:7: у вопроса «Что такое HEAD?» нет id — допиши в конец заголовка `{#короткий-id}`
parser/checks/02-voprosy.md:15: вопрос `merge-blank`: пропусков `___` в тексте 2, а строк ответа `= …` 1

Поправь файл и сделай коммит — список обновится. Уже начатые попытки ошибки не задевают: каждая закреплена на версии челленджа, с которой её начали.

Пример: челлендж из вопросов#

Челлендж про git: две теории, два этапа вопросов и карточки. Репозитория попытки у него нет — только прогресс.

git-basics/challenge.md:

---
title: "Git: коммит, ветка, слияние"
summary: Как git хранит историю и почему ветка — это просто указатель
language: ru
level: beginner
tags: [git, основы]
---

Git — это не «папка с копиями», а история снимков, где каждый снимок знает своего родителя. Когда это понятно, остальные команды перестают пугать: `commit` добавляет снимок, `branch` заводит ещё один указатель, `merge` сводит две линии истории в одну.

За этот челлендж ты разберёшь три идеи — снимок, ветку и слияние — и закрепишь их вопросами. Вопросы потом вернутся в режиме «Тренировать», чтобы не забылись.

git-basics/stages/01-snimki.md — теория, проверок нет:

---
title: История — это цепочка снимков
minutes: 5
---

## Рабочая копия, индекс, история

У каждого репозитория три места, где живёт файл:

1. **рабочая копия** — файлы, которые ты правишь в редакторе;
2. **индекс** — черновик следующего снимка: сюда попадает то, что ты отметил командой `git add`;
3. **история** — сохранённые снимки (коммиты).

`git commit` берёт индекс целиком и сохраняет его как новый снимок. У снимка есть автор, время, сообщение и ссылка на **родителя** — предыдущий снимок. Так и получается цепочка.

```text
A ← B ← C      (C знает про B, B — про A)
```

## Хэш — имя снимка

Каждый коммит называется хэшем от своего содержимого, например `3f2a9c1`. Поменяй хоть букву в файле или сообщении — получится другой хэш. Поэтому историю нельзя незаметно подменить: подмена меняет имена всех следующих снимков.

git-basics/stages/02-kommit.md — этап вопросов:

---
title: Проверь себя — снимки
quiz: checks/02-kommit.md
---

Пять вопросов про рабочую копию, индекс и историю. Неверные ответы вернутся в конце круга — этап засчитается, когда на каждый ответишь верно.

git-basics/checks/02-kommit.md — выбор одного, выбор нескольких и порядок:

---
title: Снимки
---

## Какая команда кладёт правку в индекс? {#add-to-index}

- [ ] `git commit`
- [x] `git add`
- [ ] `git push`
- [ ] `git status`

> Индекс — черновик следующего снимка. Наполняет его `git add`, а
> `git commit` сохраняет его целиком.

## Что сохраняет `git commit`? {#commit-saves}

- [ ] Все файлы рабочей копии, даже неотмеченные
- [x] Содержимое индекса
- [ ] Только изменённые строки последнего файла

> Коммит берёт индекс целиком: то, что не отмечено `git add`, в снимок не попадёт.

## Расставь путь правки от редактора до истории {#path-of-change}

1. Правишь файл в рабочей копии
2. `git add` — правка попадает в индекс
3. `git commit` — индекс становится снимком

## Что знает каждый коммит, кроме своих файлов? {#commit-knows}

- [x] Своего родителя
- [x] Автора и время
- [ ] Всех своих потомков
- [x] Сообщение

> Коммит ссылается назад, на родителя. Потомков он не знает — они появляются позже.

## Почему нельзя незаметно подменить старый коммит? {#hash-chain}

- [ ] Git запрещает правку файлов после коммита
- [x] Хэш зависит от содержимого, и подмена меняет имена всех следующих снимков
- [ ] Коммиты хранятся на сервере, а не у тебя

> Хэш — отпечаток содержимого и родителя. Изменил старый снимок — поменялись хэши всей цепочки после него.

git-basics/stages/03-vetki.md — вторая теория:

---
title: Ветка — это указатель
minutes: 5
---

## Ветка ничего не копирует

Ветка в git — **файл с одним хэшем**: она указывает на последний коммит своей линии. Завести ветку — записать ещё один указатель, поэтому это мгновенно при любом размере проекта.

`HEAD` — указатель на то, где ты сейчас: обычно на ветку. Новый коммит сдвигает вперёд ту ветку, на которую смотрит `HEAD`.

```text
      main
       ↓
A ← B ← C
         ↖
          D ← E
              ↑
            feature  ← HEAD
```

## Слияние

`git merge feature`, стоя на `main`, сводит две линии. Если `main` за это время не двигалась, git просто передвигает указатель вперёд (**fast-forward**). Если двигались обе — появляется **коммит слияния** с двумя родителями. Конфликт бывает, только когда обе линии правили одни и те же строки.

git-basics/stages/04-vetki-voprosy.md:

---
title: Проверь себя — ветки и слияние
quiz: checks/04-vetki.md
---

Вопросы про указатели, `HEAD` и слияние.

git-basics/checks/04-vetki.md — пары, число, короткий ответ и пропуск:

---
title: Ветки
---

## Что внутри у ветки git? {#branch-is}

- [ ] Копия всех файлов проекта
- [x] Хэш последнего коммита своей линии
- [ ] Список всех коммитов ветки

> Ветка — указатель. Поэтому она заводится мгновенно и ничего не копирует.

## Соотнеси понятие и его смысл {#pointer-pairs}

- ветка => указатель на последний коммит линии
- HEAD => где ты сейчас
- коммит слияния => снимок с двумя родителями

## Сколько родителей у коммита слияния двух веток? {#merge-parents}

= 2

> Слияние сводит две линии истории, поэтому у такого снимка два родителя.

## Как называется слияние, при котором git просто сдвигает указатель вперёд? {#ff-name}

= fast-forward | fast forward | перемотка вперёд

> Так бывает, когда целевая ветка за это время не двигалась.

## Команда `git ___ feature` на ветке `main` сводит линию `feature` в `main` {#merge-blank}

= merge

## Когда при слиянии возникает конфликт? {#conflict}

- [ ] Всегда, если в ветках разное число коммитов
- [x] Когда обе линии правили одни и те же строки
- [ ] Когда ветки названы по-разному

> Разные файлы и разные места одного файла git сводит сам.

git-basics/stages/05-kartochki.md и git-basics/checks/05-kartochki.md — карточки:

---
title: Карточки на повторение
quiz: checks/05-kartochki.md
---

Четыре карточки: вспомни ответ, открой обратную сторону и честно оцени, вспомнил ли. Оценка решает, когда карточка вернётся в «Тренировать».
---
title: Карточки
---

## Что такое индекс в git? {#card-index .card}

Черновик следующего снимка: сюда `git add` кладёт правки, которые войдут в коммит.

## Что такое HEAD? {#card-head .card}

Указатель на то, где ты сейчас, — обычно на текущую ветку.

## Что такое fast-forward? {#card-ff .card}

Слияние без нового коммита: целевая ветка не двигалась, и git просто передвигает её указатель вперёд.

## Чем коммит отличается от сохранения файла? {#card-commit .card}

Коммит — снимок всего индекса с автором, временем, сообщением и ссылкой на родителя; его имя — хэш содержимого.

Пример: этап с кодом и тестами#

Челлендж «Счётчик слов на Python»: пользователь пишет wordcount.py в своём репозитории попытки, тесты проверяют каждый этап.

python-wordcount/
  challenge.md
  stages/
    01-zadacha.md          теория: что считаем словом
    02-count.md            files + tests
    03-top.md              files + tests
    04-voprosy.md          quiz
  checks/
    02-count/test_count.py
    03-top/test_top.py
    04-voprosy.md
  starter/
    wordcount.py

python-wordcount/stages/02-count.md:

---
title: Посчитай слова
files: [wordcount.py]
tests: checks/02-count/test_count.py
---

Напиши `count_words(text)`: сколько слов в тексте по правилу из первого этапа.

```python
count_words("Кот, кот и КОТ!")  # 4
count_words("")                 # 0
count_words("  \n\t ")          # 0
```

Сделай правку и нажми «Проверить» — или сделай `git push`, проверка запустится сама. Если тест упал, внизу будет его вывод.

python-wordcount/checks/02-count/test_count.py:

from wordcount import count_words

assert count_words("Кот, кот и КОТ!") == 4, "«Кот, кот и КОТ!» — четыре слова"
assert count_words("") == 0, "в пустой строке слов нет"
assert count_words("  \n\t ") == 0, "одни пробелы — это ноль слов"
assert count_words("one-two three") == 3, "дефис — разделитель"
assert count_words("Python 3 вышел в 2008") == 5, "числа — тоже слова"
print("count_words: все проверки прошли")

python-wordcount/starter/wordcount.py — с этого файла начинается репозиторий попытки:

"""Частотный анализ текста.

Функции появятся по этапам челленджа — начни с `count_words`.
"""


def count_words(text):
    """Сколько слов в тексте."""
    raise NotImplementedError


def top_words(text, n):
    """n самых частых слов: список пар (слово, сколько раз)."""
    raise NotImplementedError

На заготовке тест падает на NotImplementedError, поэтому этап не засчитывается, пока функция не написана. Свой челлендж удобно начать с копии этих файлов: создай папку в своём репозитории, положи в неё challenge.md и stages/, сделай коммит и открой вкладку Учиться.

Было полезно?