master
Сергей Маринкевич 2 months ago
commit 88c9b41d26

@ -0,0 +1,2 @@
all:
cat quiz.md | python3 quiz.py 1

1805
quiz.md

File diff suppressed because it is too large Load Diff

@ -0,0 +1,193 @@
#!/usr/bin/env python3
import re
import random
import argparse
import textwrap
import sys
from collections import defaultdict
class Question:
"""Класс для хранения информации о вопросе."""
def __init__(self, number, text, options, answer_letter, block, sub_block, level):
self.number = number
self.text = text
self.options = options
self.answer_letter = answer_letter
self.block = block
self.sub_block = sub_block
self.level = level
def __repr__(self):
return f"Q{self.number} (L{self.level}): {self.text[:30]}..."
def parse_markdown(markdown_text):
"""
Парсит весь Markdown текст, извлекая вопросы, ответы и их метаданные.
"""
# 1. Извлечь ответы
answers = {}
answer_section_match = re.search(r"## 🔐 Ответы\n\n((?:.|\n)*)", markdown_text)
if answer_section_match:
answer_text = answer_section_match.group(1)
for match in re.finditer(r"(\d+)\.\s+\*\*([A-D])\*\*", answer_text):
answers[int(match.group(1))] = match.group(2)
# 2. Извлечь вопросы
questions = []
# Разделяем текст на большие блоки (начинаются с ##)
blocks = re.split(r'\n## ', markdown_text)
for block_content in blocks:
if not block_content.strip() or 'Ответы' in block_content:
continue
block_title_match = re.match(r"(.+)", block_content)
block_title = block_title_match.group(1).strip() if block_title_match else "Неизвестный блок"
# Паттерн для поиска одного полного вопроса
question_pattern = re.compile(
r"\+ \*\*(\d+)\. (.+?)\*\*\n" # Номер и текст вопроса
r"\s+\+ A\) ([^\n]+)\n" # Опция A
r"\s+\+ B\) ([^\n]+)\n" # Опция B
r"\s+\+ C\) ([^\n]+)\n" # Опция C
r"\s+\+ D\) ([^\n]+)", # Опция D
re.DOTALL
)
sub_blocks = re.split(r'### Анкета: (.+)', block_content)
current_sub_block = block_title # По умолчанию
for i in range(1, len(sub_blocks), 2):
current_sub_block = sub_blocks[i].strip()
content = sub_blocks[i+1]
levels_content = re.split(r'#### (.+)', content)
for j in range(1, len(levels_content), 2):
level_title = levels_content[j]
level_text = levels_content[j+1]
level = 1
if '🟢🟢' in level_title or '🟡' in level_title:
level = 2
elif '🟢🟢🟢' in level_title or '🔴' in level_title:
level = 3
for match in question_pattern.finditer(level_text):
q_num = int(match.group(1))
options = {
'A': match.group(3).strip(),
'B': match.group(4).strip(),
'C': match.group(5).strip(),
'D': match.group(6).strip(),
}
if q_num in answers:
question = Question(
number=q_num,
text=match.group(2).strip(),
options=options,
answer_letter=answers[q_num],
block=block_title,
sub_block=current_sub_block,
level=level
)
questions.append(question)
return questions
def select_quiz_questions(questions, num_per_block):
"""
Выбирает случайные вопросы из каждого блока, сохраняя баланс сложности.
"""
questions_by_block = defaultdict(list)
for q in questions:
questions_by_block[q.block].append(q)
final_quiz = []
for block, q_list in questions_by_block.items():
if not q_list:
continue
selected_for_block = []
by_level = defaultdict(list)
for q in q_list:
by_level[q.level].append(q)
for level in by_level:
random.shuffle(by_level[level])
level_priority_cycle = [1, 2, 1, 3, 2, 1] # Даём приоритет более простым вопросам
while len(selected_for_block) < num_per_block and any(by_level.values()):
cycled = False
for level in level_priority_cycle:
if by_level[level]:
selected_for_block.append(by_level[level].pop())
cycled = True
break
if not cycled: # Если в приоритетном цикле вопросы кончились, берём любые оставшиеся
for level in sorted(by_level.keys()):
if by_level[level]:
selected_for_block.append(by_level[level].pop())
break
final_quiz.extend(selected_for_block)
random.shuffle(final_quiz)
return final_quiz
def print_quiz(quiz_questions):
"""
Красиво печатает вопросы и ответы.
"""
print("=" * 80)
print("СЛУЧАЙНЫЙ ОПРОСНИК ПО LINUX".center(80))
print("=" * 80)
if not quiz_questions:
print("\nНе удалось сгенерировать вопросы. Проверьте исходный текст.")
return
# Печатаем вопросы
print("\n--- ВОПРОСЫ ---\n")
for i, q in enumerate(quiz_questions, 1):
level_icons = 'I' * q.level
print(f"{i}. [Блок: {q.block} | Уровень: {level_icons}]")
print(textwrap.fill(f" Вопрос #{q.number}: {q.text}", width=80, subsequent_indent=' '))
print(f" A) {q.options['A']}")
print(f" B) {q.options['B']}")
print(f" C) {q.options['C']}")
print(f" D) {q.options['D']}\n")
# Печатаем ответы
print("\n" + "=" * 35 + " ОТВЕТЫ " + "=" * 36 + "\n")
for i, q in enumerate(quiz_questions, 1):
answer_text = q.options[q.answer_letter]
print(f"{i} (Вопрос #{q.number}): {q.answer_letter}) {answer_text}")
def main():
parser = argparse.ArgumentParser(
description="Генератор опросника по Linux. Принимает Markdown текст через stdin.",
formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument(
"count",
type=int,
help="Количество случайных вопросов для выбора из КАЖДОГО основного блока."
)
args = parser.parse_args()
# Сообщение-подсказка выводится в stderr, чтобы не мешать выводу в stdout
print(
"Ожидание Markdown текста из стандартного ввода (stdin)...",
file=sys.stderr
)
markdown_text = sys.stdin.read()
all_questions = parse_markdown(markdown_text)
if not all_questions:
print("Ошибка: не найдено ни одного вопроса. Убедитесь, что вы передали текст в stdin.", file=sys.stderr)
return
quiz = select_quiz_questions(all_questions, args.count)
print_quiz(quiz)
if __name__ == "__main__":
main()
Loading…
Cancel
Save