|
7 | 7 | - ChatGPT |
8 | 8 | - uithub |
9 | 9 | private: false |
10 | | -updated_at: '2025-01-14T09:31:00+09:00' |
| 10 | +updated_at: '2025-03-28T14:22:24+09:00' |
11 | 11 | id: af13b34ac636c03d97c5 |
12 | 12 | organization_url_name: haw |
13 | 13 | slide: false |
@@ -328,6 +328,198 @@ list_git_files_with_content_excluding_type() { |
328 | 328 | list_git_files_with_content_excluding_type '\.md$' |
329 | 329 | ``` |
330 | 330 |
|
| 331 | +## 自分がまとめたスクリプト |
| 332 | + |
| 333 | +参考までに上述の調査の成果をまとめたスクリプトを作ってます。 |
| 334 | + |
| 335 | +```bash |
| 336 | +#!/usr/bin/env bash |
| 337 | + |
| 338 | +# ----------------------------------------------------------------------------- |
| 339 | +# Uithub-like Git Repository Explorer |
| 340 | +# ----------------------------------------------------------------------------- |
| 341 | +# Displays a Git repository's structure and file contents in an AI-friendly format. |
| 342 | +# |
| 343 | +# Features: |
| 344 | +# - Displays Git-tracked files with line numbers |
| 345 | +# - Shows file structure (up to 2 levels deep) |
| 346 | +# - Highlights media files with GitHub raw URLs |
| 347 | +# - Ignores blank files and large files (>100KB) |
| 348 | +# - Ignores files excluded by .gitignore |
| 349 | +# - Ignores common build, cache, and temporary files |
| 350 | +# - Keeps .md and .json files (only excludes unnecessary files) |
| 351 | +# - Interactive file selection with `fzf` (optional) |
| 352 | +# |
| 353 | +# Requirements: git, tree, cat, fzf (optional) |
| 354 | +# |
| 355 | +# Usage: |
| 356 | +# ./show_repo.sh # Standard output |
| 357 | +# ./show_repo.sh --interactive # Interactive mode (requires fzf) |
| 358 | +# ----------------------------------------------------------------------------- |
| 359 | + |
| 360 | +set -euo pipefail |
| 361 | + |
| 362 | +# Color codes for output |
| 363 | +RED='\033[1;31m' |
| 364 | +GREEN='\033[1;32m' |
| 365 | +CYAN='\033[1;36m' |
| 366 | +YELLOW='\033[1;33m' |
| 367 | +RESET='\033[0m' |
| 368 | + |
| 369 | +# Configurable settings |
| 370 | +MAX_FILE_SIZE=$((100 * 1024)) # 100KB |
| 371 | +TREE_DEPTH=2 # Depth for tree display |
| 372 | + |
| 373 | +# Patterns of files to exclude |
| 374 | +EXCLUDE_PATTERNS="\ |
| 375 | +\.lock$|\ |
| 376 | +\.o$|\.out$|\.so$|\.a$|\ |
| 377 | +\.class$|\.jar$|\.war$|\.ear$|\ |
| 378 | +\.pyc$|\.pyo$|__pycache__/|\ |
| 379 | +\.beam$|_build/|\ |
| 380 | +dist/|build/|target/|\.gradle/|\ |
| 381 | +\.DS_Store$|Thumbs\.db$|\.idea/|\.vscode/|\ |
| 382 | +node_modules/|bower_components/|\ |
| 383 | +\.pytest_cache/|\.mypy_cache/|\.cargo/|\ |
| 384 | +\.log$|\.tmp$|\.swp$" |
| 385 | + |
| 386 | +# Check if inside a Git repository |
| 387 | +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then |
| 388 | + echo -e "${RED}Error: This is not a Git repository.${RESET}" >&2 |
| 389 | + exit 1 |
| 390 | +fi |
| 391 | + |
| 392 | +# Get GitHub raw content URL |
| 393 | +get_github_url() { |
| 394 | + local file="$1" |
| 395 | + local repo_url branch |
| 396 | + |
| 397 | + repo_url= "$(git config --get remote.origin.url | sed -E 's|[email protected]:|https://github.com/|; s|https://github.com/||; s|\.git$||')" |
| 398 | + branch="$(git rev-parse --abbrev-ref HEAD)" |
| 399 | + |
| 400 | + echo "https://raw.githubusercontent.com/${repo_url}/${branch}/${file}" |
| 401 | +} |
| 402 | + |
| 403 | +# Check if a file is binary |
| 404 | +is_binary() { |
| 405 | + case "$1" in |
| 406 | + *.png|*.jpg|*.jpeg|*.gif|*.bmp|*.tiff|*.ico|*.svg|*.webp|*.avif|\ |
| 407 | + *.mp4|*.mkv|*.mov|*.avi|*.wmv|*.flv|*.webm|*.mpeg|*.mpg|*.m4v|*.3gp) |
| 408 | + return 0 ;; |
| 409 | + *) return 1 ;; |
| 410 | + esac |
| 411 | +} |
| 412 | + |
| 413 | +# Check if a file is empty (zero bytes or only whitespace) |
| 414 | +is_blank_file() { |
| 415 | + local file="$1" |
| 416 | + |
| 417 | + # File size is zero? -> Blank |
| 418 | + if [[ ! -s "$file" ]]; then |
| 419 | + return 0 |
| 420 | + fi |
| 421 | + |
| 422 | + # Check if the file contains only whitespace |
| 423 | + if [[ $(grep -cvP '\S' "$file") -eq 0 ]]; then |
| 424 | + return 0 |
| 425 | + fi |
| 426 | + |
| 427 | + return 1 |
| 428 | +} |
| 429 | + |
| 430 | +# Get list of Git-tracked files, excluding ignored ones and unnecessary files |
| 431 | +get_git_tracked_files() { |
| 432 | + git ls-files --exclude-standard -c -o | grep -Ev "${EXCLUDE_PATTERNS}" || true |
| 433 | +} |
| 434 | + |
| 435 | +# Display repository structure safely |
| 436 | +show_structure() { |
| 437 | + echo -e "${CYAN}📁 Repository Structure (Depth: ${TREE_DEPTH}):${RESET}" |
| 438 | + |
| 439 | + # Try tree first, fallback to ls if tree fails |
| 440 | + if ! tree -L "${TREE_DEPTH}" 2>/dev/null; then |
| 441 | + echo -e "${YELLOW}⚠ 'tree' command failed, falling back to 'ls -R'${RESET}" |
| 442 | + ls -R | head -n 50 # Avoid massive output |
| 443 | + fi |
| 444 | + |
| 445 | + echo -e "\n---\n" |
| 446 | +} |
| 447 | + |
| 448 | +# Process and display file contents |
| 449 | +show_files() { |
| 450 | + local interactive_mode="${1:-false}" |
| 451 | + |
| 452 | + # Get filtered list of Git-tracked files |
| 453 | + mapfile -t files < <(get_git_tracked_files) |
| 454 | + |
| 455 | + if [[ "${interactive_mode}" == "true" && -x "$(command -v fzf)" ]]; then |
| 456 | + # Interactive mode: Select files with fzf |
| 457 | + selected_file=$(printf "%s\n" "${files[@]}" | fzf --preview "bat --color=always {}" --height=40%) |
| 458 | + files=("$selected_file") |
| 459 | + fi |
| 460 | + |
| 461 | + for file in "${files[@]}"; do |
| 462 | + # Skip large files |
| 463 | + if [[ "$(stat -c%s "$file")" -gt "$MAX_FILE_SIZE" ]]; then |
| 464 | + continue |
| 465 | + fi |
| 466 | + |
| 467 | + # Skip blank files |
| 468 | + if is_blank_file "$file"; then |
| 469 | + continue |
| 470 | + fi |
| 471 | + |
| 472 | + echo -e "\n${GREEN}==> ${file} <==${RESET}" |
| 473 | + |
| 474 | + if is_binary "$file"; then |
| 475 | + echo "🔗 $(get_github_url "$file")" |
| 476 | + else |
| 477 | + cat -n "$file" |
| 478 | + fi |
| 479 | + |
| 480 | + echo -e "\n---" |
| 481 | + done |
| 482 | +} |
| 483 | + |
| 484 | +# Show usage/help text |
| 485 | +show_help() { |
| 486 | + echo "Usage: $0 [options]" |
| 487 | + echo |
| 488 | + echo "Options:" |
| 489 | + echo " --interactive Enable interactive file selection with fzf" |
| 490 | + echo " --help Show this help message" |
| 491 | +} |
| 492 | + |
| 493 | +# Main function |
| 494 | +main() { |
| 495 | + local args=("$@") |
| 496 | + |
| 497 | + # Parse options |
| 498 | + for arg in "${args[@]}"; do |
| 499 | + case "$arg" in |
| 500 | + --help) |
| 501 | + show_help |
| 502 | + exit 0 |
| 503 | + ;; |
| 504 | + --interactive) |
| 505 | + INTERACTIVE_MODE=true |
| 506 | + ;; |
| 507 | + *) |
| 508 | + echo -e "${RED}Unknown option: $arg${RESET}" >&2 |
| 509 | + show_help |
| 510 | + exit 1 |
| 511 | + ;; |
| 512 | + esac |
| 513 | + done |
| 514 | + |
| 515 | + # Generate output |
| 516 | + show_structure |
| 517 | + show_files "${INTERACTIVE_MODE:-false}" |
| 518 | +} |
| 519 | + |
| 520 | +main "$@" |
| 521 | +``` |
| 522 | + |
331 | 523 | ## おわりに |
332 | 524 |
|
333 | 525 | この記事では、Uithub から着想を得た便利なシェルスクリプトを紹介しました。 |
|
0 commit comments