Skip to content

Commit ee464f5

Browse files
unhappychoiceclaude
andcommitted
feat: add new unified model structure
- Create src/models directory with unified terminology - Add new models: Chunk, Challenge, Stage, Session, Total - Rename RankingTier -> Rank for consistency - Rename GitRepositoryInfo -> GitRepository - Establish XXXResult pattern for consistency - Maintain backward compatibility during transition 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b7e5e9a commit ee464f5

File tree

9 files changed

+540
-0
lines changed

9 files changed

+540
-0
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod cli;
22
pub mod error;
33
pub mod extractor;
44
pub mod game;
5+
pub mod models;
56
pub mod repo_manager;
67
pub mod scoring;
78
pub mod sharing;

src/models/challenge.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use super::git_repository::GitRepository;
2+
use crate::game::stage_builder::DifficultyLevel;
3+
4+
#[derive(Debug, Clone)]
5+
pub struct Challenge {
6+
pub id: String,
7+
pub source_file_path: Option<String>,
8+
pub code_content: String,
9+
pub start_line: Option<usize>,
10+
pub end_line: Option<usize>,
11+
pub language: Option<String>,
12+
pub comment_ranges: Vec<(usize, usize)>, // Character-based ranges for comments
13+
pub difficulty_level: Option<DifficultyLevel>,
14+
}
15+
16+
impl Challenge {
17+
pub fn new(id: String, code_content: String) -> Self {
18+
Self {
19+
id,
20+
source_file_path: None,
21+
code_content,
22+
start_line: None,
23+
end_line: None,
24+
language: None,
25+
comment_ranges: Vec::new(),
26+
difficulty_level: None,
27+
}
28+
}
29+
30+
pub fn with_source_info(
31+
mut self,
32+
file_path: String,
33+
start_line: usize,
34+
end_line: usize,
35+
) -> Self {
36+
self.source_file_path = Some(file_path);
37+
self.start_line = Some(start_line);
38+
self.end_line = Some(end_line);
39+
self
40+
}
41+
42+
pub fn with_language(mut self, language: String) -> Self {
43+
self.language = Some(language);
44+
self
45+
}
46+
47+
pub fn with_comment_ranges(mut self, comment_ranges: Vec<(usize, usize)>) -> Self {
48+
self.comment_ranges = comment_ranges;
49+
self
50+
}
51+
52+
pub fn with_difficulty_level(mut self, difficulty_level: DifficultyLevel) -> Self {
53+
self.difficulty_level = Some(difficulty_level);
54+
self
55+
}
56+
57+
pub fn get_display_title(&self) -> String {
58+
if let Some(ref path) = self.source_file_path {
59+
// Convert absolute path to relative path for cleaner display
60+
let relative_path = self.get_relative_path(path);
61+
if let (Some(start), Some(end)) = (self.start_line, self.end_line) {
62+
format!("{}:{}-{}", relative_path, start, end)
63+
} else {
64+
relative_path
65+
}
66+
} else {
67+
format!("Challenge {}", self.id)
68+
}
69+
}
70+
71+
pub fn get_display_title_with_repo(&self, repo_info: &Option<GitRepository>) -> String {
72+
if let Some(ref path) = self.source_file_path {
73+
let relative_path = self.get_relative_path(path);
74+
let file_info = if let (Some(start), Some(end)) = (self.start_line, self.end_line) {
75+
format!("{}:{}-{}", relative_path, start, end)
76+
} else {
77+
relative_path
78+
};
79+
80+
if let Some(repo) = repo_info {
81+
format!(
82+
"[{}/{}] {}",
83+
repo.user_name, repo.repository_name, file_info
84+
)
85+
} else {
86+
file_info
87+
}
88+
} else {
89+
format!("Challenge {}", self.id)
90+
}
91+
}
92+
93+
fn get_relative_path(&self, path: &str) -> String {
94+
use std::path::Path;
95+
96+
// Try to extract just the filename if it's a full path
97+
if let Some(file_name) = Path::new(path).file_name() {
98+
if let Some(parent) = Path::new(path).parent() {
99+
if let Some(parent_name) = parent.file_name() {
100+
// Show parent_dir/filename for better context
101+
format!(
102+
"{}/{}",
103+
parent_name.to_string_lossy(),
104+
file_name.to_string_lossy()
105+
)
106+
} else {
107+
file_name.to_string_lossy().to_string()
108+
}
109+
} else {
110+
file_name.to_string_lossy().to_string()
111+
}
112+
} else {
113+
// Fallback to original path if extraction fails
114+
path.to_string()
115+
}
116+
}
117+
}

