summaryrefslogtreecommitdiff
path: root/scripts/package-inventory
blob: 4742645c224d4a174eaee37554c8d29946ccc610 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/bin/bash
# package-inventory - Compare archsetup packages vs live system
# Shows: packages in archsetup but missing from system,
#        packages on system but not in archsetup
#
# Usage: package-inventory [--archsetup-only | --system-only | --summary]

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
ARCHSETUP="$SCRIPT_DIR/archsetup"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

# ============================================================================
# Extract packages from archsetup script
# ============================================================================
extract_archsetup_packages() {
    local tmp
    tmp=$(mktemp)

    # Extract pacman_install and aur_install calls (single package per line)
    grep -E '^\s*(pacman_install|aur_install)\s+' "$ARCHSETUP" \
        | grep -v '^\s*#' \
        | sed -E 's/^\s*(pacman_install|aur_install)\s+//' \
        | sed -E 's/\s*#.*//' \
        | sed 's/"//g' \
        | grep -v '^\$' \
        >> "$tmp"

    # Extract packages from for-loops: "for software in pkg1 pkg2 pkg3 ...; do"
    # These span multiple lines with backslash continuations
    awk '
    /for software in/ { capture=1; sub(/.*for software in/, ""); }
    capture {
        cont = ($0 ~ /\\$/)
        gsub(/\\/, "");
        gsub(/;.*/, "");
        gsub(/do$/, "");
        # Print each word as a package
        for (i=1; i<=NF; i++) {
            pkg = $i
            # Skip shell syntax
            if (pkg ~ /^\$|^#|^for|^in|^do|^done/) continue
            gsub(/"/, "", pkg)
            if (pkg != "") print pkg
        }
        if (!cont) capture=0
    }
    ' "$ARCHSETUP" >> "$tmp"

    # Extract pipx packages (these won't be in pacman -Qqe, note them separately)
    # Skip for now — pipx packages aren't pacman-managed

    sort -u "$tmp"
    rm -f "$tmp"
}

# ============================================================================
# Get packages on live system
# ============================================================================
get_system_packages_explicit() {
    pacman -Qqe | sort -u  # explicitly installed
}

get_system_packages_all() {
    pacman -Qq | sort -u   # all installed (including deps)
}

# ============================================================================
# Categorize system packages
# ============================================================================
get_system_native() {
    pacman -Qqen | sort -u  # native (official repos)
}

get_system_foreign() {
    pacman -Qqem | sort -u  # foreign (AUR/manual)
}

# ============================================================================
# Main comparison
# ============================================================================
main() {
    local mode="${1:-}"

    local archsetup_pkgs system_explicit system_all
    archsetup_pkgs=$(extract_archsetup_packages)
    system_explicit=$(get_system_packages_explicit)
    system_all=$(get_system_packages_all)

    # "not installed" = not on system at all (filters out dep-installed packages)
    # "not in archsetup" = explicitly installed but not declared in archsetup
    local missing_from_system missing_from_archsetup in_both
    missing_from_system=$(comm -23 <(echo "$archsetup_pkgs") <(echo "$system_all"))
    missing_from_archsetup=$(comm -13 <(echo "$archsetup_pkgs") <(echo "$system_explicit"))
    in_both=$(comm -12 <(echo "$archsetup_pkgs") <(echo "$system_explicit"))

    local archsetup_count system_count both_count missing_sys_count missing_arch_count
    archsetup_count=$(echo "$archsetup_pkgs" | wc -l)
    system_count=$(echo "$system_explicit" | wc -l)
    both_count=$(echo "$in_both" | grep -c . || true)
    missing_sys_count=$(echo "$missing_from_system" | grep -c . || true)
    missing_arch_count=$(echo "$missing_from_archsetup" | grep -c . || true)

    case "$mode" in
        --archsetup-only)
            echo "$missing_from_system"
            ;;
        --system-only)
            echo "$missing_from_archsetup"
            ;;
        --summary)
            print_summary "$archsetup_count" "$system_count" "$both_count" \
                "$missing_sys_count" "$missing_arch_count"
            ;;
        --help|-h)
            echo "Usage: package-inventory [OPTION]"
            echo ""
            echo "Compare packages in archsetup script vs installed on this system."
            echo ""
            echo "Options:"
            echo "  (none)            Full report with all sections"
            echo "  --archsetup-only  Packages in archsetup but not on system (plain list)"
            echo "  --system-only     Packages on system but not in archsetup (plain list)"
            echo "  --summary         Summary counts only"
            echo "  --help            This help message"
            ;;
        "")
            print_full_report "$archsetup_pkgs" "$system_explicit" \
                "$missing_from_system" "$missing_from_archsetup" "$in_both" \
                "$archsetup_count" "$system_count" "$both_count" \
                "$missing_sys_count" "$missing_arch_count"
            ;;
        *)
            echo "Unknown option: $mode" >&2
            echo "Run with --help for usage." >&2
            exit 1
            ;;
    esac
}

