use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static CONCEPT_COUNTER: AtomicU64 = AtomicU64::new(1);
static QUIZ_COUNTER: AtomicU64 = AtomicU64::new(1);
static LESSON_COUNTER: AtomicU64 = AtomicU64::new(1);
fn current_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ExplanationLevel {
Beginner,
Intermediate,
Advanced,
Expert,
}
impl ExplanationLevel {
pub fn description(&self) -> &'static str {
match self {
ExplanationLevel::Beginner => "Detailed explanations with basic concepts",
ExplanationLevel::Intermediate => "Moderate explanations assuming some knowledge",
ExplanationLevel::Advanced => "Concise explanations for experienced developers",
ExplanationLevel::Expert => "Minimal explanations, focus on edge cases",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeExplanation {
pub code: String,
pub explanation: String,
pub line_explanations: Vec<LineExplanation>,
pub concepts: Vec<String>,
pub related_topics: Vec<String>,
pub level: ExplanationLevel,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LineExplanation {
pub line_number: u32,
pub code: String,
pub explanation: String,
pub concepts: Vec<String>,
}
impl LineExplanation {
pub fn new(line_number: u32, code: impl Into<String>, explanation: impl Into<String>) -> Self {
Self {
line_number,
code: code.into(),
explanation: explanation.into(),
concepts: Vec::new(),
}
}
pub fn with_concept(mut self, concept: impl Into<String>) -> Self {
self.concepts.push(concept.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExplainModeConfig {
pub level: ExplanationLevel,
pub line_by_line: bool,
pub include_concepts: bool,
pub max_length: usize,
pub language: String,
}
impl Default for ExplainModeConfig {
fn default() -> Self {
Self {
level: ExplanationLevel::Beginner,
line_by_line: true,
include_concepts: true,
max_length: 2000,
language: "en".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct CodeExplainer {
pub config: ExplainModeConfig,
known_concepts: Vec<String>,
history: Vec<CodeExplanation>,
}
impl CodeExplainer {
pub fn new() -> Self {
Self {
config: ExplainModeConfig::default(),
known_concepts: Vec::new(),
history: Vec::new(),
}
}
pub fn with_config(mut self, config: ExplainModeConfig) -> Self {
self.config = config;
self
}
pub fn add_known_concept(&mut self, concept: impl Into<String>) {
self.known_concepts.push(concept.into());
}
pub fn explain(&mut self, code: &str) -> CodeExplanation {
let lines: Vec<&str> = code.lines().collect();
let mut line_explanations = Vec::new();
let mut all_concepts = Vec::new();
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("//") {
continue;
}
let (explanation, concepts) = self.explain_line(trimmed);
let mut line_exp = LineExplanation::new((i + 1) as u32, *line, explanation);
for concept in &concepts {
line_exp = line_exp.with_concept(concept);
if !all_concepts.contains(concept) {
all_concepts.push(concept.clone());
}
}
line_explanations.push(line_exp);
}
let overall_explanation = self.generate_overall_explanation(code, &all_concepts);
let explanation = CodeExplanation {
code: code.to_string(),
explanation: overall_explanation,
line_explanations,
concepts: all_concepts,
related_topics: self.suggest_related_topics(code),
level: self.config.level,
};
self.history.push(explanation.clone());
explanation
}
fn explain_line(&self, line: &str) -> (String, Vec<String>) {
let mut concepts = Vec::new();
let explanation;
if line.starts_with("fn ") || line.starts_with("pub fn ") {
concepts.push("function".to_string());
explanation = self.explain_function(line);
} else if line.starts_with("let ") || line.starts_with("let mut ") {
concepts.push("variable_binding".to_string());
if line.contains("mut ") {
concepts.push("mutability".to_string());
}
explanation = self.explain_variable(line);
} else if line.starts_with("struct ") || line.starts_with("pub struct ") {
concepts.push("struct".to_string());
explanation = self.explain_struct(line);
} else if line.starts_with("impl ") {
concepts.push("implementation".to_string());
explanation = self.explain_impl(line);
} else if line.starts_with("if ") {
concepts.push("conditional".to_string());
explanation =
"Conditional statement that executes code based on a condition".to_string();
} else if line.starts_with("for ")
|| line.starts_with("while ")
|| line.starts_with("loop ")
{
concepts.push("loop".to_string());
explanation = self.explain_loop(line);
} else if line.contains("->") {
concepts.push("return_type".to_string());
explanation = "Specifies the return type of a function".to_string();
} else if line.contains("::") {
concepts.push("path".to_string());
explanation = "Namespace path to access items".to_string();
} else if line.contains(".unwrap()") || line.contains(".expect(") {
concepts.push("error_handling".to_string());
explanation = "Unwraps a Result or Option, panicking if it's an error".to_string();
} else if line.contains("async ") || line.contains(".await") {
concepts.push("async".to_string());
explanation = "Asynchronous code that can be paused and resumed".to_string();
} else {
explanation = "Executes an operation or expression".to_string();
}
(explanation, concepts)
}
fn explain_function(&self, line: &str) -> String {
let visibility = if line.starts_with("pub ") {
"public"
} else {
"private"
};
format!(
"Defines a {} function. Functions encapsulate reusable blocks of code.",
visibility
)
}
fn explain_variable(&self, line: &str) -> String {
if line.contains("mut ") {
"Declares a mutable variable. Its value can be changed after initialization."
.to_string()
} else {
"Declares an immutable variable. Its value cannot be changed after assignment."
.to_string()
}
}
fn explain_struct(&self, line: &str) -> String {
let visibility = if line.starts_with("pub ") {
"public"
} else {
"private"
};
format!(
"Defines a {} struct. Structs group related data together.",
visibility
)
}
fn explain_impl(&self, line: &str) -> String {
if line.contains(" for ") {
"Implements a trait for a type, adding functionality.".to_string()
} else {
"Implements methods for a type.".to_string()
}
}
fn explain_loop(&self, line: &str) -> String {
if line.starts_with("for ") {
"Iterates over a collection or range of values.".to_string()
} else if line.starts_with("while ") {
"Loops while a condition is true.".to_string()
} else {
"Infinite loop that runs until explicitly broken.".to_string()
}
}
fn generate_overall_explanation(&self, _code: &str, concepts: &[String]) -> String {
let mut explanation = String::new();
match self.config.level {
ExplanationLevel::Beginner => {
explanation.push_str("This code demonstrates several programming concepts:\n\n");
for concept in concepts {
explanation.push_str(&format!(
"- **{}**: {}\n",
concept,
self.describe_concept(concept)
));
}
}
ExplanationLevel::Intermediate => {
explanation.push_str("Key concepts used: ");
explanation.push_str(&concepts.join(", "));
}
ExplanationLevel::Advanced | ExplanationLevel::Expert => {
explanation.push_str("Uses: ");
explanation.push_str(&concepts.join(", "));
}
}
explanation
}
fn describe_concept(&self, concept: &str) -> &'static str {
match concept {
"function" => "A reusable block of code that performs a specific task",
"variable_binding" => "Assigning a value to a name for later use",
"mutability" => "The ability to modify a value after it's created",
"struct" => "A custom data type that groups related values",
"implementation" => "Adding methods and behavior to a type",
"conditional" => "Making decisions based on conditions",
"loop" => "Repeating code multiple times",
"return_type" => "The type of value a function returns",
"path" => "The location of an item in the code hierarchy",
"error_handling" => "Managing and recovering from errors",
"async" => "Non-blocking code that can wait for operations",
_ => "A programming concept",
}
}
fn suggest_related_topics(&self, _code: &str) -> Vec<String> {
vec![
"Ownership and borrowing".to_string(),
"Error handling with Result".to_string(),
"Pattern matching".to_string(),
]
}
pub fn history(&self) -> &[CodeExplanation] {
&self.history
}
}
impl Default for CodeExplainer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Difficulty {
Beginner,
Elementary,
Intermediate,
Advanced,
Expert,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Concept {
pub id: String,
pub name: String,
pub description: String,
pub difficulty: Difficulty,
pub category: String,
pub prerequisites: Vec<String>,
pub examples: Vec<String>,
pub occurrences: Vec<PathBuf>,
pub occurrence_count: u32,
}
impl Concept {
pub fn new(name: impl Into<String>, category: impl Into<String>) -> Self {
let id = format!("concept_{}", CONCEPT_COUNTER.fetch_add(1, Ordering::SeqCst));
Self {
id,
name: name.into(),
description: String::new(),
difficulty: Difficulty::Beginner,
category: category.into(),
prerequisites: Vec::new(),
examples: Vec::new(),
occurrences: Vec::new(),
occurrence_count: 0,
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn with_difficulty(mut self, difficulty: Difficulty) -> Self {
self.difficulty = difficulty;
self
}
pub fn add_prerequisite(&mut self, prerequisite: impl Into<String>) {
self.prerequisites.push(prerequisite.into());
}
pub fn add_example(&mut self, example: impl Into<String>) {
self.examples.push(example.into());
}
pub fn record_occurrence(&mut self, file: impl Into<PathBuf>) {
self.occurrences.push(file.into());
self.occurrence_count += 1;
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lesson {
pub id: String,
pub title: String,
pub description: String,
pub concepts: Vec<String>,
pub estimated_minutes: u32,
pub order: u32,
pub objectives: Vec<String>,
pub exercises: Vec<Exercise>,
}
impl Lesson {
pub fn new(title: impl Into<String>, order: u32) -> Self {
let id = format!("lesson_{}", LESSON_COUNTER.fetch_add(1, Ordering::SeqCst));
Self {
id,
title: title.into(),
description: String::new(),
concepts: Vec::new(),
estimated_minutes: 30,
order,
objectives: Vec::new(),
exercises: Vec::new(),
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn add_concept(&mut self, concept_id: impl Into<String>) {
self.concepts.push(concept_id.into());
}
pub fn add_objective(&mut self, objective: impl Into<String>) {
self.objectives.push(objective.into());
}
pub fn add_exercise(&mut self, exercise: Exercise) {
self.exercises.push(exercise);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Exercise {
pub title: String,
pub instructions: String,
pub starter_code: Option<String>,
pub solution_pattern: Option<String>,
pub hints: Vec<String>,
}
impl Exercise {
pub fn new(title: impl Into<String>, instructions: impl Into<String>) -> Self {
Self {
title: title.into(),
instructions: instructions.into(),
starter_code: None,
solution_pattern: None,
hints: Vec::new(),
}
}
pub fn with_starter_code(mut self, code: impl Into<String>) -> Self {
self.starter_code = Some(code.into());
self
}
pub fn add_hint(mut self, hint: impl Into<String>) -> Self {
self.hints.push(hint.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Curriculum {
pub title: String,
pub description: String,
pub concepts: HashMap<String, Concept>,
pub lessons: Vec<Lesson>,
pub total_minutes: u32,
pub target_audience: ExplanationLevel,
}
impl Curriculum {
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
description: String::new(),
concepts: HashMap::new(),
lessons: Vec::new(),
total_minutes: 0,
target_audience: ExplanationLevel::Beginner,
}
}
pub fn add_concept(&mut self, concept: Concept) {
self.concepts.insert(concept.id.clone(), concept);
}
pub fn add_lesson(&mut self, lesson: Lesson) {
self.total_minutes += lesson.estimated_minutes;
self.lessons.push(lesson);
}
pub fn get_concept(&self, id: &str) -> Option<&Concept> {
self.concepts.get(id)
}
pub fn lessons_by_difficulty(&self, max_difficulty: Difficulty) -> Vec<&Lesson> {
self.lessons
.iter()
.filter(|l| {
l.concepts.iter().all(|c| {
self.concepts
.get(c)
.map(|concept| concept.difficulty <= max_difficulty)
.unwrap_or(true)
})
})
.collect()
}
pub fn suggested_order(&self) -> Vec<&Lesson> {
let mut sorted: Vec<_> = self.lessons.iter().collect();
sorted.sort_by_key(|l| l.order);
sorted
}
}
#[derive(Debug, Clone)]
pub struct ConceptExtractor {
patterns: HashMap<String, ConceptPattern>,
}
#[derive(Debug, Clone)]
pub struct ConceptPattern {
pub name: String,
pub keywords: Vec<String>,
pub category: String,
pub difficulty: Difficulty,
}
impl ConceptExtractor {
pub fn new() -> Self {
let mut extractor = Self {
patterns: HashMap::new(),
};
extractor.add_default_patterns();
extractor
}
fn add_default_patterns(&mut self) {
self.patterns.insert(
"ownership".to_string(),
ConceptPattern {
name: "Ownership".to_string(),
keywords: vec!["move".to_string(), "borrow".to_string(), "&".to_string()],
category: "memory".to_string(),
difficulty: Difficulty::Intermediate,
},
);
self.patterns.insert(
"pattern_matching".to_string(),
ConceptPattern {
name: "Pattern Matching".to_string(),
keywords: vec![
"match".to_string(),
"if let".to_string(),
"while let".to_string(),
],
category: "control_flow".to_string(),
difficulty: Difficulty::Intermediate,
},
);
self.patterns.insert(
"error_handling".to_string(),
ConceptPattern {
name: "Error Handling".to_string(),
keywords: vec![
"Result".to_string(),
"Option".to_string(),
"?".to_string(),
"unwrap".to_string(),
],
category: "error_handling".to_string(),
difficulty: Difficulty::Intermediate,
},
);
self.patterns.insert(
"traits".to_string(),
ConceptPattern {
name: "Traits".to_string(),
keywords: vec!["trait".to_string(), "impl".to_string(), "dyn".to_string()],
category: "abstraction".to_string(),
difficulty: Difficulty::Advanced,
},
);
self.patterns.insert(
"generics".to_string(),
ConceptPattern {
name: "Generics".to_string(),
keywords: vec!["<T>".to_string(), "where".to_string()],
category: "abstraction".to_string(),
difficulty: Difficulty::Advanced,
},
);
}
pub fn extract_from_code(&self, code: &str, file_path: &Path) -> Vec<Concept> {
let mut found_concepts = Vec::new();
for pattern in self.patterns.values() {
let mut matches = 0;
for keyword in &pattern.keywords {
if code.contains(keyword) {
matches += 1;
}
}
if matches > 0 {
let mut concept = Concept::new(&pattern.name, &pattern.category)
.with_difficulty(pattern.difficulty);
concept.record_occurrence(file_path.to_path_buf());
found_concepts.push(concept);
}
}
found_concepts
}
pub fn generate_curriculum(&self, concepts: &[Concept], title: &str) -> Curriculum {
let mut curriculum = Curriculum::new(title);
let mut by_difficulty: HashMap<Difficulty, Vec<&Concept>> = HashMap::new();
for concept in concepts {
by_difficulty
.entry(concept.difficulty)
.or_default()
.push(concept);
}
let difficulties = [
Difficulty::Beginner,
Difficulty::Elementary,
Difficulty::Intermediate,
Difficulty::Advanced,
Difficulty::Expert,
];
let mut order = 1;
for difficulty in difficulties {
if let Some(concepts_at_level) = by_difficulty.get(&difficulty) {
let lesson_title = format!("{:?} Concepts", difficulty);
let mut lesson = Lesson::new(&lesson_title, order);
lesson.description =
format!("Learn {:?} level concepts from the codebase", difficulty);
for concept in concepts_at_level {
curriculum.add_concept((*concept).clone());
lesson.add_concept(&concept.id);
}
lesson.estimated_minutes = (concepts_at_level.len() * 15) as u32;
curriculum.add_lesson(lesson);
order += 1;
}
}
curriculum
}
}
impl Default for ConceptExtractor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum QuestionType {
MultipleChoice,
TrueFalse,
FillInBlank,
CodeCompletion,
BugFix,
CodeExplanation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuizQuestion {
pub id: String,
pub question_type: QuestionType,
pub question: String,
pub code_snippet: Option<String>,
pub options: Vec<String>,
pub correct_answers: Vec<usize>,
pub explanation: String,
pub concepts: Vec<String>,
pub difficulty: Difficulty,
pub points: u32,
}
impl QuizQuestion {
pub fn multiple_choice(question: impl Into<String>) -> Self {
let id = format!("q_{}", QUIZ_COUNTER.fetch_add(1, Ordering::SeqCst));
Self {
id,
question_type: QuestionType::MultipleChoice,
question: question.into(),
code_snippet: None,
options: Vec::new(),
correct_answers: Vec::new(),
explanation: String::new(),
concepts: Vec::new(),
difficulty: Difficulty::Beginner,
points: 1,
}
}
pub fn true_false(question: impl Into<String>, answer: bool) -> Self {
let id = format!("q_{}", QUIZ_COUNTER.fetch_add(1, Ordering::SeqCst));
Self {
id,
question_type: QuestionType::TrueFalse,
question: question.into(),
code_snippet: None,
options: vec!["True".to_string(), "False".to_string()],
correct_answers: vec![if answer { 0 } else { 1 }],
explanation: String::new(),
concepts: Vec::new(),
difficulty: Difficulty::Beginner,
points: 1,
}
}
pub fn code_completion(question: impl Into<String>, code: impl Into<String>) -> Self {
let id = format!("q_{}", QUIZ_COUNTER.fetch_add(1, Ordering::SeqCst));
Self {
id,
question_type: QuestionType::CodeCompletion,
question: question.into(),
code_snippet: Some(code.into()),
options: Vec::new(),
correct_answers: Vec::new(),
explanation: String::new(),
concepts: Vec::new(),
difficulty: Difficulty::Intermediate,
points: 2,
}
}
pub fn with_options(mut self, options: Vec<String>, correct: Vec<usize>) -> Self {
self.options = options;
self.correct_answers = correct;
self
}
pub fn with_explanation(mut self, explanation: impl Into<String>) -> Self {
self.explanation = explanation.into();
self
}
pub fn with_concept(mut self, concept: impl Into<String>) -> Self {
self.concepts.push(concept.into());
self
}
pub fn with_difficulty(mut self, difficulty: Difficulty) -> Self {
self.difficulty = difficulty;
self
}
pub fn is_correct(&self, answer_indices: &[usize]) -> bool {
if answer_indices.len() != self.correct_answers.len() {
return false;
}
let mut sorted_given = answer_indices.to_vec();
let mut sorted_correct = self.correct_answers.clone();
sorted_given.sort();
sorted_correct.sort();
sorted_given == sorted_correct
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Quiz {
pub id: String,
pub title: String,
pub description: String,
pub questions: Vec<QuizQuestion>,
pub time_limit_mins: Option<u32>,
pub passing_score: u32,
pub created_at: u64,
}
impl Quiz {
pub fn new(title: impl Into<String>) -> Self {
let id = format!("quiz_{}", QUIZ_COUNTER.fetch_add(1, Ordering::SeqCst));
Self {
id,
title: title.into(),
description: String::new(),
questions: Vec::new(),
time_limit_mins: None,
passing_score: 70,
created_at: current_timestamp(),
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn with_time_limit(mut self, minutes: u32) -> Self {
self.time_limit_mins = Some(minutes);
self
}
pub fn add_question(&mut self, question: QuizQuestion) {
self.questions.push(question);
}
pub fn total_points(&self) -> u32 {
self.questions.iter().map(|q| q.points).sum()
}
pub fn question_count(&self) -> usize {
self.questions.len()
}
pub fn questions_by_difficulty(&self, difficulty: Difficulty) -> Vec<&QuizQuestion> {
self.questions
.iter()
.filter(|q| q.difficulty == difficulty)
.collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuizResult {
pub quiz_id: String,
pub answers: HashMap<String, Vec<usize>>,
pub score: u32,
pub total_points: u32,
pub percentage: f64,
pub passed: bool,
pub time_taken_secs: u64,
pub completed_at: u64,
pub incorrect_questions: Vec<String>,
}
impl QuizResult {
pub fn from_attempt(
quiz: &Quiz,
answers: HashMap<String, Vec<usize>>,
time_taken_secs: u64,
) -> Self {
let mut score = 0;
let mut incorrect = Vec::new();
for question in &quiz.questions {
if let Some(given) = answers.get(&question.id) {
if question.is_correct(given) {
score += question.points;
} else {
incorrect.push(question.id.clone());
}
} else {
incorrect.push(question.id.clone());
}
}
let total_points = quiz.total_points();
let percentage = if total_points > 0 {
(score as f64 / total_points as f64) * 100.0
} else {
0.0
};
QuizResult {
quiz_id: quiz.id.clone(),
answers,
score,
total_points,
percentage,
passed: percentage >= quiz.passing_score as f64,
time_taken_secs,
completed_at: current_timestamp(),
incorrect_questions: incorrect,
}
}
}
#[derive(Debug, Clone)]
pub struct QuizGenerator {
templates: Vec<QuestionTemplate>,
}
#[derive(Debug, Clone)]
pub struct QuestionTemplate {
pub name: String,
pub pattern: String,
pub question_type: QuestionType,
pub concept: String,
}
impl QuizGenerator {
pub fn new() -> Self {
let mut generator = Self {
templates: Vec::new(),
};
generator.add_default_templates();
generator
}
fn add_default_templates(&mut self) {
self.templates.push(QuestionTemplate {
name: "ownership_move".to_string(),
pattern: "What happens to the variable after this line?".to_string(),
question_type: QuestionType::MultipleChoice,
concept: "ownership".to_string(),
});
self.templates.push(QuestionTemplate {
name: "mutability".to_string(),
pattern: "Can this variable be modified?".to_string(),
question_type: QuestionType::TrueFalse,
concept: "mutability".to_string(),
});
self.templates.push(QuestionTemplate {
name: "error_handling".to_string(),
pattern: "What will happen if this returns an error?".to_string(),
question_type: QuestionType::MultipleChoice,
concept: "error_handling".to_string(),
});
}
pub fn generate_from_code(&self, code: &str, num_questions: usize) -> Quiz {
let mut quiz = Quiz::new("Code Understanding Quiz")
.with_description("Test your understanding of this code");
let lines: Vec<&str> = code.lines().collect();
for (i, line) in lines.iter().take(num_questions).enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("//") {
continue;
}
if let Some(question) = self.generate_question_for_line(trimmed, i + 1) {
quiz.add_question(question);
}
}
quiz
}
fn generate_question_for_line(&self, line: &str, line_num: usize) -> Option<QuizQuestion> {
if line.contains("let mut ") {
Some(
QuizQuestion::true_false(
format!(
"Line {}: The variable declared here can be modified later.",
line_num
),
true,
)
.with_concept("mutability")
.with_explanation(
"Variables declared with 'let mut' are mutable and can be changed.",
),
)
} else if line.contains("let ") {
Some(
QuizQuestion::true_false(
format!("Line {}: This variable is immutable.", line_num),
true,
)
.with_concept("immutability")
.with_explanation("Variables declared with 'let' without 'mut' are immutable."),
)
} else if line.contains(".unwrap()") {
Some(
QuizQuestion::multiple_choice(format!(
"Line {}: What happens if unwrap() is called on None or Err?",
line_num
))
.with_options(
vec![
"The program panics".to_string(),
"Returns a default value".to_string(),
"Returns None".to_string(),
"Silently fails".to_string(),
],
vec![0],
)
.with_concept("error_handling")
.with_explanation("unwrap() causes a panic if called on None or Err."),
)
} else if line.contains("fn ") {
Some(
QuizQuestion::true_false(
format!("Line {}: This declares a function.", line_num),
true,
)
.with_concept("functions"),
)
} else {
None
}
}
pub fn generate_concept_quiz(&self, concept: &Concept, num_questions: usize) -> Quiz {
let mut quiz = Quiz::new(format!("{} Quiz", concept.name))
.with_description(format!("Test your understanding of {}", concept.name));
let q1 = QuizQuestion::true_false(
format!(
"{} is a {:?} level concept.",
concept.name, concept.difficulty
),
true,
)
.with_concept(&concept.name);
quiz.add_question(q1);
for example in concept.examples.iter().take(num_questions - 1) {
let q = QuizQuestion::code_completion(
format!("Identify the {} usage in this code:", concept.name),
example.clone(),
)
.with_concept(&concept.name)
.with_difficulty(concept.difficulty);
quiz.add_question(q);
}
quiz
}
}
impl Default for QuizGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_explanation_level() {
assert!(!ExplanationLevel::Beginner.description().is_empty());
assert!(ExplanationLevel::Beginner < ExplanationLevel::Expert);
}
#[test]
fn test_line_explanation() {
let line = LineExplanation::new(1, "let x = 5;", "Declares a variable")
.with_concept("variable_binding");
assert_eq!(line.line_number, 1);
assert_eq!(line.concepts.len(), 1);
}
#[test]
fn test_code_explainer() {
let mut explainer = CodeExplainer::new();
let code = r#"
fn main() {
let x = 5;
let mut y = 10;
}
"#;
let explanation = explainer.explain(code);
assert!(!explanation.explanation.is_empty());
assert!(!explanation.line_explanations.is_empty());
}
#[test]
fn test_explainer_config() {
let config = ExplainModeConfig {
level: ExplanationLevel::Advanced,
line_by_line: false,
..Default::default()
};
let explainer = CodeExplainer::new().with_config(config);
assert_eq!(explainer.config.level, ExplanationLevel::Advanced);
}
#[test]
fn test_concept_creation() {
let mut concept = Concept::new("Ownership", "memory")
.with_description("Rust's ownership system")
.with_difficulty(Difficulty::Intermediate);
concept.add_prerequisite("variables");
concept.add_example("let s = String::from(\"hello\");");
concept.record_occurrence("src/main.rs");
assert_eq!(concept.category, "memory");
assert_eq!(concept.difficulty, Difficulty::Intermediate);
assert_eq!(concept.occurrence_count, 1);
}
#[test]
fn test_lesson() {
let mut lesson = Lesson::new("Introduction", 1).with_description("Getting started");
lesson.add_concept("concept_1");
lesson.add_objective("Understand basics");
lesson.add_exercise(Exercise::new("Hello World", "Write hello world"));
assert_eq!(lesson.concepts.len(), 1);
assert_eq!(lesson.exercises.len(), 1);
}
#[test]
fn test_curriculum() {
let mut curriculum = Curriculum::new("Rust Basics");
curriculum.add_concept(Concept::new("Variables", "basics"));
let lesson = Lesson::new("Variables", 1);
curriculum.add_lesson(lesson);
assert_eq!(curriculum.lessons.len(), 1);
assert!(curriculum.total_minutes > 0);
}
#[test]
fn test_concept_extractor() {
let extractor = ConceptExtractor::new();
let code = r#"
fn main() {
let x: Result<i32, &str> = Ok(5);
match x {
Ok(v) => println!("{}", v),
Err(e) => println!("{}", e),
}
}
"#;
let concepts = extractor.extract_from_code(code, &PathBuf::from("test.rs"));
assert!(!concepts.is_empty());
}
#[test]
fn test_curriculum_generation() {
let extractor = ConceptExtractor::new();
let concepts = vec![
Concept::new("Variables", "basics").with_difficulty(Difficulty::Beginner),
Concept::new("Ownership", "memory").with_difficulty(Difficulty::Intermediate),
];
let curriculum = extractor.generate_curriculum(&concepts, "Test Curriculum");
assert!(!curriculum.lessons.is_empty());
}
#[test]
fn test_multiple_choice_question() {
let question = QuizQuestion::multiple_choice("What is 2 + 2?")
.with_options(
vec!["3".to_string(), "4".to_string(), "5".to_string()],
vec![1],
)
.with_explanation("Basic arithmetic");
assert!(question.is_correct(&[1]));
assert!(!question.is_correct(&[0]));
}
#[test]
fn test_true_false_question() {
let question = QuizQuestion::true_false("Rust has garbage collection", false);
assert!(question.is_correct(&[1])); assert!(!question.is_correct(&[0]));
}
#[test]
fn test_quiz() {
let mut quiz = Quiz::new("Test Quiz")
.with_description("A test quiz")
.with_time_limit(30);
quiz.add_question(QuizQuestion::true_false("Test", true));
quiz.add_question(
QuizQuestion::multiple_choice("Test MC")
.with_options(vec!["A".to_string(), "B".to_string()], vec![0]),
);
assert_eq!(quiz.question_count(), 2);
assert_eq!(quiz.total_points(), 2);
}
#[test]
fn test_quiz_result() {
let mut quiz = Quiz::new("Test");
quiz.add_question(
QuizQuestion::multiple_choice("Q1")
.with_options(vec!["A".to_string(), "B".to_string()], vec![0]),
);
let q_id = quiz.questions[0].id.clone();
let mut answers = HashMap::new();
answers.insert(q_id, vec![0]);
let result = QuizResult::from_attempt(&quiz, answers, 60);
assert_eq!(result.score, 1);
assert!(result.passed);
assert!(result.incorrect_questions.is_empty());
}
#[test]
fn test_quiz_generator() {
let generator = QuizGenerator::new();
let code = r#"
let x = 5;
let mut y = 10;
y.to_string().unwrap();
"#;
let quiz = generator.generate_from_code(code, 5);
assert!(!quiz.questions.is_empty());
}
#[test]
fn test_concept_quiz() {
let generator = QuizGenerator::new();
let mut concept = Concept::new("Testing", "basics");
concept.add_example("assert!(true);");
let quiz = generator.generate_concept_quiz(&concept, 3);
assert!(!quiz.questions.is_empty());
}
#[test]
fn test_exercise() {
let exercise = Exercise::new("Hello World", "Print hello world")
.with_starter_code("fn main() { }")
.add_hint("Use println! macro");
assert!(exercise.starter_code.is_some());
assert_eq!(exercise.hints.len(), 1);
}
#[test]
fn test_explanation_level_description() {
assert_eq!(
ExplanationLevel::Beginner.description(),
"Detailed explanations with basic concepts"
);
assert_eq!(
ExplanationLevel::Intermediate.description(),
"Moderate explanations assuming some knowledge"
);
assert_eq!(
ExplanationLevel::Advanced.description(),
"Concise explanations for experienced developers"
);
assert_eq!(
ExplanationLevel::Expert.description(),
"Minimal explanations, focus on edge cases"
);
}
#[test]
fn test_explanation_level_ordering() {
assert!(ExplanationLevel::Beginner < ExplanationLevel::Intermediate);
assert!(ExplanationLevel::Intermediate < ExplanationLevel::Advanced);
assert!(ExplanationLevel::Advanced < ExplanationLevel::Expert);
}
#[test]
fn test_explanation_level_eq() {
assert_eq!(ExplanationLevel::Beginner, ExplanationLevel::Beginner);
assert_ne!(ExplanationLevel::Beginner, ExplanationLevel::Expert);
}
#[test]
fn test_explain_mode_config_default() {
let config = ExplainModeConfig::default();
assert_eq!(config.level, ExplanationLevel::Beginner);
assert!(config.line_by_line);
assert!(config.include_concepts);
assert_eq!(config.max_length, 2000);
assert_eq!(config.language, "en");
}
#[test]
fn test_line_explanation_new() {
let exp = LineExplanation::new(10, "let x = 5;", "Declares a variable");
assert_eq!(exp.line_number, 10);
assert_eq!(exp.code, "let x = 5;");
assert!(exp.concepts.is_empty());
}
#[test]
fn test_line_explanation_with_concepts() {
let exp = LineExplanation::new(1, "fn main()", "Main function")
.with_concept("function")
.with_concept("entry_point");
assert_eq!(exp.concepts.len(), 2);
assert!(exp.concepts.contains(&"function".to_string()));
}
#[test]
fn test_code_explainer_known_concepts() {
let mut explainer = CodeExplainer::new();
explainer.add_known_concept("variables");
explainer.add_known_concept("functions");
assert_eq!(explainer.known_concepts.len(), 2);
}
#[test]
fn test_difficulty_ordering() {
assert!(Difficulty::Beginner < Difficulty::Elementary);
assert!(Difficulty::Elementary < Difficulty::Intermediate);
assert!(Difficulty::Intermediate < Difficulty::Advanced);
assert!(Difficulty::Advanced < Difficulty::Expert);
}
#[test]
fn test_difficulty_all_variants() {
let variants = [
Difficulty::Beginner,
Difficulty::Elementary,
Difficulty::Intermediate,
Difficulty::Advanced,
Difficulty::Expert,
];
for v in variants {
let _ = format!("{:?}", v);
}
}
#[test]
fn test_concept_with_example() {
let mut concept = Concept::new("Ownership", "memory");
concept.add_example("let s = String::new();");
concept.add_example("drop(s);");
assert_eq!(concept.examples.len(), 2);
}
#[test]
fn test_concept_record_occurrence() {
let mut concept = Concept::new("Test", "test");
assert_eq!(concept.occurrence_count, 0);
concept.record_occurrence("src/main.rs");
assert_eq!(concept.occurrence_count, 1);
concept.record_occurrence("src/lib.rs");
assert_eq!(concept.occurrence_count, 2);
}
#[test]
fn test_concept_prerequisites() {
let mut concept =
Concept::new("Borrowing", "memory").with_difficulty(Difficulty::Intermediate);
concept.add_prerequisite("ownership");
concept.add_prerequisite("references");
assert_eq!(concept.prerequisites.len(), 2);
}
#[test]
fn test_curriculum_new() {
let curriculum = Curriculum::new("Rust Fundamentals");
assert_eq!(curriculum.title, "Rust Fundamentals");
assert!(curriculum.lessons.is_empty());
assert_eq!(curriculum.total_minutes, 0);
}
#[test]
fn test_lesson_estimated_minutes_default() {
let lesson = Lesson::new("Basics", 1).with_description("Basic concepts");
assert_eq!(lesson.estimated_minutes, 30);
}
#[test]
fn test_question_type_variants() {
let types = [
QuestionType::MultipleChoice,
QuestionType::TrueFalse,
QuestionType::FillInBlank,
QuestionType::CodeCompletion,
QuestionType::BugFix,
QuestionType::CodeExplanation,
];
for t in types {
let _ = format!("{:?}", t);
}
}
#[test]
fn test_quiz_default_passing_score() {
let quiz = Quiz::new("Test Quiz");
assert_eq!(quiz.passing_score, 70);
}
#[test]
fn test_quiz_result_partial_answers() {
let mut quiz = Quiz::new("Test");
quiz.add_question(
QuizQuestion::multiple_choice("Q1")
.with_options(vec!["A".to_string(), "B".to_string()], vec![0]),
);
quiz.add_question(
QuizQuestion::multiple_choice("Q2")
.with_options(vec!["A".to_string(), "B".to_string()], vec![1]),
);
let q_id = quiz.questions[0].id.clone();
let mut answers = HashMap::new();
answers.insert(q_id, vec![0]);
let result = QuizResult::from_attempt(&quiz, answers, 60);
assert_eq!(result.score, 1);
}
#[test]
fn test_code_explanation_clone() {
let explanation = CodeExplanation {
code: "let x = 1;".to_string(),
explanation: "Declares variable".to_string(),
line_explanations: vec![],
concepts: vec!["variable".to_string()],
related_topics: vec![],
level: ExplanationLevel::Beginner,
};
let cloned = explanation.clone();
assert_eq!(explanation.code, cloned.code);
assert_eq!(explanation.level, cloned.level);
}
#[test]
fn test_exercise_hints() {
let exercise = Exercise::new("Sum", "Calculate the sum")
.add_hint("First hint")
.add_hint("Second hint");
assert_eq!(exercise.hints.len(), 2);
}
#[test]
fn test_concept_clone() {
let concept = Concept::new("Variables", "basics")
.with_difficulty(Difficulty::Beginner)
.with_description("Variable declaration");
let cloned = concept.clone();
assert_eq!(concept.name, cloned.name);
assert_eq!(concept.category, cloned.category);
}
#[test]
fn test_lesson_clone() {
let lesson = Lesson::new("Intro", 1).with_description("Introduction");
let cloned = lesson.clone();
assert_eq!(lesson.title, cloned.title);
assert_eq!(lesson.order, cloned.order);
}
#[test]
fn test_quiz_question_default_points() {
let question = QuizQuestion::true_false("Test?", true);
assert_eq!(question.points, 1);
}
#[test]
fn test_quiz_question_default_difficulty() {
let question = QuizQuestion::true_false("Hard?", false);
assert_eq!(question.difficulty, Difficulty::Beginner);
}
#[test]
fn test_explain_struct_line() {
let mut explainer = CodeExplainer::new();
let code = "pub struct MyStruct { field: i32 }";
let explanation = explainer.explain(code);
assert!(!explanation.concepts.is_empty());
}
#[test]
fn test_explain_impl_line() {
let mut explainer = CodeExplainer::new();
let code = "impl MyTrait for MyType { }";
let explanation = explainer.explain(code);
assert!(explanation.concepts.contains(&"implementation".to_string()));
}
#[test]
fn test_explain_async_code() {
let mut explainer = CodeExplainer::new();
let code = "async fn fetch() { data.await }";
let explanation = explainer.explain(code);
assert!(explanation.concepts.contains(&"async".to_string()));
}
}