Skip to content

Commit 423d865

Browse files
unhappychoiceclaude
andcommitted
feat: Implement AST-based code extraction with gitignore support
- Implement CodeExtractor with tree-sitter parsing for Rust, TypeScript, Python - Add gitignore-aware file traversal using ignore crate - Implement ExtractionOptions for configurable file patterns and max lines - Add AST-based indentation normalization and comment range tracking - Extract functions, methods, classes, structs, and impls as CodeChunks - Add ChallengeConverter for transforming chunks to game challenges - Implement RepositoryLoader for loading challenges from repositories Core features: - Respects .gitignore, global gitignore, and .git/info/exclude - Normalizes indentation based on AST node column position - Tracks comment ranges through text transformations - Supports filtering by max line count and file patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 7125ed2 commit 423d865

File tree

6 files changed

+497
-8
lines changed

6 files changed

+497
-8
lines changed

src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub enum GitTypeError {
2525

2626
#[error("Terminal error: {0}")]
2727
TerminalError(String),
28+
29+
#[error("Walk directory error: {0}")]
30+
WalkDirError(#[from] walkdir::Error),
2831
}
2932

3033
pub type Result<T> = std::result::Result<T, GitTypeError>;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use crate::game::Challenge;
2+
use super::{CodeChunk, ChunkType};
3+
use uuid::Uuid;
4+
5+
pub struct ChallengeConverter;
6+
7+
impl ChallengeConverter {
8+
pub fn new() -> Self {
9+
Self
10+
}
11+
12+
pub fn convert_chunk_to_challenge(&self, chunk: CodeChunk) -> Challenge {
13+
let id = Uuid::new_v4().to_string();
14+
let language = self.language_to_string(&chunk.language);
15+
let file_path = chunk.file_path.to_string_lossy().to_string();
16+
17+
Challenge::new(id, chunk.content)
18+
.with_source_info(file_path, chunk.start_line, chunk.end_line)
19+
.with_language(language)
20+
.with_comment_ranges(chunk.comment_ranges)
21+
}
22+
23+
pub fn convert_chunks_to_challenges(&self, chunks: Vec<CodeChunk>) -> Vec<Challenge> {
24+
chunks.into_iter()
25+
.map(|chunk| self.convert_chunk_to_challenge(chunk))
26+
.collect()
27+
}
28+
29+
pub fn convert_with_filter<F>(&self, chunks: Vec<CodeChunk>, filter: F) -> Vec<Challenge>
30+
where
31+
F: Fn(&CodeChunk) -> bool,
32+
{
33+
chunks.into_iter()
34+
.filter(filter)
35+
.map(|chunk| self.convert_chunk_to_challenge(chunk))
36+
.collect()
37+
}
38+
39+
pub fn convert_functions_only(&self, chunks: Vec<CodeChunk>) -> Vec<Challenge> {
40+
self.convert_with_filter(chunks, |chunk| {
41+
matches!(chunk.chunk_type, ChunkType::Function | ChunkType::Method)
42+
})
43+
}
44+
45+
pub fn convert_classes_only(&self, chunks: Vec<CodeChunk>) -> Vec<Challenge> {
46+
self.convert_with_filter(chunks, |chunk| {
47+
matches!(chunk.chunk_type, ChunkType::Class | ChunkType::Struct)
48+
})
49+
}
50+
51+
fn language_to_string(&self, language: &super::Language) -> String {
52+
match language {
53+
super::Language::Rust => "rust".to_string(),
54+
super::Language::TypeScript => "typescript".to_string(),
55+
super::Language::Python => "python".to_string(),
56+
}
57+
}
58+
}
59+

src/extractor/chunk.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ pub struct CodeChunk {
1818
pub language: Language,
1919
pub chunk_type: ChunkType,
2020
pub name: String,
21+
pub comment_ranges: Vec<(usize, usize)>, // Character-based ranges for comments relative to content
22+
pub original_indentation: usize, // Column position of the first character in source
2123
}

src/extractor/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
pub mod language;
22
pub mod parser;
33
pub mod chunk;
4+
pub mod challenge_converter;
5+
pub mod repository_loader;
46

57
pub use language::Language;
68
pub use chunk::{CodeChunk, ChunkType};
7-
pub use parser::CodeExtractor;
9+
pub use parser::{CodeExtractor, ExtractionOptions};
10+
pub use challenge_converter::ChallengeConverter;
11+
pub use repository_loader::RepositoryLoader;

0 commit comments

Comments
 (0)