Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

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

### Changed

### Fixed
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@ extra = /Volumes/Work/clients # ~ is expanded, e.g. extra = ~/Sites

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.

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.

```ini
[scan]
dirs = ~/Code # one "dirs =" line per directory; home is NOT implied
dirs = /Volumes/Work/clients # add as many roots as you need
```

`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.

### Add your own patterns

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.
Expand Down
5 changes: 5 additions & 0 deletions lib/asimov/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ load_config() {
ASIMOV_CONFIG_EXTRA_SENTINELS=()
ASIMOV_CONFIG_DISABLED_SENTINELS=()
ASIMOV_CONFIG_SCAN_DIRS=()
ASIMOV_CONFIG_SCAN_DIRS_ONLY=()
ASIMOV_CONFIG_EXTRA_SKIP_PATHS=()

[[ -f "$ASIMOV_CONFIG_FILE" ]] || return 0
Expand Down Expand Up @@ -47,6 +48,10 @@ load_config() {
value="${value/#\~/$HOME}"
ASIMOV_CONFIG_SCAN_DIRS+=("$value")
;;
scan:dirs)
value="${value/#\~/$HOME}"
ASIMOV_CONFIG_SCAN_DIRS_ONLY+=("$value")
;;
sentinels:extra)
ASIMOV_CONFIG_EXTRA_SENTINELS+=("$value")
;;
Expand Down
2 changes: 1 addition & 1 deletion lib/asimov/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ doctor_check_config() {
if [[ "$line" =~ ^([a-z_]+)[[:space:]]*=[[:space:]]*(.*)$ ]]; then
key="${BASH_REMATCH[1]}"
case "${section}:${key}" in
fixed_dirs:enabled|fixed_dirs:extra|scan:extra|\
fixed_dirs:enabled|fixed_dirs:extra|scan:extra|scan:dirs|\
sentinels:extra|sentinels:disabled|skip_paths:extra) ;;
*)
# A key under an already-reported section is the same fault.
Expand Down
37 changes: 29 additions & 8 deletions lib/asimov/scan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

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

ASIMOV_SCAN_DIRS=("$ASIMOV_ROOT")
local scan_dir
for scan_dir in ${ASIMOV_CONFIG_SCAN_DIRS[@]+"${ASIMOV_CONFIG_SCAN_DIRS[@]}"}; do
if [[ ! -d "$scan_dir" ]]; then
[[ -z "$ASIMOV_QUIET" ]] && \
echo "asimov: configured scan directory does not exist, skipping: ${scan_dir}" >&2
continue
if [[ ${#ASIMOV_CONFIG_SCAN_DIRS_ONLY[@]} -gt 0 ]]; then
if [[ ${#ASIMOV_CONFIG_SCAN_DIRS[@]} -gt 0 && -z "$ASIMOV_QUIET" ]]; then
echo "asimov: [scan] dirs is set, ignoring [scan] extra" >&2
fi
ASIMOV_SCAN_DIRS+=("$scan_dir")
done
for scan_dir in "${ASIMOV_CONFIG_SCAN_DIRS_ONLY[@]}"; do
if [[ ! -d "$scan_dir" ]]; then
[[ -z "$ASIMOV_QUIET" ]] && \
echo "asimov: configured scan directory does not exist, skipping: ${scan_dir}" >&2
continue
fi
ASIMOV_SCAN_DIRS+=("$scan_dir")
done

if [[ ${#ASIMOV_SCAN_DIRS[@]} -eq 0 ]]; then
echo "asimov: none of the [scan] dirs in ${ASIMOV_CONFIG_FILE} exist" >&2
exit 1
fi
else
ASIMOV_SCAN_DIRS=("$ASIMOV_ROOT")
for scan_dir in ${ASIMOV_CONFIG_SCAN_DIRS[@]+"${ASIMOV_CONFIG_SCAN_DIRS[@]}"}; do
if [[ ! -d "$scan_dir" ]]; then
[[ -z "$ASIMOV_QUIET" ]] && \
echo "asimov: configured scan directory does not exist, skipping: ${scan_dir}" >&2
continue
fi
ASIMOV_SCAN_DIRS+=("$scan_dir")
done
fi

prune_nested_scan_dirs
}
Expand Down
139 changes: 139 additions & 0 deletions tests/behavior.bats
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,145 @@ extra = ${HOME}/Code"
[[ "$(count_exclusions)" -eq 1 ]]
}

# =============================================================================
# [scan] dirs — scan only these directories, home not implied
# =============================================================================

@test "config [scan] dirs scans only the listed directory, not home" {
local external
external="$(mktemp -d)"
mkdir -p "${external}/Site/node_modules"
echo "sentinel" > "${external}/Site/package.json"
create_project "Code/Home-Project" "package.json" "node_modules"

write_config "[scan]
dirs = ${external}"

run_asimov
assert_excluded "${external}/Site/node_modules"
refute_excluded "${HOME}/Code/Home-Project/node_modules"
[[ "$(count_exclusions)" -eq 1 ]]

rm -rf "$external"
}

@test "config [scan] dirs with multiple entries scans each, home still excluded" {
local a b
a="$(mktemp -d)"
b="$(mktemp -d)"
mkdir -p "${a}/Site/node_modules"
echo "sentinel" > "${a}/Site/package.json"
mkdir -p "${b}/App/node_modules"
echo "sentinel" > "${b}/App/package.json"
create_project "Code/Home-Project" "package.json" "node_modules"

write_config "[scan]
dirs = ${a}
dirs = ${b}"

run_asimov
assert_excluded "${a}/Site/node_modules"
assert_excluded "${b}/App/node_modules"
refute_excluded "${HOME}/Code/Home-Project/node_modules"
[[ "$(count_exclusions)" -eq 2 ]]

rm -rf "$a" "$b"
}

@test "config [scan] dirs includes home only when listed explicitly" {
create_project "Code/Home-Project" "package.json" "node_modules"

write_config "[scan]
dirs = ${HOME}"

run_asimov
assert_excluded "${HOME}/Code/Home-Project/node_modules"
}

@test "config [scan] dirs with a missing directory warns and scans the rest" {
local external
external="$(mktemp -d)"
mkdir -p "${external}/Site/node_modules"
echo "sentinel" > "${external}/Site/package.json"

write_config "[scan]
dirs = /does/not/exist
dirs = ${external}"

run_asimov
[[ "$status" -eq 0 ]]
[[ "$output" =~ "does not exist" ]]
assert_excluded "${external}/Site/node_modules"

rm -rf "$external"
}

@test "config [scan] dirs all missing exits with an error" {
write_config "[scan]
dirs = /does/not/exist"

run_asimov
[[ "$status" -eq 1 ]]
[[ "$output" =~ "none of the" ]]
}

@test "config [scan] dirs takes precedence over [scan] extra, with a warning" {
local only
only="$(mktemp -d)"
mkdir -p "${only}/Site/node_modules"
echo "sentinel" > "${only}/Site/package.json"

local extra
extra="$(mktemp -d)"
mkdir -p "${extra}/Other/node_modules"
echo "sentinel" > "${extra}/Other/package.json"

write_config "[scan]
dirs = ${only}
extra = ${extra}"

run_asimov
[[ "$output" == *"ignoring [scan] extra"* ]]
assert_excluded "${only}/Site/node_modules"
refute_excluded "${extra}/Other/node_modules"

rm -rf "$only" "$extra"
}

@test "config [scan] dirs narrows a cache built from a wider scan" {
create_project "Code/Home-Project" "package.json" "node_modules"
create_project "Other/Side-Project" "package.json" "node_modules"

# A cache left over from an earlier full-home run holds both paths. Narrowing
# the scan roots must drop the out-of-scope one on read rather than re-exclude it.
write_path_cache "${HOME}/Code/Home-Project/node_modules" \
"${HOME}/Other/Side-Project/node_modules"

write_config "[scan]
dirs = ${HOME}/Code"

run_asimov
assert_excluded "${HOME}/Code/Home-Project/node_modules"
refute_excluded "${HOME}/Other/Side-Project/node_modules"
}

@test "directory argument overrides config [scan] dirs" {
local external
external="$(mktemp -d)"
mkdir -p "${external}/Site/node_modules"
echo "sentinel" > "${external}/Site/package.json"
create_project "Code/Home-Project" "package.json" "node_modules"

write_config "[scan]
dirs = ${external}"

run_asimov "${HOME}/Code"
assert_excluded "${HOME}/Code/Home-Project/node_modules"
refute_excluded "${external}/Site/node_modules"

rm -rf "$external"
}

# =============================================================================
# --verbose
# =============================================================================
Expand Down
Loading