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