Skip to content

Commit 9ff5e09

Browse files
AaronYoung5erijo
authored andcommitted
Add support for negative alt conditions (#522)
1 parent d479610 commit 9ff5e09

3 files changed

Lines changed: 109 additions & 15 deletions

File tree

test/test_unit_score_file.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,76 @@ def test_underscores_and_upper_case_in_distro_and_family(runner, yadm):
321321
assert run.success
322322
assert run.err == ""
323323
assert run.out == expected
324+
325+
326+
def test_negative_class_condition(runner, yadm):
327+
"""Test negative class condition: returns 0 when matching and proper score when not matching."""
328+
script = f"""
329+
YADM_TEST=1 source {yadm}
330+
local_class="testclass"
331+
local_classes=("testclass")
332+
333+
# 0
334+
score=0
335+
score_file "filename##~class.testclass" "dest"
336+
echo "score: $score"
337+
338+
# 16
339+
score=0
340+
score_file "filename##~class.badclass" "dest"
341+
echo "score2: $score"
342+
343+
# 16
344+
score=0
345+
score_file "filename##~c.badclass" "dest"
346+
echo "score3: $score"
347+
"""
348+
run = runner(command=["bash"], inp=script)
349+
assert run.success
350+
output = run.out.strip().splitlines()
351+
assert output[0] == "score: 0"
352+
assert output[1] == "score2: 16"
353+
assert output[2] == "score3: 16"
354+
355+
356+
def test_negative_combined_conditions(runner, yadm):
357+
"""Test negative conditions for multiple alt types: returns 0 when matching and proper score when not matching."""
358+
script = f"""
359+
YADM_TEST=1 source {yadm}
360+
local_class="testclass"
361+
local_classes=("testclass")
362+
local_distro="testdistro"
363+
364+
# (0) + (0) = 0
365+
score=0
366+
score_file "filename##~class.testclass,~distro.testdistro" "dest"
367+
echo "score: $score"
368+
369+
# (1000 + 16) + (1000 + 4) = 2020
370+
score=0
371+
score_file "filename##class.testclass,distro.testdistro" "dest"
372+
echo "score2: $score"
373+
374+
# 0 (negated class condition)
375+
score=0
376+
score_file "filename##~class.badclass,~distro.testdistro" "dest"
377+
echo "score3: $score"
378+
379+
# (1000 + 16) + (4) = 1020
380+
score=0
381+
score_file "filename##class.testclass,~distro.baddistro" "dest"
382+
echo "score4: $score"
383+
384+
# (1000 + 16) + (16) = 1032
385+
score=0
386+
score_file "filename##class.testclass,~class.badclass" "dest"
387+
echo "score5: $score"
388+
"""
389+
run = runner(command=["bash"], inp=script)
390+
assert run.success
391+
output = run.out.strip().splitlines()
392+
assert output[0] == "score: 0"
393+
assert output[1] == "score2: 2020"
394+
assert output[2] == "score3: 0"
395+
assert output[3] == "score4: 1020"
396+
assert output[4] == "score5: 1032"

yadm

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -180,32 +180,39 @@ function score_file() {
180180
local value=${field#*.}
181181
[ "$field" = "$label" ] && value="" # when .value is omitted
182182

183+
# Check for negative condition prefix (e.g., "~<label>")
184+
local negate=0
185+
if [ "${label:0:1}" = "~" ]; then
186+
negate=1
187+
label="${label:1}"
188+
fi
189+
183190
shopt -s nocasematch
184-
local -i delta=-1
191+
local -i delta=$((negate ? 1 : -1))
185192
case "$label" in
186193
default)
187194
delta=0
188195
;;
189196
a | arch)
190-
[[ "$value" = "$local_arch" ]] && delta=1
197+
[[ "$value" = "$local_arch" ]] && delta=1 || delta=-1
191198
;;
192199
o | os)
193-
[[ "$value" = "$local_system" ]] && delta=2
200+
[[ "$value" = "$local_system" ]] && delta=2 || delta=-2
194201
;;
195202
d | distro)
196-
[[ "${value// /_}" = "${local_distro// /_}" ]] && delta=4
203+
[[ "${value// /_}" = "${local_distro// /_}" ]] && delta=4 || delta=-4
197204
;;
198205
f | distro_family)
199-
[[ "${value// /_}" = "${local_distro_family// /_}" ]] && delta=8
206+
[[ "${value// /_}" = "${local_distro_family// /_}" ]] && delta=8 || delta=-8
200207
;;
201208
c | class)
202-
in_list "$value" "${local_classes[@]}" && delta=16
209+
in_list "$value" "${local_classes[@]}" && delta=16 || delta=-16
203210
;;
204211
h | hostname)
205-
[[ "$value" = "$local_host" ]] && delta=32
212+
[[ "$value" = "$local_host" ]] && delta=32 || delta=-32
206213
;;
207214
u | user)
208-
[[ "$value" = "$local_user" ]] && delta=64
215+
[[ "$value" = "$local_user" ]] && delta=64 || delta=-64
209216
;;
210217
e | extension)
211218
# extension isn't a condition and doesn't affect the score
@@ -231,11 +238,13 @@ function score_file() {
231238
esac
232239
shopt -u nocasematch
233240

241+
((negate)) && delta=$((-delta))
234242
if ((delta < 0)); then
235243
score=0
236244
return
237245
fi
238-
score=$((score + 1000 + delta))
246+
((negate)) || delta=$((delta + 1000))
247+
score=$((score + delta))
239248
done
240249

241250
record_score "$score" "$target" "$source" "$template_processor"

yadm.1

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -476,9 +476,11 @@ commas.
476476

477477
Each condition is an attribute/value pair, separated by a period. Some
478478
conditions do not require a "value", and in that case, the period and value can
479-
be omitted. Most attributes can be abbreviated as a single letter.
479+
be omitted. Most attributes can be abbreviated as a single letter. Prefixing an
480+
attribute with "~" negates the condition, meaning the condition is considered
481+
only if the attribute/value pair evaluates to false.
480482

481-
<attribute>[.<value>]
483+
[~]<attribute>[.<value>]
482484

483485
.BR NOTE :
484486
Value is compared case-insensitive.
@@ -555,9 +557,10 @@ symbolic links will be created for the most appropriate version.
555557

556558
The "most appropriate" version is determined by calculating a score for each
557559
version of a file. A template is always scored higher than any symlink
558-
condition. The number of conditions is the next largest factor in scoring.
559-
Files with more conditions will always be favored. Any invalid condition will
560-
disqualify that file completely.
560+
condition. The number of conditions is the next largest factor in scoring;
561+
files with more conditions will always be favored. Negative conditions (prefixed
562+
with "~") are scored only relative to the number of non-negated conditions.
563+
Any invalid condition will disqualify that file completely.
561564

562565
If you don't care to have all versions of alternates stored in the same
563566
directory as the generated symlink, you can place them in the
@@ -576,6 +579,7 @@ files are managed by yadm's repository:
576579
- $HOME/path/example.txt##os.Linux
577580
- $HOME/path/example.txt##os.Linux,hostname.host1
578581
- $HOME/path/example.txt##os.Linux,hostname.host2
582+
- $HOME/path/example.txt##class.Work,~os.Darwin
579583

580584
If running on a Macbook named "host2",
581585
yadm will create a symbolic link which looks like this:
@@ -598,10 +602,18 @@ If running on a Solaris server, the link will use the default version:
598602

599603
.IR $HOME/path/example.txt " -> " $HOME/path/example.txt##default
600604

601-
If running on a system, with class set to "Work", the link will be:
605+
If running on a Macbook with class set to "Work", the link will be:
602606

603607
.IR $HOME/path/example.txt " -> " $HOME/path/example.txt##class.Work
604608

609+
Negative conditions are supported via the "~" prefix. If again running on a system
610+
with class set to "Work", but instead within Windows Subsystem for Linux, where the
611+
os is reported as WSL, the link will be:
612+
613+
.IR $HOME/path/example.txt " -> " $HOME/path/example.txt##class.Work,~os.Darwin
614+
615+
Negative conditions use the same weight which corresponds to the attached attribute.
616+
605617
If no "##default" version exists and no files have valid conditions, then no
606618
link will be created.
607619

0 commit comments

Comments
 (0)