Skip to content

Commit b2a6a83

Browse files
django23claude
andauthored
feat(scan): add [scan] dirs to set scan roots without implying home (#130)
* feat(scan): add [scan] dirs to scan roots without implying home [scan] extra always adds to home, with no way to scan only a subset of it. [scan] dirs is an exhaustive list instead: home is scanned only if it's listed. Positional CLI overrides both; dirs wins over extra if both are set, with a warning; all-missing dirs is an error rather than a silent no-op scan. Closes #129. * test(scan): cover [scan] dirs narrowing a cache from a wider scan The path cache outlives a config change, so a cache built from a full home scan must be filtered on read rather than re-excluded wholesale when the scan roots narrow. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4b926d1 commit b2a6a83

6 files changed

Lines changed: 189 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
88

99
### Added
1010

11+
- `[scan] dirs` config key: an exhaustive list of scan roots, unlike `[scan] extra` which
12+
adds to home. Home is scanned only if you list it yourself, so you can scan just
13+
`~/Code` instead of all of home
14+
([#129](https://github.com/AsimovMac/asimov/issues/129)).
15+
1116
### Changed
1217

1318
### Fixed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,16 @@ extra = /Volumes/Work/clients # ~ is expanded, e.g. extra = ~/Sites
245245

246246
Your home directory is always scanned; these are added to it. Configured directories that don't exist (an unmounted volume, say) are skipped with a warning instead of failing the run. Passing a directory on the command line (`asimov /some/path`) still overrides everything and scans only that path.
247247

248+
Only keep projects under a subset of home — say `~/Code` — and don't want the rest of home scanned at all? Use `dirs` instead of `extra`. It's an exhaustive list: home is scanned only if you list it.
249+
250+
```ini
251+
[scan]
252+
dirs = ~/Code # one "dirs =" line per directory; home is NOT implied
253+
dirs = /Volumes/Work/clients # add as many roots as you need
254+
```
255+
256+
`dirs` and `extra` answer different questions, so don't mix them — if both are set, `dirs` wins and `extra` is ignored with a warning. If every listed directory turns out to be missing, Asimov exits with an error rather than silently scanning nothing.
257+
248258
### Add your own patterns
249259

250260
Using a tool Asimov doesn't know about yet? Add it yourself. Each pattern is a `directory sentinel` pair — exactly the same mechanism the [built-ins](#how-it-works) use: the directory is excluded **only** when the sentinel file sits right beside it, so it's safe even for common folder names.

lib/asimov/config.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ load_config() {
1414
ASIMOV_CONFIG_EXTRA_SENTINELS=()
1515
ASIMOV_CONFIG_DISABLED_SENTINELS=()
1616
ASIMOV_CONFIG_SCAN_DIRS=()
17+
ASIMOV_CONFIG_SCAN_DIRS_ONLY=()
1718
ASIMOV_CONFIG_EXTRA_SKIP_PATHS=()
1819

1920
[[ -f "$ASIMOV_CONFIG_FILE" ]] || return 0
@@ -47,6 +48,10 @@ load_config() {
4748
value="${value/#\~/$HOME}"
4849
ASIMOV_CONFIG_SCAN_DIRS+=("$value")
4950
;;
51+
scan:dirs)
52+
value="${value/#\~/$HOME}"
53+
ASIMOV_CONFIG_SCAN_DIRS_ONLY+=("$value")
54+
;;
5055
sentinels:extra)
5156
ASIMOV_CONFIG_EXTRA_SENTINELS+=("$value")
5257
;;

lib/asimov/doctor.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ doctor_check_config() {
283283
if [[ "$line" =~ ^([a-z_]+)[[:space:]]*=[[:space:]]*(.*)$ ]]; then
284284
key="${BASH_REMATCH[1]}"
285285
case "${section}:${key}" in
286-
fixed_dirs:enabled|fixed_dirs:extra|scan:extra|\
286+
fixed_dirs:enabled|fixed_dirs:extra|scan:extra|scan:dirs|\
287287
sentinels:extra|sentinels:disabled|skip_paths:extra) ;;
288288
*)
289289
# A key under an already-reported section is the same fault.

lib/asimov/scan.sh

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
# Resolve the set of directories to scan into ASIMOV_SCAN_DIRS.
77
# - A positional CLI argument overrides everything: scan only that directory.
8+
# - Otherwise, [scan] dirs in config (if set) is the exhaustive list of roots to
9+
# scan; home is included only if listed explicitly.
810
# - Otherwise scan the home directory plus any [scan] extra dirs from config.
911
# Configured dirs that don't exist are warned about and skipped rather than
1012
# aborting the run (e.g. an unmounted external volume).
@@ -20,16 +22,35 @@ resolve_scan_dirs() {
2022
return 0
2123
fi
2224

23-
ASIMOV_SCAN_DIRS=("$ASIMOV_ROOT")
2425
local scan_dir
25-
for scan_dir in ${ASIMOV_CONFIG_SCAN_DIRS[@]+"${ASIMOV_CONFIG_SCAN_DIRS[@]}"}; do
26-
if [[ ! -d "$scan_dir" ]]; then
27-
[[ -z "$ASIMOV_QUIET" ]] && \
28-
echo "asimov: configured scan directory does not exist, skipping: ${scan_dir}" >&2
29-
continue
26+
if [[ ${#ASIMOV_CONFIG_SCAN_DIRS_ONLY[@]} -gt 0 ]]; then
27+
if [[ ${#ASIMOV_CONFIG_SCAN_DIRS[@]} -gt 0 && -z "$ASIMOV_QUIET" ]]; then
28+
echo "asimov: [scan] dirs is set, ignoring [scan] extra" >&2
3029
fi
31-
ASIMOV_SCAN_DIRS+=("$scan_dir")
32-
done
30+
for scan_dir in "${ASIMOV_CONFIG_SCAN_DIRS_ONLY[@]}"; do
31+
if [[ ! -d "$scan_dir" ]]; then
32+
[[ -z "$ASIMOV_QUIET" ]] && \
33+
echo "asimov: configured scan directory does not exist, skipping: ${scan_dir}" >&2
34+
continue
35+
fi
36+
ASIMOV_SCAN_DIRS+=("$scan_dir")
37+
done
38+
39+
if [[ ${#ASIMOV_SCAN_DIRS[@]} -eq 0 ]]; then
40+
echo "asimov: none of the [scan] dirs in ${ASIMOV_CONFIG_FILE} exist" >&2
41+
exit 1
42+
fi
43+
else
44+
ASIMOV_SCAN_DIRS=("$ASIMOV_ROOT")
45+
for scan_dir in ${ASIMOV_CONFIG_SCAN_DIRS[@]+"${ASIMOV_CONFIG_SCAN_DIRS[@]}"}; do
46+
if [[ ! -d "$scan_dir" ]]; then
47+
[[ -z "$ASIMOV_QUIET" ]] && \
48+
echo "asimov: configured scan directory does not exist, skipping: ${scan_dir}" >&2
49+
continue
50+
fi
51+
ASIMOV_SCAN_DIRS+=("$scan_dir")
52+
done
53+
fi
3354

3455
prune_nested_scan_dirs
3556
}

tests/behavior.bats

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,145 @@ extra = ${HOME}/Code"
638638
[[ "$(count_exclusions)" -eq 1 ]]
639639
}
640640

641+
# =============================================================================
642+
# [scan] dirs — scan only these directories, home not implied
643+
# =============================================================================
644+
645+
@test "config [scan] dirs scans only the listed directory, not home" {
646+
local external
647+
external="$(mktemp -d)"
648+
mkdir -p "${external}/Site/node_modules"
649+
echo "sentinel" > "${external}/Site/package.json"
650+
create_project "Code/Home-Project" "package.json" "node_modules"
651+
652+
write_config "[scan]
653+
dirs = ${external}"
654+
655+
run_asimov
656+
assert_excluded "${external}/Site/node_modules"
657+
refute_excluded "${HOME}/Code/Home-Project/node_modules"
658+
[[ "$(count_exclusions)" -eq 1 ]]
659+
660+
rm -rf "$external"
661+
}
662+
663+
@test "config [scan] dirs with multiple entries scans each, home still excluded" {
664+
local a b
665+
a="$(mktemp -d)"
666+
b="$(mktemp -d)"
667+
mkdir -p "${a}/Site/node_modules"
668+
echo "sentinel" > "${a}/Site/package.json"
669+
mkdir -p "${b}/App/node_modules"
670+
echo "sentinel" > "${b}/App/package.json"
671+
create_project "Code/Home-Project" "package.json" "node_modules"
672+
673+
write_config "[scan]
674+
dirs = ${a}
675+
dirs = ${b}"
676+
677+
run_asimov
678+
assert_excluded "${a}/Site/node_modules"
679+
assert_excluded "${b}/App/node_modules"
680+
refute_excluded "${HOME}/Code/Home-Project/node_modules"
681+
[[ "$(count_exclusions)" -eq 2 ]]
682+
683+
rm -rf "$a" "$b"
684+
}
685+
686+
@test "config [scan] dirs includes home only when listed explicitly" {
687+
create_project "Code/Home-Project" "package.json" "node_modules"
688+
689+
write_config "[scan]
690+
dirs = ${HOME}"
691+
692+
run_asimov
693+
assert_excluded "${HOME}/Code/Home-Project/node_modules"
694+
}
695+
696+
@test "config [scan] dirs with a missing directory warns and scans the rest" {
697+
local external
698+
external="$(mktemp -d)"
699+
mkdir -p "${external}/Site/node_modules"
700+
echo "sentinel" > "${external}/Site/package.json"
701+
702+
write_config "[scan]
703+
dirs = /does/not/exist
704+
dirs = ${external}"
705+
706+
run_asimov
707+
[[ "$status" -eq 0 ]]
708+
[[ "$output" =~ "does not exist" ]]
709+
assert_excluded "${external}/Site/node_modules"
710+
711+
rm -rf "$external"
712+
}
713+
714+
@test "config [scan] dirs all missing exits with an error" {
715+
write_config "[scan]
716+
dirs = /does/not/exist"
717+
718+
run_asimov
719+
[[ "$status" -eq 1 ]]
720+
[[ "$output" =~ "none of the" ]]
721+
}
722+
723+
@test "config [scan] dirs takes precedence over [scan] extra, with a warning" {
724+
local only
725+
only="$(mktemp -d)"
726+
mkdir -p "${only}/Site/node_modules"
727+
echo "sentinel" > "${only}/Site/package.json"
728+
729+
local extra
730+
extra="$(mktemp -d)"
731+
mkdir -p "${extra}/Other/node_modules"
732+
echo "sentinel" > "${extra}/Other/package.json"
733+
734+
write_config "[scan]
735+
dirs = ${only}
736+
extra = ${extra}"
737+
738+
run_asimov
739+
[[ "$output" == *"ignoring [scan] extra"* ]]
740+
assert_excluded "${only}/Site/node_modules"
741+
refute_excluded "${extra}/Other/node_modules"
742+
743+
rm -rf "$only" "$extra"
744+
}
745+
746+
@test "config [scan] dirs narrows a cache built from a wider scan" {
747+
create_project "Code/Home-Project" "package.json" "node_modules"
748+
create_project "Other/Side-Project" "package.json" "node_modules"
749+
750+
# A cache left over from an earlier full-home run holds both paths. Narrowing
751+
# the scan roots must drop the out-of-scope one on read rather than re-exclude it.
752+
write_path_cache "${HOME}/Code/Home-Project/node_modules" \
753+
"${HOME}/Other/Side-Project/node_modules"
754+
755+
write_config "[scan]
756+
dirs = ${HOME}/Code"
757+
758+
run_asimov
759+
assert_excluded "${HOME}/Code/Home-Project/node_modules"
760+
refute_excluded "${HOME}/Other/Side-Project/node_modules"
761+
}
762+
763+
@test "directory argument overrides config [scan] dirs" {
764+
local external
765+
external="$(mktemp -d)"
766+
mkdir -p "${external}/Site/node_modules"
767+
echo "sentinel" > "${external}/Site/package.json"
768+
create_project "Code/Home-Project" "package.json" "node_modules"
769+
770+
write_config "[scan]
771+
dirs = ${external}"
772+
773+
run_asimov "${HOME}/Code"
774+
assert_excluded "${HOME}/Code/Home-Project/node_modules"
775+
refute_excluded "${external}/Site/node_modules"
776+
777+
rm -rf "$external"
778+
}
779+
641780
# =============================================================================
642781
# --verbose
643782
# =============================================================================

0 commit comments

Comments
 (0)