|
#!/usr/bin/env bash |
|
# |
|
# kotlin-source-lookup - Fast source code lookup from Gradle dependencies |
|
# |
|
# Usage: |
|
# kotlin-source-lookup McpSchema # Simple class name |
|
# kotlin-source-lookup -l McpSchema # List matching JARs only |
|
# kotlin-source-lookup -a McpSchema # Show all matches |
|
# kotlin-source-lookup --refresh # Refresh dependency cache |
|
# kotlin-source-lookup --all McpSchema # Search entire gradle cache (old behavior) |
|
# |
|
|
|
set -euo pipefail |
|
|
|
GRADLE_CACHE="${GRADLE_CACHE:-$HOME/.gradle/caches/modules-2/files-2.1}" |
|
DEP_CACHE_FILE=".kotlin-source-deps" |
|
DEP_CACHE_MAX_AGE=86400 # 24 hours in seconds |
|
|
|
LIST_ONLY=false |
|
ALL_MATCHES=false |
|
SEARCH_ALL_CACHE=false |
|
REFRESH_CACHE=false |
|
|
|
# Parse flags |
|
while [[ $# -gt 0 ]]; do |
|
case $1 in |
|
-l) LIST_ONLY=true; shift ;; |
|
-a) ALL_MATCHES=true; shift ;; |
|
--all) SEARCH_ALL_CACHE=true; shift ;; |
|
--refresh) REFRESH_CACHE=true; shift ;; |
|
-h|--help) |
|
echo "Usage: $0 [-l] [-a] [--all] [--refresh] <class-name>" |
|
echo " -l List matching JARs only (don't extract)" |
|
echo " -a Show all matches (default: first match only)" |
|
echo " --all Search entire gradle cache, not just project deps" |
|
echo " --refresh Refresh the dependency cache" |
|
echo "" |
|
echo "Examples:" |
|
echo " $0 McpSchema" |
|
echo " $0 -l Flow" |
|
echo " $0 --all Flow # Find Flow in ANY cached library" |
|
exit 0 |
|
;; |
|
-*) echo "Unknown option: $1" >&2; exit 1 ;; |
|
*) break ;; |
|
esac |
|
done |
|
|
|
# Handle --refresh with no query |
|
if $REFRESH_CACHE && [[ $# -eq 0 ]]; then |
|
rm -f "$DEP_CACHE_FILE" |
|
echo "Dependency cache cleared. Will regenerate on next search." |
|
exit 0 |
|
fi |
|
|
|
if [[ $# -lt 1 ]]; then |
|
echo "Usage: $0 [-l] [-a] [--all] [--refresh] <class-name>" >&2 |
|
exit 1 |
|
fi |
|
|
|
QUERY="$1" |
|
|
|
# Convert class name to grep pattern |
|
if [[ "$QUERY" == *.* ]]; then |
|
CLASS_PATH="${QUERY//./\/}" |
|
PATTERN="${CLASS_PATH}\.(kt|java)$" |
|
else |
|
PATTERN="/${QUERY}\.(kt|java)$" |
|
fi |
|
|
|
# Generate dependency cache if needed |
|
generate_dep_cache() { |
|
echo "Generating dependency cache (this may take a moment)..." >&2 |
|
|
|
if [[ ! -f "build.gradle.kts" && ! -f "build.gradle" ]]; then |
|
echo "Warning: No build.gradle found, searching entire cache" >&2 |
|
SEARCH_ALL_CACHE=true |
|
return |
|
fi |
|
|
|
# Get dependencies from all configurations |
|
# Running without --configuration shows everything |
|
local deps |
|
deps=$(./gradlew dependencies 2>/dev/null | \ |
|
grep -oE '[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+:[0-9a-zA-Z._-]+' | \ |
|
sort -u || true) |
|
|
|
if [[ -z "$deps" ]]; then |
|
echo "Warning: Could not parse dependencies, searching entire cache" >&2 |
|
SEARCH_ALL_CACHE=true |
|
return |
|
fi |
|
|
|
# Convert to source JAR paths |
|
> "$DEP_CACHE_FILE" |
|
while IFS=: read -r group artifact version; do |
|
[[ -z "$group" ]] && continue |
|
# Find source JARs matching this dependency (glob for hash directory) |
|
local jar_pattern="$GRADLE_CACHE/$group/$artifact/$version/*/${artifact}-${version}-sources.jar" |
|
for jar in $jar_pattern; do |
|
[[ -f "$jar" ]] && echo "$jar" >> "$DEP_CACHE_FILE" |
|
done |
|
done <<< "$deps" |
|
|
|
local count |
|
count=$(wc -l < "$DEP_CACHE_FILE" | tr -d ' ') |
|
echo "Cached $count source JARs from project dependencies" >&2 |
|
} |
|
|
|
# Check if cache needs refresh |
|
needs_cache_refresh() { |
|
[[ ! -f "$DEP_CACHE_FILE" ]] && return 0 |
|
$REFRESH_CACHE && return 0 |
|
|
|
# Check age |
|
local cache_age |
|
if [[ "$(uname)" == "Darwin" ]]; then |
|
cache_age=$(( $(date +%s) - $(stat -f %m "$DEP_CACHE_FILE") )) |
|
else |
|
cache_age=$(( $(date +%s) - $(stat -c %Y "$DEP_CACHE_FILE") )) |
|
fi |
|
|
|
[[ $cache_age -gt $DEP_CACHE_MAX_AGE ]] && return 0 |
|
return 1 |
|
} |
|
|
|
# Get list of JARs to search |
|
get_source_jars() { |
|
if $SEARCH_ALL_CACHE; then |
|
# Search entire gradle cache (original behavior) |
|
if command -v fd &>/dev/null; then |
|
fd -e jar 'sources\.jar$' "$GRADLE_CACHE" 2>/dev/null |
|
else |
|
find "$GRADLE_CACHE" -name '*-sources.jar' 2>/dev/null |
|
fi |
|
else |
|
# Use project dependencies only |
|
if needs_cache_refresh; then |
|
generate_dep_cache |
|
fi |
|
|
|
if [[ -f "$DEP_CACHE_FILE" && -s "$DEP_CACHE_FILE" ]]; then |
|
cat "$DEP_CACHE_FILE" |
|
else |
|
# Fallback to full cache search |
|
echo "Warning: No dependency cache, searching entire cache" >&2 |
|
if command -v fd &>/dev/null; then |
|
fd -e jar 'sources\.jar$' "$GRADLE_CACHE" 2>/dev/null |
|
else |
|
find "$GRADLE_CACHE" -name '*-sources.jar' 2>/dev/null |
|
fi |
|
fi |
|
fi |
|
} |
|
|
|
# Search JAR contents |
|
search_jar() { |
|
local jar="$1" |
|
if command -v zipinfo &>/dev/null; then |
|
zipinfo -1 "$jar" 2>/dev/null | grep -E "$PATTERN" || true |
|
else |
|
unzip -l "$jar" 2>/dev/null | awk '{print $4}' | grep -E "$PATTERN" || true |
|
fi |
|
} |
|
|
|
# Extract and display source |
|
extract_source() { |
|
local jar="$1" |
|
local file="$2" |
|
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" |
|
echo "📦 JAR: ${jar##*/}" |
|
echo "📄 File: $file" |
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" |
|
unzip -p "$jar" "$file" 2>/dev/null |
|
} |
|
|
|
# Main search |
|
found=0 |
|
while IFS= read -r jar; do |
|
[[ -z "$jar" ]] && continue |
|
[[ ! -f "$jar" ]] && continue |
|
|
|
while IFS= read -r match; do |
|
[[ -z "$match" ]] && continue |
|
found=$((found + 1)) |
|
|
|
if $LIST_ONLY; then |
|
echo "$jar -> $match" |
|
else |
|
extract_source "$jar" "$match" |
|
fi |
|
|
|
# Exit after first match unless -a flag |
|
if ! $ALL_MATCHES && ! $LIST_ONLY; then |
|
exit 0 |
|
fi |
|
done < <(search_jar "$jar") |
|
|
|
done < <(get_source_jars) |
|
|
|
if [[ $found -eq 0 ]]; then |
|
echo "❌ No source found for: $QUERY" >&2 |
|
echo "" >&2 |
|
echo "Tips:" >&2 |
|
echo " • Ensure sources are downloaded: ./gradlew idea" >&2 |
|
echo " • Try --all to search entire gradle cache" >&2 |
|
echo " • Try --refresh to update dependency cache" >&2 |
|
echo " • Check spelling (case-sensitive)" >&2 |
|
exit 1 |
|
fi |
|
|
|
if $LIST_ONLY; then |
|
echo "" |
|
echo "Found $found match(es)" |
|
fi |