Skip to content

Commit e65973e

Browse files
committed
improve Make's default handling of multiline statements (if, for, while)
1 parent 6aec550 commit e65973e

File tree

5 files changed

+375
-47
lines changed

5 files changed

+375
-47
lines changed

Runfile

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,44 @@
11
BIN = ./node_modules/.bin
22

33
lint: # lint
4-
$(BIN)/shellcheck runfile.sh
4+
$(BIN)/shellcheck runfile.sh
55

66
man: # generate/update man page from README
7-
sed '1,/^Usage$$/d' README.md \
8-
| sed '1s/^/DESCRIPTION\\nBasic usage:\n/' \
9-
| $(BIN)/marked-man --name=runfile.sh \
10-
| sed -E 's/^(..Brunfile.sh)/\1 – Language-agnostic project task runner built on the ubiquitous Make./' \
11-
| tr '\n' '\r' \
12-
| sed -E 's/\([^\)]*\)//g' \
13-
| tr '\r' '\n' > ./man/run.1
7+
sed '1,/^Usage$$/d' README.md \
8+
| sed '1s/^/DESCRIPTION\\nBasic usage:\n/' \
9+
| $(BIN)/marked-man --name=runfile.sh \
10+
| sed -E 's/^(..Brunfile.sh)/\1 – Language-agnostic project task runner built on the ubiquitous Make./' \
11+
| tr '\n' '\r' \
12+
| sed -E 's/\([^\)]*\)//g' \
13+
| tr '\r' '\n' > ./man/run.1
1414

1515
release: # publish a new release (with confirmation)
16-
npm pack && tar -xvzf *.tgz && rm -rf package *.tgz \
17-
&& echo $'\n'\"Press ENTER to publish the files above · CTRL+C to cancel · node $( node -v ) · npm $( npm -v )\"$'\n\n' \
18-
&& head -n 1 >/dev/null && npm publish
16+
npm pack && tar -xvzf *.tgz && rm -rf package *.tgz \
17+
&& echo $'\n'\"Press ENTER to publish the files above · CTRL+C to cancel · node $( node -v ) · npm $( npm -v )\"$'\n\n' \
18+
&& head -n 1 >/dev/null && npm publish
1919

2020
releasenext: # publish a new pre-release (with confirmation)
21-
npm pack && tar -xvzf *.tgz && rm -rf package *.tgz \
22-
&& echo $'\n'\"Press ENTER to publish the files above (tag=next) · CTRL+C to cancel · node $( node -v ) · npm $( npm -v )\"$'\n\n' \
23-
&& head -n 1 >/dev/null && npm publish --tag next
21+
npm pack && tar -xvzf *.tgz && rm -rf package *.tgz \
22+
&& echo $'\n'\"Press ENTER to publish the files above (tag=next) · CTRL+C to cancel · node $( node -v ) · npm $( npm -v )\"$'\n\n' \
23+
&& head -n 1 >/dev/null && npm publish --tag next
2424

2525
test: # test
26-
if [[ "$(@)" =~ ^[0-9]+$$ ]]
27-
then run testi "$(@)"
28-
elif [[ -n "$(@)" ]]
29-
then run testf "$(@)"
30-
else run lint && $(BIN)/bats --jobs 16 --no-parallelize-across-files tests/test.bats
31-
fi
32-
33-
testf: lint # test by substring
34-
$(BIN)/bats -f "$(@)" tests/test.bats
35-
36-
testi: lint # test by index
37-
$(BIN)/bats -f "$$( grep @test ./tests/test.bats | head -$(1) | tail -1 | cut -d' ' -f3- | cut -d'"' -f1 )$$" ./tests/test.bats
26+
if [[ "$(@)" =~ ^[0-9]+$$ ]]
27+
then
28+
run test-by-index "$(@)"
29+
elif [[ -n "$(@)" ]]
30+
then
31+
run test-by-substring "$(@)"
32+
else
33+
run test-all
34+
fi
35+
36+
test-all: lint # run all tests
37+
$(BIN)/bats --jobs 16 --no-parallelize-across-files tests/test.bats
38+
39+
test-by-index: lint # run test case at index
40+
$(BIN)/bats -f "$$( grep @test ./tests/test.bats | head -$(1) | tail -1 | cut -d' ' -f3- | cut -d'"' -f1 )$$" ./tests/test.bats
41+
42+
test-by-substring: lint # run test cases matching substring
43+
$(BIN)/bats -f "$(@)" tests/test.bats
44+

runfile.sh

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ function wizard() {
403403
}
404404

