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