You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 lines
7.5 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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