src/models/chunk.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use std::path::PathBuf;
2+
3+
#[derive(Debug, Clone)]
4+
pub enum ChunkType {
5+
Function,
6+
Class,
7+
Method,
8+
Struct,
9+
Enum,
10+
Trait,
11+
TypeAlias,
12+
Interface,
13+
Module,
14+
Const,
15+
Variable,
16+
Component, // JSX/TSX React components
17+
Namespace, // For C# namespaces
18+
}
19+
20+
#[derive(Debug, Clone)]
21+
pub struct CodeChunk {
22+
pub content: String,
23+
pub file_path: PathBuf,
24+
pub start_line: usize,
25+
pub end_line: usize,
26+
pub language: String,
27+
pub chunk_type: ChunkType,
28+
pub name: String,
29+
pub comment_ranges: Vec<(usize, usize)>, // Character-based ranges for comments relative to content
30+
pub original_indentation: usize, // Column position of the first character in source
31+
}

src/models/git_repository.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4+
pub struct GitRepository {
5+
pub user_name: String,
6+
pub repository_name: String,
7+
pub remote_url: String,
8+
pub branch: Option<String>,
9+
pub commit_hash: Option<String>,
10+
pub is_dirty: bool,
11+
}

src/models/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
pub mod chunk;
2+
pub mod challenge;
3+
pub mod stage;
4+
pub mod session;
5+
pub mod total;
6+
pub mod rank;
7+
pub mod git_repository;
8+
9+
// Re-export main types for easy access
10+
pub use chunk::{ChunkType, CodeChunk};
11+
pub use challenge::Challenge;
12+
pub use stage::{Stage, StageResult};
13+
pub use session::{Session, SessionResult};
14+
pub use total::{Total, TotalResult};
15+
pub use rank::{Rank, RankingTitle};
16+
pub use git_repository::GitRepository;