405405
function run() ( set -euo pipefail
406-
local makefile='' buffer='' task=''
406+
local makefile='' buffer='' task='' baseindent=''
407407
local arg='' make_args=() named_args=() pos_args=() pos_arg_idx=0
408408

409409
local runfile_variables='' runfile_variable_re=''
@@ -539,26 +539,48 @@ function run() ( set -euo pipefail
539539
runfile_variables="${runfile_variables}"$'\n\n'
540540
fi
541541
542+
baseindent="$( grep -Eo '^[[:space:]]+' "$( smartcase-file runfile )" | head -1 )"
543+
if [[ -z "${baseindent}" ]]
544+
then
545+
baseindent='\t'
546+
fi
547+
542548
# ::::::::::::::::::::::::::::::::::::::::::
543549
# Construct temporary Makefile from Runfile:
544550
# Note: <<-EOF doesn't produce correct indentation for final lines; <<EOF required.
545551
cat <<EOF> "${makefile}"
552+
SHELL = bash
553+
546554
${runfile_variables}.PHONY: _tasks
547555
_tasks: .tasks
548556
$(
549557
grep "${runfile_grep_filter_args[@]}" "$( smartcase-file runfile )" \
550558
| grep -Ev "${runfile_variable_re}" \
551559
| sed -E \
552-
-e 's/[[:space:]]*$//' \
560+
-e "s/[[:space:]]*\$//" \
553561
`# trim any trailing whitespace from lines` \
554-
-e "s!^[[:space:]]*([^[:space:]])!\t\1!" \
555-
`# prefix every non-blank line with TAB` \
562+
-e "s/^${baseindent}//" \
563+
`# trim leading base indent from lines` \
564+
-e "s/^[[:space:]]+/\t/" \
565+
`# replace deeper indentation with TAB` \
566+
-e "s!^!\t!" \
567+
`# prefix every line with TAB` \
568+
-e "s!^\t\$!!" \
569+
`# remove TAB from blank lines` \
556570
-e "s!^\t${task_re}(.*)\$!\n.PHONY: \1\n\1:${subtask_re}\#\3!" \
557-
`# remove TAB prefix from lines that match task pattern` \
558-
-e "s!^\t(if|elif|then|else|for|while)[[:space:]](.*;.*)\$!\t\1 \2 \\\\!" \
559-
-e "s!^\t(if|elif|then|else|for|while)[[:space:]]([^;]*)\$!\t\1 \2; \\\\!" \
560-
-e "s!^\t(then|do|else|elif)\$!\t\1 \\\\!" \
561-
`# automatically add backslashes to multiline statements (if, for, while)` \
571+
`# remove TAB-prefix from lines that match task pattern` \
572+
`# Improve Make's default handling of multiline statements (if, for, while):` \
573+
-e "s!^\t\t(.*;[[:space:]]*[^\\]?)\$!\t\t\1 \\\\!" \
574+
-e "s!^\t\t(.*[^\\])\$!\t\t\1; \\\\!" \
575+
`# add backslash and/or semicolon to deep-indented lines where missing` \
576+
-e "s!^\t(if|elif|for|while)[[:space:]]([^;]*;[[:space:]]*(then|do))\$!\t\1 \2 \\\\!" \
577+
`# add backslash after "if ...; then" and "for ...; do" lines` \
578+
-e "s!^\t(if|elif|then|else|for|while|do)[[:space:]](.*;)\$!\t\1 \2 \\\\!" \
579+
`# add backslash after keyword lines that end with a semicolon` \
580+
-e "s!^\t(if|elif|then|else|for|while|do)[[:space:]](.*[^;\\])\$!\t\1 \2; \\\\!" \
581+
`# add semicolon and backslash after keyword lines NOT ending with semicolon` \
582+
-e "s!^\t(then|else|do)\$!\t\1 \\\\!" \
583+
`# add backslash after "then", "else", "do" when on their own line` \
562584
| cat -s
563585
)
564586

tests/Makefile

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
SHELL = bash
2+
13
.PHONY: _tasks
24
_tasks: .tasks
35

@@ -25,6 +27,153 @@ test: # run all tests or specific tests [vars: name1, name2, etc.]
2527
lint: # lint all files or specific file [vars: file]
2628
[[ -n $(1) ]] && echo "linting file $(1)" || echo "linting all files"
2729

30+
.PHONY: complex
31+
complex: # a sample command exploring complex multiline syntax
32+
echo "--- for loops ---"
33+
34+
for x in abc hjkl xyz; \
35+
do \
36+
echo -n "$${x} :: "; \
37+
done
38+
echo
39+
40+
for x in abc hjkl xyz; do \
41+
echo -n "$${x} :: "; \
42+
done
43+
echo
44+
45+
for x in abc hjkl xyz; \
46+
do echo -n "$${x} :: "; \
47+
done
48+
echo
49+
50+
echo "--- while loops ---"
51+
52+
while read x; \
53+
do \
54+
echo -n "$${x} :: "; \
55+
done <<< $$'abc\n hjkl\n xyz'
56+
echo
57+
58+
while read x; do \
59+
echo -n "$${x} :: "; \
60+
done <<< $$'abc\n hjkl\n xyz'
61+
echo
62+
63+
while read x; \
64+
do echo -n "$${x} :: "; \
65+
done <<< $$'abc\n hjkl\n xyz'
66+
echo
67+
68+
echo "--- if constructs ---"
69+
70+
x=''
71+
if [[ -z "$${x}" ]]; \
72+
then \
73+
echo -n "abc"; \
74+
echo -n " :: "; \
75+
elif [[ "$${x}" =~ a[Bb][a-z]$$ ]]; \
76+
then \
77+
echo -n "hjkl"; \
78+
echo -n " :: "; \
79+
else \
80+
echo -n "xyz"; \
81+
echo -n " :: "; \
82+
fi
83+
84+
y=''
85+
if [[ -n "$${y}" ]]; then \
86+
echo -n "abc"; \
87+
echo -n " :: "; \
88+
elif ! [[ "$${y}" =~ a[Bb][a-z]$$ ]]; then \
89+
echo -n "hjkl"; \
90+
echo -n " :: "; \
91+
else \
92+
echo -n "xyz"; \
93+
echo -n " :: "; \
94+
fi
95+
96+
z=''
97+
if [[ -z "$${x}" ]]; \
98+
then x='abc'; echo -n "$${x} :: "; \
99+
elif [[ x =~ a[Bb][a-z]$$ ]]; \
100+
then x='hjkl'; echo -n "$${x} :: "; \
101+
else echo -n "$${x} :: "; \
102+
fi
103+
echo
104+
105+
echo "--- for loops :: pathological semicolon usage ---"
106+
107+
for x in abc hjkl xyz; \
108+
do \
109+
echo -n "$${x} :: "; \
110+
done
111+
echo
112+
113+
for x in abc hjkl xyz; do \
114+
echo -n "$${x} :: "; \
115+
done
116+
echo
117+
118+
for x in abc hjkl xyz; \
119+
do echo -n "$${x} :: "; \
120+
done
121+
echo
122+
123+
echo "--- while loops :: pathological semicolon usage ---"
124+
125+
while read x; \
126+
do \
127+
echo -n "$${x} :: "; \
128+
done <<< $$'abc\n hjkl\n xyz';
129+
echo
130+
131+
while read x; do \
132+
echo -n "$${x} :: "; \
133+
done <<< $$'abc\n hjkl\n xyz';
134+
echo
135+
136+
while read x; \
137+
do echo -n "$${x} :: "; \
138+
done <<< $$'abc\n hjkl\n xyz';
139+
echo
140+
141+
echo "--- if constructs :: pathological semicolon usage ---"
142+
143+
x=''
144+
if [[ -z "$${x}" ]]; \
145+
then \
146+
echo -n "abc"; \
147+
echo -n " :: "; \
148+
elif [[ "$${x}" =~ a[Bb][a-z]$$ ]]; \
149+
then \
150+
echo -n "hjkl"; \
151+
echo -n " :: "; \
152+
else \
153+
echo -n "xyz"; \
154+
echo -n " :: "; \
155+
fi
156+
157+
y=''
158+
if [[ -n "$${y}" ]]; then \
159+
echo -n "abc"; \
160+
echo -n " :: "; \
161+
elif ! [[ "$${y}" =~ a[Bb][a-z]$$ ]]; then \
162+
echo -n "hjkl"; \
163+
echo -n " :: "; \
164+
else \
165+
echo -n "xyz"; \
166+
echo -n " :: "; \
167+
fi
168+
169+
z=''
170+
if [[ -z "$${x}" ]]; \
171+
then x='abc'; echo -n "$${x} :: "; \
172+
elif [[ x =~ a[Bb][a-z]$$ ]]; \
173+
then x='hjkl'; echo -n "$${x} :: "; \
174+
else echo -n "$${x} :: "; \
175+
fi
176+
28177
.PHONY: .tasks
29178
.tasks:
30179
@grep -E "^(([[:alnum:]_-][[:alnum:][:space:]_-]+):([[:alnum:][:space:]_-]+)?#)" $(MAKEFILE_LIST) \

0 commit comments

Comments
 (0)