|
|
#!/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()
|