# glance - Provides a quick and intelligent overview of a directory's contents. # # Authors: anduin # # Features: # - Auto-detects Git repositories to intelligently list files. # - Dynamically calculates and adjusts the `tree` view depth to prevent clutter. # - File content display strictly adheres to the calculated tree depth. # - Limits the maximum lines printed per file to avoid long scrolls. # - Supports powerful filtering by file glob patterns. # # Usage: # glance [DIRECTORY] [OPTIONS...] # # Options: # --max-files-preview : Max items (files+dirs) in tree view. (Default: 100) # --max-file-lines : Max lines to print per file. (Default: 150) # --filter : Comma-separated glob patterns to filter files (e.g., '*.md,*.cs') # glance() { local target_dir="." local max_items_preview=100 local max_file_lines=150 local filter_patterns="" local positional_args=() # --- Internal helper function to process and print a single file --- _glance_process_file() { local file="$1" if [ ! -f "$file" ]; then return; fi # Check if it's a text file if file -b --mime-type "$file" | grep -q '^text/'; then local total_lines total_lines=$(wc -l < "$file") # More efficient line count local extension="${file##*.}"; local lang="" case "$extension" in js) lang="javascript" ;; py) lang="python" ;; rb) lang="ruby" ;; sh) lang="bash" ;; css) lang="css" ;; html) lang="html" ;; json) lang="json" ;; md) lang="markdown" ;; yml|yaml) lang="yaml" ;; java) lang="java" ;; c) lang="c" ;; cpp) lang="cpp" ;; go) lang="go" ;; *) lang="" ;; esac echo echo "Content of file \"$file\":" echo "\`\`\`$lang" if [ "$total_lines" -gt "$max_file_lines" ]; then head -n "$max_file_lines" "$file" echo echo "... (Only shown top $max_file_lines lines of $total_lines) ..." else cat "$file" fi echo echo "\`\`\`" echo "----------------------------------------------------------" else echo echo "--> Skipped binary file: \"$file\"" echo "----------------------------------------------------------" fi } # --- Step 1: Parse arguments --- while [[ $# -gt 0 ]]; do case "$1" in --max-files-preview) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then max_items_preview="$2"; shift 2; else echo "Error: '--max-files-preview' requires a positive integer argument." >&2; return 1; fi ;; --max-file-lines) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then max_file_lines="$2"; shift 2; else echo "Error: '--max-file-lines' requires a positive integer argument." >&2; return 1; fi ;; --filter) if [[ -n "$2" && "$2" != -* ]]; then filter_patterns="$2"; shift 2; else echo "Error: '--filter' requires a pattern string argument." >&2; return 1; fi ;; -*) echo "Error: Unknown option '$1'" >&2; return 1 ;; *) positional_args+=("$1"); shift ;; esac done [[ ${#positional_args[@]} -gt 0 ]] && target_dir="${positional_args[0]}" # --- Step 2: Check dependencies and directory validity --- if ! command -v tree &>/dev/null; then echo "Error: 'tree' command not found. Please install it first." >&2; return 1; fi if [ ! -d "$target_dir" ]; then echo "Error: Directory '$target_dir' does not exist." >&2; return 1; fi echo "==========================================================" echo "🔎 Overview for directory: $(realpath "$target_dir")" echo "==========================================================" echo # --- Step 3: Dynamically calculate the optimal tree depth (based on the full, unfiltered directory) --- echo "🌳 Directory Structure:" local best_depth=1; local max_depth_to_check=6; local prev_item_count=-1 for depth in $(seq 1 $max_depth_to_check); do local current_item_count; current_item_count=$(tree -L "$depth" -a --noreport "$target_dir" | wc -l) if [ "$depth" -gt 1 ] && [ "$current_item_count" -gt "$max_items_preview" ]; then best_depth=$((depth - 1)); break; fi # If item count stops increasing, we've reached the maximum effective depth if [ "$depth" -gt 1 ] && [ "$current_item_count" -eq "$prev_item_count" ]; then best_depth=$depth; break; fi best_depth=$depth; prev_item_count=$current_item_count done # --- Step 3.5: Apply filter and print the tree --- local tree_args=(-L "$best_depth") if [ -n "$filter_patterns" ]; then local tree_pattern; tree_pattern=$(echo "$filter_patterns" | tr ',' '|') tree_args+=(-P "$tree_pattern") echo "(Filter active: ${filter_patterns})" fi echo "(Dynamic depth set to L${best_depth} to fit item limit: ${max_items_preview})" tree "${tree_args[@]}" "$target_dir" echo # --- Step 4: Iterate and print file contents (applying filters and depth limits) --- local header_suffix="" if [ -n "$filter_patterns" ]; then header_suffix=" (filter: ${filter_patterns})"; fi echo "📄 File Content Details (within depth L${best_depth}, max ${max_file_lines} lines per file${header_suffix}):" ( cd "$target_dir" || exit 1 echo "----------------------------------------------------------" local filter_array=() [ -n "$filter_patterns" ] && IFS=',' read -ra filter_array <<< "$filter_patterns" if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then # Git mode local git_files if [ ${#filter_array[@]} -gt 0 ]; then git_files=$(git ls-files -- "${filter_array[@]}") else git_files=$(git ls-files) fi # Use awk to filter paths by slash count to simulate depth limit echo "$git_files" | awk -F'/' -v depth="$best_depth" 'NF <= depth' | while read -r file; do _glance_process_file "$file" done else # Normal mode local find_args=(-maxdepth "$best_depth" -type f) if [ ${#filter_array[@]} -gt 0 ]; then find_args+=("(") for i in "${!filter_array[@]}"; do [ "$i" -ne 0 ] && find_args+=("-o") find_args+=(-name "${filter_array[$i]}") done find_args+=(")") fi # Use find's -maxdepth parameter and name patterns find . "${find_args[@]}" | while read -r file; do # Remove leading './' from file path for cleaner output _glance_process_file "${file#./}" done fi ) }