commit 254edfc
xplshn
·
2026-07-01 05:12:22 +0000 UTC
parent 1586f63
sh: qol change Signed-off-by: xplshn <anto@xplshn.com.ar>
6 files changed,
+525,
-8
M
Makefile
+1,
-0
1@@ -785,6 +785,7 @@ SHOBJ =\
2 cmd/posix/sh/cd.o\
3 cmd/posix/sh/echo.o\
4 cmd/posix/sh/error.o\
5+ cmd/posix/sh/easter.o\
6 cmd/posix/sh/eval.o\
7 cmd/posix/sh/exec.o\
8 cmd/posix/sh/expand.o\
+9,
-8
1@@ -35,9 +35,10 @@ posix)
2 FEATURE_STAT_FILESYSTEM=0; FEATURE_STAT_FORMAT=0
3 FEATURE_SORT_BIG=0; FEATURE_SORT_STABLE=0
4 FEATURE_OD_ENDIAN=0;
5- FEATURE_SH_HISTEDIT=0; FEATURE_SH_LOCAL=0
6- FEATURE_SH_LET=0; FEATURE_SH_ULIMIT=0
7- FEATURE_SH_SETVAR=0; FEATURE_SH_WORDEXP=0
8+ FEATURE_SH_EASTER=0; FEATURE_SH_HISTEDIT=0;
9+ FEATURE_SH_LOCAL=0 FEATURE_SH_LET=0;
10+ FEATURE_SH_ULIMIT=0 FEATURE_SH_SETVAR=0;
11+ FEATURE_SH_WORDEXP=0
12 FEATURE_CAL_EXT=0
13 FEATURE_LS_COLOR=0
14 FEATURE_DIFF3_MERGE=0; FEATURE_DIFF3_EDSCRIPT=0
15@@ -70,9 +71,9 @@ common)
16 FEATURE_STAT_FILESYSTEM=1; FEATURE_STAT_FORMAT=1
17 FEATURE_SORT_BIG=0; FEATURE_SORT_STABLE=1
18 FEATURE_OD_ENDIAN=0;
19- FEATURE_SH_HISTEDIT=0; FEATURE_SH_LOCAL=1
20- FEATURE_SH_LET=1; FEATURE_SH_ULIMIT=1
21- FEATURE_SH_SETVAR=0; FEATURE_SH_WORDEXP=0
22+ FEATURE_SH_EASTER=0; FEATURE_SH_HISTEDIT=0;
23+ FEATURE_SH_LOCAL=1 FEATURE_SH_LET=1;
24+ FEATURE_SH_ULIMIT=1 FEATURE_SH_SETVAR=0;
25 FEATURE_CAL_EXT=1
26 FEATURE_LS_COLOR=1
27 FEATURE_DIFF3_MERGE=0; FEATURE_DIFF3_EDSCRIPT=1
28@@ -105,8 +106,8 @@ all)
29 FEATURE_STAT_FILESYSTEM=1; FEATURE_STAT_FORMAT=1
30 FEATURE_SORT_BIG=1; FEATURE_SORT_STABLE=1
31 FEATURE_OD_ENDIAN=1;
32- FEATURE_SH_HISTEDIT=1
33- FEATURE_INPROC_EXEC=0 FEATURE_SH_LOCAL=1
34+ FEATURE_SH_EASTER=0; FEATURE_SH_HISTEDIT=1
35+ FEATURE_INPROC_EXEC=0; FEATURE_SH_LOCAL=1
36 FEATURE_SH_LET=1; FEATURE_SH_ULIMIT=1
37 FEATURE_SH_SETVAR=1; FEATURE_SH_WORDEXP=1
38 FEATURE_CAL_EXT=1
+2,
-0
1@@ -89,3 +89,5 @@ unaliascmd unalias
2 unsetcmd -s unset
3 waitcmd wait
4 wordexpcmd wordexp #FEATURE_SH_WORDEXP
5+bogometercmd -n bogometer #FEATURE_SH_EASTER
6+boguscmd -n bogus #FEATURE_SH_EASTER
+509,
-0
1@@ -0,0 +1,509 @@
2+/* See LICENSE file for copyright and license details. */
3+#include "bltin.h"
4+#include "fs.h"
5+#include "util.h"
6+
7+#include <sys/stat.h>
8+#include <sys/types.h>
9+
10+#include <fcntl.h>
11+#include <string.h>
12+#include <unistd.h>
13+
14+enum {
15+ COMMENT_C, /* line // and block /* */
16+ COMMENT_HASH, /* line # only */
17+ COMMENT_PERCENT, /* line % only */
18+ COMMENT_SEMI, /* line ; only */
19+ COMMENT_DASH, /* line -- only */
20+ COMMENT_HASKELL, /* line -- and block {- -} */
21+ COMMENT_PAREN, /* block (* *) only, no line comment */
22+};
23+
24+enum {
25+ LANG_C,
26+ LANG_CPP,
27+ LANG_SH,
28+ LANG_AWK,
29+ LANG_MAKE,
30+ LANG_M4,
31+ LANG_LUA,
32+ LANG_PERL,
33+ LANG_PYTHON,
34+ LANG_RUBY,
35+ LANG_PHP,
36+ LANG_JS,
37+ LANG_TS,
38+ LANG_JAVA,
39+ LANG_KOTLIN,
40+ LANG_SCALA,
41+ LANG_CS,
42+ LANG_GO,
43+ LANG_SWIFT,
44+ LANG_ZIG,
45+ LANG_DART,
46+ LANG_ELIXIR,
47+ LANG_ERLANG,
48+ LANG_HASKELL,
49+ LANG_OCAML,
50+ LANG_CLOJURE,
51+ LANG_RUST,
52+ LANG_ADA,
53+ LANG_R,
54+ LANG_COUNT
55+};
56+
57+struct CommentSpec {
58+ const char *line;
59+ const char *bopen;
60+ const char *bclose;
61+};
62+
63+static const struct CommentSpec comment_specs[] = {
64+ [COMMENT_C] = {"//", "/*", "*/"},
65+ [COMMENT_HASH] = {"#", NULL, NULL},
66+ [COMMENT_PERCENT] = {"%", NULL, NULL},
67+ [COMMENT_SEMI] = {";", NULL, NULL},
68+ [COMMENT_DASH] = {"--", NULL, NULL},
69+ [COMMENT_HASKELL] = {"--", "{-", "-}"},
70+ [COMMENT_PAREN] = {NULL, "(*", "*)"},
71+};
72+
73+/* weight is this languages contribution per percent of the codebase */
74+struct LangDef {
75+ const char *name;
76+ int weight;
77+ int cstyle;
78+};
79+
80+static const struct LangDef lang_defs[LANG_COUNT] = {
81+ [LANG_C] = {"C", 1, COMMENT_C },
82+ [LANG_CPP] = {"C++", 300, COMMENT_C },
83+ [LANG_SH] = {"Shell", 6, COMMENT_HASH },
84+ [LANG_AWK] = {"Awk", 6, COMMENT_HASH },
85+ [LANG_MAKE] = {"Make", 10, COMMENT_HASH },
86+ [LANG_M4] = {"M4", 25, COMMENT_HASH },
87+ [LANG_LUA] = {"Lua", 25, COMMENT_DASH },
88+ [LANG_PERL] = {"Perl", 500, COMMENT_HASH },
89+ [LANG_PYTHON] = {"Python", 80, COMMENT_HASH },
90+ [LANG_RUBY] = {"Ruby", 90, COMMENT_HASH },
91+ [LANG_PHP] = {"PHP", 110, COMMENT_C },
92+ [LANG_JS] = {"JS", 100, COMMENT_C },
93+ [LANG_TS] = {"TS", 140, COMMENT_C },
94+ [LANG_JAVA] = {"Java", 150, COMMENT_C },
95+ [LANG_KOTLIN] = {"Kotlin", 160, COMMENT_C },
96+ [LANG_SCALA] = {"Scala", 180, COMMENT_C },
97+ [LANG_CS] = {"C#", 140, COMMENT_C },
98+ [LANG_GO] = {"Go", 3, COMMENT_C },
99+ [LANG_SWIFT] = {"Swift", 70, COMMENT_C },
100+ [LANG_ZIG] = {"Zig", 65, COMMENT_C },
101+ [LANG_DART] = {"Dart", 55, COMMENT_C },
102+ [LANG_ELIXIR] = {"Elixir", 50, COMMENT_HASH },
103+ [LANG_ERLANG] = {"Erlang", 55, COMMENT_PERCENT},
104+ [LANG_HASKELL] = {"Haskell", 200, COMMENT_HASKELL},
105+ [LANG_OCAML] = {"OCaml", 45, COMMENT_PAREN },
106+ [LANG_CLOJURE] = {"Clojure", 85, COMMENT_SEMI },
107+ [LANG_RUST] = {"Rust", 3000, COMMENT_C },
108+ [LANG_ADA] = {"Ada", 15, COMMENT_DASH },
109+ [LANG_R] = {"R", 40, COMMENT_HASH },
110+};
111+
112+struct ExtMap {
113+ const char *ext;
114+ int lang;
115+};
116+
117+static const struct ExtMap ext_map[] = {
118+ {".c", LANG_C },
119+ {".cc", LANG_CPP },
120+ {".cpp", LANG_CPP },
121+ {".cxx", LANG_CPP },
122+ {".hpp", LANG_CPP },
123+ {".sh", LANG_SH },
124+ {".awk", LANG_AWK },
125+ {".mk", LANG_MAKE },
126+ {".m4", LANG_M4 },
127+ {".lua", LANG_LUA },
128+ {".pl", LANG_PERL },
129+ {".pm", LANG_PERL },
130+ {".py", LANG_PYTHON },
131+ {".pyw", LANG_PYTHON },
132+ {".rb", LANG_RUBY },
133+ {".php", LANG_PHP },
134+ {".js", LANG_JS },
135+ {".mjs", LANG_JS },
136+ {".cjs", LANG_JS },
137+ {".ts", LANG_TS },
138+ {".tsx", LANG_TS },
139+ {".java", LANG_JAVA },
140+ {".kt", LANG_KOTLIN },
141+ {".kts", LANG_KOTLIN },
142+ {".scala", LANG_SCALA },
143+ {".cs", LANG_CS },
144+ {".go", LANG_GO },
145+ {".swift", LANG_SWIFT },
146+ {".zig", LANG_ZIG },
147+ {".dart", LANG_DART },
148+ {".ex", LANG_ELIXIR },
149+ {".exs", LANG_ELIXIR },
150+ {".erl", LANG_ERLANG },
151+ {".hrl", LANG_ERLANG },
152+ {".hs", LANG_HASKELL},
153+ {".lhs", LANG_HASKELL},
154+ {".ml", LANG_OCAML },
155+ {".mli", LANG_OCAML },
156+ {".clj", LANG_CLOJURE},
157+ {".cljs", LANG_CLOJURE},
158+ {".rs", LANG_RUST },
159+ {".adb", LANG_ADA },
160+ {".ads", LANG_ADA },
161+ {".r", LANG_R },
162+ {".R", LANG_R },
163+};
164+
165+struct NameMap {
166+ const char *name;
167+ int lang;
168+};
169+
170+static const struct NameMap name_map[] = {
171+ {"Makefile", LANG_MAKE},
172+ {"GNUmakefile", LANG_MAKE},
173+ {".profile", LANG_SH },
174+ {"profile", LANG_SH },
175+ {".bashrc", LANG_SH },
176+ {".bash_profile", LANG_SH },
177+ {".shrc", LANG_SH },
178+ {".kshrc", LANG_SH },
179+ {".zshrc", LANG_SH },
180+ {".cshrc", LANG_SH },
181+ {".login", LANG_SH },
182+};
183+
184+/* not scanned for lines, but still checked against markers below */
185+static const char *const skip_dirs[] = {
186+ ".git", ".hg", ".svn", "node_modules", "vendor", "__pycache__",
187+ ".venv", "venv", "target", "build", "dist", ".cache",
188+ ".next", ".idea", ".vscode", NULL,
189+};
190+
191+struct Marker {
192+ const char *name;
193+ int is_dir;
194+ double penalty;
195+ const char *remark;
196+};
197+
198+static const struct Marker markers[] = {
199+ {"node_modules", 1, 150.0, "skipping bullshit / malware" },
200+ {"Cargo.toml", 0, 80.0, "I challenge you to bootstrapping this shitty lang" },
201+ {"package.json", 0, 60.0, "js. Self-explanatory" },
202+ {"go.mod", 0, 20.0, "Not C. But still good. Minor penalty" },
203+ {"Dockerfile", 0, 20.0, "containerization crutch" },
204+ {"Gemfile", 0, 55.0, "shitty language baseline" },
205+ {"CMakeLists.txt", 0, 55.0, "shitty build system" },
206+ {"tsconfig.json", 0, 60.0, "a language with a configuration file. Degenerate." },
207+};
208+
209+struct ComboPenalty {
210+ int lang_a;
211+ int lang_b;
212+ double penalty;
213+ const char *remark;
214+};
215+
216+static const struct ComboPenalty combos[] = {
217+ {LANG_RUST, LANG_CPP, 4000.0, "MAXIMUM BOGUSNESS, rust and c++ sharing a tree"},
218+ {LANG_RUST, LANG_C, 400.0, "comical bogusness, rust apologizing to the c it links against"},
219+ {LANG_JS, LANG_TS, 150.0, "half the codebase does not trust the other half"},
220+ {LANG_C, LANG_CPP, 120.0, "abi boundary between two compilers of the same language"},
221+};
222+
223+struct LangStat {
224+ long files;
225+ long blank;
226+ long comment;
227+ long code;
228+};
229+
230+struct ScanState {
231+ struct LangStat stats[LANG_COUNT];
232+ double bonus;
233+ int marker_seen[LEN(markers)];
234+};
235+
236+#define BABEL_THRESHOLD 3
237+#define BABEL_MULT 1.5
238+
239+static int
240+is_classic(int lang)
241+{
242+ return lang == LANG_C || lang == LANG_SH || lang == LANG_AWK;
243+}
244+
245+static int
246+is_skip_dir(const char *name)
247+{
248+ int i;
249+
250+ for (i = 0; skip_dirs[i]; i++)
251+ if (!strcmp(name, skip_dirs[i]))
252+ return 1;
253+ return 0;
254+}
255+
256+/* checks a 1 or 2 char marker at p without reading past bufend */
257+static int
258+marker_at(const char *p, const char *bufend, const char *marker)
259+{
260+ if (!marker)
261+ return 0;
262+ if (!marker[1])
263+ return *p == marker[0];
264+ return p + 1 < bufend && p[0] == marker[0] && p[1] == marker[1];
265+}
266+
267+static int
268+detect_lang(const char *name)
269+{
270+ const char *ext;
271+ size_t i, nlen;
272+
273+ for (i = 0; i < LEN(name_map); i++)
274+ if (!strcmp(name, name_map[i].name))
275+ return name_map[i].lang;
276+
277+ ext = strrchr(name, '.');
278+ if (ext) {
279+ for (i = 0; i < LEN(ext_map); i++)
280+ if (!strcmp(ext, ext_map[i].ext))
281+ return ext_map[i].lang;
282+ }
283+
284+ /*
285+ * anchored to the end of the name, so arch.txt (which contains "rc"
286+ * but does not end in it) is never mistaken for a shell rc file
287+ */
288+ nlen = strlen(name);
289+ if (nlen >= 2 && !strcmp(name + nlen - 2, "rc"))
290+ return LANG_SH;
291+ if (nlen >= 7 && !strcmp(name + nlen - 7, "profile"))
292+ return LANG_SH;
293+
294+ return -1;
295+}
296+
297+static void
298+check_marker(const char *name, int is_dir, struct ScanState *st)
299+{
300+ size_t i;
301+
302+ for (i = 0; i < LEN(markers); i++) {
303+ if (markers[i].is_dir != is_dir || strcmp(name, markers[i].name) != 0)
304+ continue;
305+ if (st->marker_seen[i])
306+ return;
307+ st->marker_seen[i] = 1;
308+ st->bonus += markers[i].penalty;
309+ fprintf(stderr, "note: %s: %s\n", markers[i].name, markers[i].remark);
310+ return;
311+ }
312+}
313+
314+static void
315+scan_file(int dirfd, const char *name, int lang, struct LangStat *lp)
316+{
317+ const struct CommentSpec *cs;
318+ char buf[8192];
319+ char *p, *bufend;
320+ ssize_t nread;
321+ int fd, in_block, leading_ws, checked, line_has_content, line_is_comment;
322+
323+ fd = openat(dirfd, name, O_RDONLY);
324+ if (fd < 0)
325+ return;
326+
327+ lp->files++;
328+ cs = &comment_specs[lang_defs[lang].cstyle];
329+ in_block = 0;
330+ leading_ws = 1;
331+ checked = 0;
332+ line_has_content = 0;
333+ line_is_comment = 0;
334+
335+ while ((nread = read(fd, buf, sizeof(buf))) > 0) {
336+ for (p = buf, bufend = buf + nread; p < bufend; p++) {
337+ if (*p == '\n') {
338+ if (!line_has_content)
339+ lp->blank++;
340+ else if (line_is_comment)
341+ lp->comment++;
342+ else
343+ lp->code++;
344+ leading_ws = 1;
345+ checked = 0;
346+ line_has_content = 0;
347+ line_is_comment = 0;
348+ continue;
349+ }
350+
351+ if (leading_ws) {
352+ if (*p == ' ' || *p == '\t' || *p == '\r')
353+ continue;
354+ leading_ws = 0;
355+ line_has_content = 1;
356+ }
357+
358+ /* only the first token of a line decides if the line starts a comment */
359+ if (!checked) {
360+ checked = 1;
361+ if (in_block) {
362+ line_is_comment = 1;
363+ if (marker_at(p, bufend, cs->bclose))
364+ in_block = 0;
365+ } else if (marker_at(p, bufend, cs->line)) {
366+ line_is_comment = 1;
367+ } else if (marker_at(p, bufend, cs->bopen)) {
368+ line_is_comment = 1;
369+ in_block = 1;
370+ }
371+ } else if (in_block && marker_at(p, bufend, cs->bclose)) {
372+ in_block = 0;
373+ }
374+ }
375+ }
376+
377+ /* files that dont end in a newline still have one line left to count */
378+ if (line_has_content) {
379+ if (line_is_comment)
380+ lp->comment++;
381+ else
382+ lp->code++;
383+ }
384+
385+ close(fd);
386+}
387+
388+static void
389+scan(int dirfd, const char *name, struct stat *st, void *data, struct recursor *r)
390+{
391+ struct ScanState *scanst = data;
392+ int lang;
393+
394+ check_marker(name, S_ISDIR(st->st_mode), scanst);
395+
396+ if (S_ISDIR(st->st_mode)) {
397+ if (is_skip_dir(name))
398+ return;
399+ recurse(dirfd, name, data, r);
400+ return;
401+ }
402+
403+ if (!S_ISREG(st->st_mode))
404+ return;
405+
406+ lang = detect_lang(name);
407+ if (lang < 0)
408+ return;
409+
410+ scan_file(dirfd, name, lang, &scanst->stats[lang]);
411+}
412+
413+int
414+bogometercmd(int argc, char **argv)
415+{
416+ struct recursor r = {.fn = scan, .follow = 'P', .flags = SILENT};
417+ struct ScanState st = {0};
418+ const char *target;
419+ double pct, score;
420+ long total_code;
421+ int i, modern_langs;
422+
423+ target = (argc > 1) ? argv[1] : ".";
424+ total_code = 0;
425+ score = 0.0;
426+
427+ recurse(AT_FDCWD, target, &st, &r);
428+
429+ printf("%-10s %10s %10s %10s %10s\n", "Language", "Files", "Blank", "Comment", "Code");
430+ printf("---------------------------------------------------------\n");
431+
432+ for (i = 0; i < LANG_COUNT; i++) {
433+ if (st.stats[i].files == 0)
434+ continue;
435+ printf("%-10s %10ld %10ld %10ld %10ld\n",
436+ lang_defs[i].name, st.stats[i].files, st.stats[i].blank, st.stats[i].comment,
437+ st.stats[i].code);
438+ total_code += st.stats[i].code;
439+ }
440+
441+ if (total_code == 0) {
442+ printf("\nNo targeted code found. System is pure.\n");
443+ return 0;
444+ }
445+
446+ printf("---------------------------------------------------------\n");
447+ modern_langs = 0;
448+ for (i = 0; i < LANG_COUNT; i++) {
449+ if (st.stats[i].code == 0)
450+ continue;
451+ pct = ((double)st.stats[i].code / total_code) * 100.0;
452+ score += pct * lang_defs[i].weight;
453+ printf("Distribution: %-6s is %5.2f%% of targeted language lines\n", lang_defs[i].name, pct);
454+ if (!is_classic(i))
455+ modern_langs++;
456+ }
457+
458+ if (modern_langs >= BABEL_THRESHOLD) {
459+ score *= BABEL_MULT;
460+ fprintf(stderr,
461+ "note: %d languages in one tree: babel tower, no two files agree on what tongue they speak\n",
462+ modern_langs);
463+ }
464+
465+ fflush(stdout);
466+ for (i = 0; i < (int)LEN(combos); i++) {
467+ if (st.stats[combos[i].lang_a].code > 0 && st.stats[combos[i].lang_b].code > 0) {
468+ score += combos[i].penalty;
469+ fprintf(stderr, "note: %s + %s: %s\n", lang_defs[combos[i].lang_a].name,
470+ lang_defs[combos[i].lang_b].name, combos[i].remark);
471+ fflush(stderr);
472+ }
473+ }
474+
475+ if (st.stats[LANG_RUST].code > 0 && st.stats[LANG_CPP].code == 0 && st.stats[LANG_C].code == 0)
476+ fprintf(stderr, "note: rust: enormous magnitudes of bogus\n");
477+
478+ score += st.bonus;
479+
480+ if (modern_langs == 0 && st.stats[LANG_C].code > 0 && st.stats[LANG_SH].code > 0 &&
481+ st.stats[LANG_AWK].code > 0)
482+ printf("note: sh + awk + c only: peak codebase\n");
483+
484+ printf("---------------------------------------------------------\n");
485+ printf("TOTAL CODE LINES: %ld\n", total_code);
486+ printf("AGGREGATE BOGOSITY INDEX: %5.2f uL\n", score);
487+
488+ if (score > 5000.0)
489+ fprintf(stderr, "note: bogosity index over 5000: critical\n");
490+ else if (st.stats[LANG_C].code > 0 && score < 200.0)
491+ printf("note: bogosity index under 200 with c present: clean architecture\n");
492+
493+ return 0;
494+}
495+
496+int
497+boguscmd(int argc __unused, char **argv __unused)
498+{
499+ printf(
500+ "BOGOSITY n. The degree to which something is BOGUS. Measured with a\n"
501+ "bogometer, in units of the microLenat (uL).\n"
502+ "\n"
503+ "BOGUS adj. 1. Non-functional. \"Your patches are bogus.\"\n"
504+ "2. Useless. \"OPCON is a bogus program.\"\n"
505+ "3. False. \"Your arguments are bogus.\"\n"
506+ "4. Incorrect. \"That algorithm is bogus.\"\n"
507+ "5. Silly. \"Stop writing those bogus sagas.\"\n"
508+ );
509+ return 0;
510+}
+3,
-0
1@@ -44,6 +44,7 @@ feature_let=1
2 feature_ulimit=1
3 feature_setvar=1
4 feature_wordexp=1
5+feature_easter=1
6
7 if [ -f ../../config.mk ]; then
8 feature_histedit=$(grep '^[[:space:]]*FEATURE_SH_HISTEDIT[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
9@@ -52,6 +53,7 @@ if [ -f ../../config.mk ]; then
10 feature_ulimit=$(grep '^[[:space:]]*FEATURE_SH_ULIMIT[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
11 feature_setvar=$(grep '^[[:space:]]*FEATURE_SH_SETVAR[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
12 feature_wordexp=$(grep '^[[:space:]]*FEATURE_SH_WORDEXP[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
13+ feature_easter=$(grep '^[[:space:]]*FEATURE_SH_EASTER[[:space:]]*=' ../../config.mk | cut -d= -f2 | tr -d '[:space:]')
14 fi
15
16 exec > builtins.c
17@@ -72,6 +74,7 @@ awk '/^[^#]/ {
18 if ($0 ~ /#FEATURE_SH_ULIMIT/ && '$feature_ulimit' != 1) next;
19 if ($0 ~ /#FEATURE_SH_SETVAR/ && '$feature_setvar' != 1) next;
20 if ($0 ~ /#FEATURE_SH_WORDEXP/ && '$feature_wordexp' != 1) next;
21+ if ($0 ~ /#FEATURE_SH_EASTER/ && '$feature_easter' != 1) next;
22
23 if ('$havejobs' || $2 != "-j") {
24 sub(/#[A-Z0-9_]*/, "");
+1,
-0
1@@ -223,6 +223,7 @@ FEATURE_STAT_FORMAT = 1
2 FEATURE_SORT_BIG = 1
3 FEATURE_SORT_STABLE = 1
4 FEATURE_OD_ENDIAN = 1
5+FEATURE_SH_EASTER = 1
6 FEATURE_SH_HISTEDIT = 1
7 FEATURE_SH_LOCAL = 1
8 FEATURE_SH_LET = 1