Init
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user