src/models/rank.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/// Represents a ranking title with associated metadata
2+
#[derive(Debug, Clone, PartialEq)]
3+
pub struct RankingTitle {
4+
pub name: String,
5+
pub tier: Rank,
6+
pub min_score: u32,
7+
pub max_score: u32,
8+
}
9+
10+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11+
pub enum Rank {
12+
Beginner,
13+
Intermediate,
14+
Advanced,
15+
Expert,
16+
Legendary,
17+
}
18+
19+
impl Rank {
20+
/// Get the color palette name for ASCII art generation
21+
pub fn color_palette(&self) -> &'static str {
22+
match self {
23+
Rank::Beginner => "grad-blue",
24+
Rank::Intermediate => "dawn",
25+
Rank::Advanced => "forest",
26+
Rank::Expert => "gold",
27+
Rank::Legendary => "fire",
28+
}
29+
}
30+
31+
/// Get the terminal color for this tier
32+
pub fn terminal_color(&self) -> crossterm::style::Color {
33+
use crossterm::style::Color;
34+
match self {
35+
Rank::Beginner => Color::Cyan,
36+
Rank::Intermediate => Color::Blue,
37+
Rank::Advanced => Color::Green,
38+
Rank::Expert => Color::Yellow,
39+
Rank::Legendary => Color::Red,
40+
}
41+
}
42+
}
43+
44+
impl RankingTitle {
45+
/// Create a new ranking title
46+
pub fn new(name: impl Into<String>, tier: Rank, min_score: u32, max_score: u32) -> Self {
47+
Self {
48+
name: name.into(),
49+
tier,
50+
min_score,
51+
max_score,
52+
}
53+
}
54+
55+
/// Get the display name of the ranking title
56+
pub fn name(&self) -> &str {
57+
&self.name
58+
}
59+
60+
/// Get the tier of the ranking title
61+
#[allow(dead_code)]
62+
pub fn tier(&self) -> &Rank {
63+
&self.tier
64+
}
65+
66+
/// Check if a score falls within this ranking title's range
67+
#[allow(dead_code)]
68+
pub fn contains_score(&self, score: f64) -> bool {
69+
let score = score as u32;
70+
score >= self.min_score && score <= self.max_score
71+
}
72+
73+
/// Get the color palette name for ASCII art generation
74+
pub fn color_palette(&self) -> &'static str {
75+
self.tier.color_palette()
76+
}
77+
78+
/// Get the terminal color for this ranking title
79+
pub fn terminal_color(&self) -> crossterm::style::Color {
80+
self.tier.terminal_color()
81+
}
82+
83+
/// Get all ranking titles in order from lowest to highest score
84+
pub fn all_titles() -> Vec<RankingTitle> {
85+
vec![
86+
// Beginner Level (clean boundaries, ~even progression)
87+
RankingTitle::new("Hello World", Rank::Beginner, 0, 800),
88+
RankingTitle::new("Syntax Error", Rank::Beginner, 801, 1200),
89+
RankingTitle::new("Rubber Duck", Rank::Beginner, 1201, 1600),
90+
RankingTitle::new("Script Kid", Rank::Beginner, 1601, 2000),
91+
RankingTitle::new("Bash Newbie", Rank::Beginner, 2001, 2450),
92+
RankingTitle::new("CLI Wanderer", Rank::Beginner, 2451, 2900),
93+
RankingTitle::new("Tab Tamer", Rank::Beginner, 2901, 3300),
94+
RankingTitle::new("Bracket Juggler", Rank::Beginner, 3301, 3700),
95+
RankingTitle::new("Copy-Paste Engineer", Rank::Beginner, 3701, 4150),
96+
RankingTitle::new("Linter Apprentice", Rank::Beginner, 4151, 4550),
97+
RankingTitle::new("Unit Test Trainee", Rank::Beginner, 4551, 5000),
98+
RankingTitle::new("Code Monkey", Rank::Beginner, 5001, 5600),
99+
// Intermediate Level
100+
RankingTitle::new("Ticket Picker", Rank::Intermediate, 5601, 5850),
101+
RankingTitle::new("Junior Dev", Rank::Intermediate, 5851, 6000),
102+
RankingTitle::new("Git Ninja", Rank::Intermediate, 6001, 6100),
103+
RankingTitle::new("Merge Wrangler", Rank::Intermediate, 6101, 6250),
104+
RankingTitle::new("API Crafter", Rank::Intermediate, 6251, 6400),
105+
RankingTitle::new("Frontend Dev", Rank::Intermediate, 6401, 6550),
106+
RankingTitle::new("Backend Dev", Rank::Intermediate, 6551, 6700),
107+
RankingTitle::new("CI Tinkerer", Rank::Intermediate, 6701, 6850),
108+
RankingTitle::new("Test Pilot", Rank::Intermediate, 6851, 7000),
109+
RankingTitle::new("Build Tamer", Rank::Intermediate, 7001, 7100),
110+
RankingTitle::new("Code Reviewer", Rank::Intermediate, 7101, 7250),
111+
RankingTitle::new("Release Handler", Rank::Intermediate, 7251, 7500),
112+
// Advanced Level
113+
RankingTitle::new("Refactorer", Rank::Advanced, 7501, 7800),
114+
RankingTitle::new("Senior Dev", Rank::Advanced, 7801, 8000),
115+
RankingTitle::new("DevOps Engineer", Rank::Advanced, 8001, 8100),
116+
RankingTitle::new("Incident Responder", Rank::Advanced, 8101, 8250),
117+
RankingTitle::new("Reliability Guardian", Rank::Advanced, 8251, 8400),
118+
RankingTitle::new("Security Engineer", Rank::Advanced, 8401, 8500),
119+
RankingTitle::new("Performance Alchemist", Rank::Advanced, 8501, 8650),
120+
RankingTitle::new("Data Pipeline Master", Rank::Advanced, 8651, 8800),
121+
RankingTitle::new("Tech Lead", Rank::Advanced, 8801, 8950),
122+
RankingTitle::new("Architect", Rank::Advanced, 8951, 9100),
123+
RankingTitle::new("Protocol Artisan", Rank::Advanced, 9101, 9250),
124+
RankingTitle::new("Kernel Hacker", Rank::Advanced, 9251, 9500),
125+
// Expert Level
126+
RankingTitle::new("Compiler", Rank::Expert, 9501, 9800),
127+
RankingTitle::new("Bytecode Interpreter", Rank::Expert, 9801, 9950),
128+
RankingTitle::new("Virtual Machine", Rank::Expert, 9951, 10100),
129+
RankingTitle::new("Operating System", Rank::Expert, 10101, 10200),
130+
RankingTitle::new("Filesystem", Rank::Expert, 10201, 10350),
131+
RankingTitle::new("Network Stack", Rank::Expert, 10351, 10500),
132+
RankingTitle::new("Database Engine", Rank::Expert, 10501, 10650),
133+
RankingTitle::new("Query Optimizer", Rank::Expert, 10651, 10800),
134+
RankingTitle::new("Cloud Platform", Rank::Expert, 10801, 10950),
135+
RankingTitle::new("Container Orchestrator", Rank::Expert, 10951, 11100),
136+
RankingTitle::new("Stream Processor", Rank::Expert, 11101, 11200),
137+
RankingTitle::new("Quantum Computer", Rank::Expert, 11201, 11400),
138+
// Legendary Level
139+
RankingTitle::new("GPU Cluster", Rank::Legendary, 11401, 11700),
140+
RankingTitle::new("DNS Overlord", Rank::Legendary, 11701, 12250),
141+
RankingTitle::new("CDN Sentinel", Rank::Legendary, 12251, 12800),
142+
RankingTitle::new("Load Balancer Primarch", Rank::Legendary, 12801, 13400),
143+
RankingTitle::new("Singularity", Rank::Legendary, 13401, 13950),
144+
RankingTitle::new("The Machine", Rank::Legendary, 13951, 14500),
145+
RankingTitle::new("Origin", Rank::Legendary, 14501, 15100),
146+
RankingTitle::new("SegFault", Rank::Legendary, 15101, 15650),
147+
RankingTitle::new("Buffer Overflow", Rank::Legendary, 15651, 16200),
148+
RankingTitle::new("Memory Leak", Rank::Legendary, 16201, 16800),
149+
RankingTitle::new("Null Pointer Exception", Rank::Legendary, 16801, 17350),
150+
RankingTitle::new("Undefined Behavior", Rank::Legendary, 17351, 17900),
151+
RankingTitle::new("Heisenbug", Rank::Legendary, 17901, 18500),
152+
RankingTitle::new("Blue Screen", Rank::Legendary, 18501, 19100),
153+
RankingTitle::new("Kernel Panic", Rank::Legendary, 19101, u32::MAX),
154+
]
155+
}
156+
157+
/// Find the ranking title for a given score
158+
#[allow(dead_code)]
159+
pub fn for_score(score: f64) -> RankingTitle {
160+
Self::all_titles()
161+
.into_iter()
162+
.find(|title| title.contains_score(score))
163+
.unwrap_or_else(|| {
164+
// Fallback to highest title if score exceeds all ranges
165+
RankingTitle::new("Kernel Panic", Rank::Legendary, 40001, u32::MAX)
166+
})
167+
}
168+
}

0 commit comments

Comments
 (0)