# ============================================================================
# Output formatting
# ============================================================================
print_summary() {
    local archsetup_count="$1" system_count="$2" both_count="$3"
    local missing_sys_count="$4" missing_arch_count="$5"

    echo -e "${BOLD}Package Inventory Summary${NC}"
    echo "─────────────────────────────────────"
    echo -e "  Archsetup declares:    ${CYAN}${archsetup_count}${NC} packages"
    echo -e "  System has installed:  ${CYAN}${system_count}${NC} packages (explicit)"
    echo -e "  In common:             ${GREEN}${both_count}${NC}"
    echo -e "  In archsetup, not system: ${YELLOW}${missing_sys_count}${NC}"
    echo -e "  On system, not archsetup: ${YELLOW}${missing_arch_count}${NC}"
}

print_full_report() {
    local archsetup_pkgs="$1" system_pkgs="$2"
    local missing_from_system="$3" missing_from_archsetup="$4" in_both="$5"
    local archsetup_count="$6" system_count="$7" both_count="$8"
    local missing_sys_count="$9" missing_arch_count="${10}"

    echo ""
    print_summary "$archsetup_count" "$system_count" "$both_count" \
        "$missing_sys_count" "$missing_arch_count"

    if [[ -n "$missing_from_system" ]]; then
        echo ""
        echo -e "${BOLD}${YELLOW}In archsetup but NOT installed on this system (${missing_sys_count}):${NC}"
        echo -e "${YELLOW}These may need installing, or are for a different desktop/hardware.${NC}"
        echo "─────────────────────────────────────"
        echo "$missing_from_system" | while read -r pkg; do
            echo "  $pkg"
        done
    fi

    if [[ -n "$missing_from_archsetup" ]]; then
        echo ""
        echo -e "${BOLD}${YELLOW}On system but NOT in archsetup (${missing_arch_count}):${NC}"
        echo -e "${YELLOW}Add to archsetup, or remove from system if no longer needed.${NC}"
        echo "─────────────────────────────────────"

        # Split into native and foreign for easier review
        local native foreign
        native=$(get_system_native)
        foreign=$(get_system_foreign)

        local foreign_extras native_extras
        foreign_extras=$(comm -12 <(echo "$missing_from_archsetup") <(echo "$foreign"))
        native_extras=$(comm -12 <(echo "$missing_from_archsetup") <(echo "$native"))

        local foreign_count native_count
        foreign_count=$(echo "$foreign_extras" | grep -c . || true)
        native_count=$(echo "$native_extras" | grep -c . || true)

        if [[ -n "$foreign_extras" ]]; then
            echo -e "  ${CYAN}AUR/foreign (${foreign_count}):${NC}"
            echo "$foreign_extras" | while read -r pkg; do
                echo "    $pkg"
            done
        fi

        if [[ -n "$native_extras" ]]; then
            echo -e "  ${CYAN}Official repos (${native_count}):${NC}"
            echo "$native_extras" | while read -r pkg; do
                echo "    $pkg"
            done
        fi
    fi

    echo ""
}

main "$@"