diff options
| author | Paul Buetow <paul@buetow.org> | 2025-07-08 23:20:48 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-07-08 23:20:48 +0300 |
| commit | 4abc942bd49c6633041d7822bc75d5b64e11899b (patch) | |
| tree | 72904d33547e786a4909d400cdb2a836d9da3d3a /internal/showcase/code_extractor.go | |
| parent | 81b6357a96e684d7588e499c8a7f3ab892c8c02a (diff) | |
feat: improve showcase code snippets and add Unicode icons
- Refactor code snippet extraction to show complete functions (5-50 lines)
- Add findSmallestCompleteFunction to prioritize smaller, complete code units
- Move code samples to the end of project descriptions (after links)
- Remove '### Code Sample' header for cleaner output
- Add AI detection for CLAUDE.md, GEMINI.md, and 'agentic coding' mentions
- Add AI-Assisted indicator when AI usage is detected
- Add Unicode icons to all statistics for better visual presentation:
* 📦 Projects, 📊 Commits, 📈 Lines of Code, 📄 Documentation
* 💻 Languages, 📚 Documentation types, 📅 Development Period
* 🔥 Recent Activity, ⚖️ License, 🤖 AI-Assisted
- Add HCL/Terraform language support (.tf, .tfvars, .hcl files)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'internal/showcase/code_extractor.go')
| -rw-r--r-- | internal/showcase/code_extractor.go | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/internal/showcase/code_extractor.go b/internal/showcase/code_extractor.go new file mode 100644 index 0000000..e8ba0d3 --- /dev/null +++ b/internal/showcase/code_extractor.go @@ -0,0 +1,395 @@ +package showcase + +import ( + "bufio" + "fmt" + "math/rand" + "os" + "path/filepath" + "strings" + "time" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +// extractCodeSnippet extracts a random code snippet from the repository +func extractCodeSnippet(repoPath string, languages []LanguageStats) (string, string, error) { + if len(languages) == 0 { + return "", "", fmt.Errorf("no programming languages found") + } + + // Get the primary language (highest percentage) + primaryLang := languages[0].Name + + // Define file extensions for each language + langExtensions := map[string][]string{ + "Go": {".go"}, + "Python": {".py"}, + "JavaScript": {".js"}, + "TypeScript": {".ts"}, + "Java": {".java"}, + "C": {".c", ".h"}, + "C++": {".cpp", ".cc", ".cxx", ".hpp"}, + "C/C++": {".h"}, + "C#": {".cs"}, + "Ruby": {".rb"}, + "PHP": {".php"}, + "Swift": {".swift"}, + "Kotlin": {".kt"}, + "Rust": {".rs"}, + "Shell": {".sh", ".bash"}, + "Perl": {".pl", ".pm"}, + "Haskell": {".hs"}, + "Lua": {".lua"}, + "HTML": {".html", ".htm"}, + "CSS": {".css"}, + "SQL": {".sql"}, + "Make": {"Makefile", "makefile", "GNUmakefile"}, + "HCL": {".tf", ".tfvars", ".hcl"}, + } + + // Get file extensions for the primary language + extensions, ok := langExtensions[primaryLang] + if !ok { + // Try other languages if primary doesn't have extensions defined + for _, lang := range languages { + if exts, exists := langExtensions[lang.Name]; exists { + extensions = exts + primaryLang = lang.Name + break + } + } + if len(extensions) == 0 { + return "", "", fmt.Errorf("no known file extensions for languages") + } + } + + // Find all files matching the extensions + var codeFiles []string + err := filepath.Walk(repoPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + + // Skip directories + if info.IsDir() { + name := info.Name() + // Skip hidden directories and common non-code directories + if strings.HasPrefix(name, ".") && name != "." || + name == "node_modules" || + name == "vendor" || + name == "target" || + name == "dist" || + name == "build" || + name == "__pycache__" { + return filepath.SkipDir + } + return nil + } + + // Skip files that are too large + if info.Size() > 1*1024*1024 { // 1MB + return nil + } + + // Check if file matches extensions + basename := filepath.Base(path) + ext := filepath.Ext(path) + + for _, validExt := range extensions { + if validExt == basename || (strings.HasPrefix(validExt, ".") && ext == validExt) { + // Skip test files and generated files + if !strings.Contains(basename, "_test") && + !strings.Contains(basename, ".test.") && + !strings.Contains(basename, ".min.") && + !strings.Contains(path, "/test/") && + !strings.Contains(path, "/tests/") { + codeFiles = append(codeFiles, path) + } + break + } + } + + return nil + }) + + if err != nil { + return "", "", err + } + + if len(codeFiles) == 0 { + return "", "", fmt.Errorf("no code files found") + } + + // Select a random file + selectedFile := codeFiles[rand.Intn(len(codeFiles))] + + // Read the file and extract a snippet (~10 lines but complete functions) + snippet, err := extractSnippetFromFile(selectedFile, 10, 15) + if err != nil { + return "", "", err + } + + // Get relative path for display + relPath, _ := filepath.Rel(repoPath, selectedFile) + + return snippet, fmt.Sprintf("%s from `%s`", primaryLang, relPath), nil +} + +// extractSnippetFromFile extracts a code snippet from a file +func extractSnippetFromFile(filePath string, minLines, maxLines int) (string, error) { + file, err := os.Open(filePath) + if err != nil { + return "", err + } + defer file.Close() + + // Read all lines + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + if err := scanner.Err(); err != nil { + return "", err + } + + totalLines := len(lines) + if totalLines == 0 { + return "", fmt.Errorf("file is empty") + } + + // Try to find the smallest complete function + bestFunction := findSmallestCompleteFunction(lines) + if bestFunction != "" { + return bestFunction, nil + } + + // If no complete function found, try to find a complete function/method + functionStart, functionEnd := findCompleteFunctionOrMethod(lines, minLines, maxLines*2) // Allow larger functions + if functionStart >= 0 && functionEnd >= 0 { + return strings.Join(lines[functionStart:functionEnd+1], "\n"), nil + } + + // Fallback to finding an interesting start with at least minLines + interestingStart := findInterestingStart(lines, minLines) + if interestingStart >= 0 { + endLine := interestingStart + minLines + if endLine > totalLines { + endLine = totalLines + } + return strings.Join(lines[interestingStart:endLine], "\n"), nil + } + + // Last resort: return first minLines (skip imports if possible) + skipLines := 0 + for i, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed != "" && !strings.HasPrefix(trimmed, "import") && + !strings.HasPrefix(trimmed, "package") && !strings.HasPrefix(trimmed, "using") && + !strings.HasPrefix(trimmed, "#include") && !strings.HasPrefix(trimmed, "from") { + skipLines = i + break + } + } + + endLine := skipLines + minLines + if endLine > totalLines { + endLine = totalLines + } + + return strings.Join(lines[skipLines:endLine], "\n"), nil +} + +// findSmallestCompleteFunction finds the smallest complete function in the file +func findSmallestCompleteFunction(lines []string) string { + type functionInfo struct { + start int + end int + size int + } + + var functions []functionInfo + + // Keywords that typically start functions/methods + functionKeywords := []string{ + "func ", "function ", "def ", "public ", "private ", "protected ", + "static ", "async ", "procedure ", "sub ", "method ", + } + + // Find all complete functions + for i := 0; i < len(lines); i++ { + line := strings.TrimSpace(lines[i]) + + // Check if this line starts a function + isFunction := false + for _, keyword := range functionKeywords { + if strings.Contains(line, keyword) && !strings.HasPrefix(line, "//") && !strings.HasPrefix(line, "#") { + isFunction = true + break + } + } + + if !isFunction { + continue + } + + // Try to find the end of this function + functionEnd := findFunctionEnd(lines, i) + if functionEnd > i { + size := functionEnd - i + 1 + // Only consider functions between 5 and 50 lines + if size >= 5 && size <= 50 { + functions = append(functions, functionInfo{ + start: i, + end: functionEnd, + size: size, + }) + } + } + } + + // Find the smallest function + if len(functions) > 0 { + smallest := functions[0] + for _, f := range functions[1:] { + if f.size < smallest.size { + smallest = f + } + } + return strings.Join(lines[smallest.start:smallest.end+1], "\n") + } + + return "" +} + +// findFunctionEnd finds the end of a function starting at the given line +func findFunctionEnd(lines []string, start int) int { + if start >= len(lines) { + return -1 + } + + // For brace-based languages + braceCount := 0 + inFunction := false + + // For Python - track initial indentation + isPython := strings.Contains(lines[start], "def ") || strings.Contains(lines[start], "class ") + var initialIndent int + if isPython && start < len(lines)-1 { + // Get indentation of first line after def + for i := start + 1; i < len(lines); i++ { + if strings.TrimSpace(lines[i]) != "" { + initialIndent = len(lines[i]) - len(strings.TrimLeft(lines[i], " \t")) + break + } + } + } + + for i := start; i < len(lines); i++ { + line := lines[i] + trimmed := strings.TrimSpace(line) + + // Handle Python indentation + if isPython && i > start { + if trimmed == "" { + continue + } + currentIndent := len(line) - len(strings.TrimLeft(line, " \t")) + if currentIndent < initialIndent { + return i - 1 + } + } + + // Handle brace-based languages + for _, ch := range line { + if ch == '{' { + braceCount++ + inFunction = true + } else if ch == '}' { + braceCount-- + if braceCount == 0 && inFunction { + return i + } + } + } + } + + // If we're in Python and reached the end, return the last line + if isPython { + return len(lines) - 1 + } + + return -1 +} + +// findCompleteFunctionOrMethod finds a complete function or method within size constraints +func findCompleteFunctionOrMethod(lines []string, minLines, maxLines int) (int, int) { + // Keywords that typically start functions/methods + functionKeywords := []string{ + "func ", "function ", "def ", "public ", "private ", "protected ", + "static ", "async ", "procedure ", "sub ", "method ", + } + + // Try to find a function that fits within our size constraints + for i := 0; i < len(lines); i++ { + line := strings.TrimSpace(lines[i]) + + // Check if this line starts a function + isFunction := false + for _, keyword := range functionKeywords { + if strings.Contains(line, keyword) && !strings.HasPrefix(line, "//") && !strings.HasPrefix(line, "#") { + isFunction = true + break + } + } + + if !isFunction { + continue + } + + // Try to find the end of this function + functionEnd := findFunctionEnd(lines, i) + if functionEnd > i { + functionLength := functionEnd - i + 1 + if functionLength >= minLines && functionLength <= maxLines { + return i, functionEnd + } + } + } + + return -1, -1 +} + +// findInterestingStart tries to find a good starting point for the snippet +func findInterestingStart(lines []string, snippetSize int) int { + // Look for function/class definitions + keywords := []string{ + "func ", "function ", "def ", "class ", "public class", + "interface ", "struct ", "type ", "const ", "var ", + "procedure ", "sub ", "method ", + } + + for i := 0; i < len(lines)-snippetSize; i++ { + line := strings.TrimSpace(lines[i]) + // Skip empty lines and comments + if line == "" || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "#") || + strings.HasPrefix(line, "/*") || strings.HasPrefix(line, "*") { + continue + } + + // Check for interesting keywords + for _, keyword := range keywords { + if strings.Contains(line, keyword) { + // Found something interesting, start here + return i + } + } + } + + // No interesting start found + return -1 +}
\ No newline at end of file |
