tt / 1.0 / tt.c 290 lines  7.09 kB  Raw
/* tt.c -- tangle to, written by John Ankarström */

#include <stdio.h>

#ifdef _WIN32
#include <shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
#else
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/stat.h>
#endif

#define err(code, string) do { fprintf(stderr, "%s: %s: %s\n", string, strerror(errno)); exit(code); } while (0)
#define die(...) do { fprintf(stderr, __VA_ARGS__); exit(1); } while (0)
#define true 1
#define false 0
#define bool int

#define REFMAX 80

char *code_prefix;
char *doc_prefix;

char *ref; /* current reference */

char **ins; /* insertions */
char **refs; /* references */
int refs_c; /* count */
int refs_s; /* size */

void reference(char *line) {
  int i, j;
  char *ln, **tmp;
  
  ln = line;
  
  /* parse: -> identifier_without_whitespace */

start:
  if (*ln == '\0') return;
  else if (*ln == '-') { ln++; goto arrow; }
  else { ln++; goto start; }
arrow:
  if (*ln != '>') goto start;
  else ln++;
space:
  if (*ln == ' ') { ln++; goto space; }
  
  for (i = 0; i < strlen(ln); i++)
    if (isspace(ln[i])) {
      for (j = i; j < strlen(ln); j++)
        if (!isspace(ln[j])) return;
      break;
    }
  
  ln[i] = '\0';
  
  if (strlen(ln) > REFMAX) {
    fprintf(stderr, "Warning: Truncating identifier exceeding %d characters\n",
      REFMAX);
    ln[REFMAX] = '\0';
  }

  ref = malloc(1 * strlen(ln) * sizeof(ln));
  sprintf(ref, "%s", ln); /* set current reference */
  ref[strlen(ln)] = 0;
  
  for (i = 0; i < refs_c; i++)
    if (strcmp(refs[i], ref) == 0) return;
  
  fprintf(stderr, "New reference: %s\n", ref);
  
  if (++refs_c > refs_s) {
    refs_s += 10;
    tmp = realloc(refs, refs_s * sizeof(char *));
    if (tmp == NULL) err(1, "malloc");
    refs = tmp;
    tmp = realloc(ins, refs_s * sizeof(char *));
    if (tmp == NULL) err(1, "malloc");
    ins = tmp;
    for (i = refs_s - 10; i < refs_s; i++) /* TODO: is this right? */
      ins[i] = NULL;
  }
  
  refs[refs_c-1] = malloc(1 + REFMAX * sizeof(char));
  sprintf(refs[refs_c-1], "%s", ref);
}

bool code(char *line) {
  char *in, *_in; /* current insertion */
  int i, j;
  
  if (ref[0] == '\0') return false;
  if (strlen(code_prefix) > 0)
    if (strncmp(line, code_prefix, strlen(code_prefix)) != 0) return false;
  if (strlen(doc_prefix) > 0)
    if (strncmp(line, doc_prefix, strlen(doc_prefix)) == 0) return false;

  for (i = 0; i < refs_c; i++)
    if (strcmp(refs[i], ref) == 0) break;
  
  if (ins[i] == NULL) {
    ins[i] = malloc(1 + (strlen(line) + 1) * sizeof(char));
    j = 0;
  } else {
    j = strlen(ins[i]);
    _in = realloc(ins[i],
      1 + j * sizeof(char) + (strlen(line) + 1) * sizeof(char));
    if (_in == NULL) err(1, "malloc");
    ins[i] = _in;
  }
  
  strncpy(ins[i] + j, line + strlen(code_prefix),
    strlen(line) - strlen(code_prefix));
  ins[i][j + strlen(line) - strlen(code_prefix)] = '\n';
  ins[i][j + strlen(line) - strlen(code_prefix) + 1] = 0;
  
  return true;
}

int main(int argc, char *argv[]) {
  bool finish, found, iscode, wascode;
  char **a, b, c, *line, *out_prefix, *tangledfile, *tmp;
  FILE *f, *fo;
  int i, j, k;
  int line_l, line_s, offset;
  
  ref = malloc(1 + (REFMAX + 4) * sizeof(char)); /* incl. << and >> */
  if (ref == NULL) err(1, "malloc");
  
  refs_c = 0;
  refs_s = 10;
  refs = malloc(refs_s * sizeof(char *));
  ins = malloc(refs_s * sizeof(char *));
  if (refs == NULL || ins == NULL) err(1, "malloc");
  for (i = 0; i < refs_s; i++)
    ins[i] = NULL;
  
  code_prefix = "    ";
  doc_prefix = "";
  out_prefix = "out/";
  
  for (i = 1; i < argc; i++)
    if (argv[i][0] == '-') {
      switch(argv[i][1]) {
        case 'c':
          code_prefix = argv[i] + 2;
          break;
        case 'd':
          doc_prefix = argv[i] + 2;
          break;
        case 'o':
          out_prefix = argv[i] + 2;
          break;
      }
    } else
      break;

  if (strcmp(code_prefix, doc_prefix) == 0)
    die("code_prefix and doc_prefix cannot be identical\n");
  if (strlen(out_prefix) == 0)
    die("out_prefix cannot be empty\n");
  
  offset = i;
  if (offset == argc) die("usage: %s destination ...\n", argv[0]);
  
  line_l = 0;
  line_s = 100;
  line = malloc(1 + line_s * sizeof(char));
  if (line == NULL) err(1, "malloc");
  
  finish = false;
  iscode = false;
  wascode = false;
  while ((b = getchar()) != EOF) {  
    c = b;
    if (c != '\n') {
      if (line_l + 1 > line_s) {
        line_s += 20;
        tmp = realloc(line, 1 + line_s * sizeof(char));
        if (tmp == NULL) err(1, "malloc");
        line = tmp;
      }
      line[line_l++] = c;
      continue;
    }
    
finish:
    line[line_l] = '\0';
    line_l = 0;
    
    if (strlen(code_prefix) == 0 && !wascode && strcmp(line, "") == 0) {
      continue; /* ignore empty lines after doc text */
    }
    
    iscode = code(line);
    if (!iscode) reference(line);
    
    if (strlen(code_prefix) > 0 && wascode && !iscode) {
      code(code_prefix); /* add extra empty line after code block */
    }
    wascode = iscode;
  }
  if (c != '\n' && !finish) { finish = true; goto finish; }
    
  free(ref);
  
  for (k = offset; k < argc; k++) {
    tangledfile = malloc(
      1 + (strlen(out_prefix) + 50 + sizeof(argv[k])) * sizeof(char) /* ??? */
    );
    if (tangledfile == NULL) err(1, "malloc");
    
    if (sprintf(tangledfile, "%s%s", out_prefix, argv[k]) == -1)
      err(1, "sprintf");
    
    f = fopen(tangledfile, "w");
    if (f == NULL) err(1, "fopen");
    fo = fopen(argv[k], "r");
    if (fo == NULL) err(1, "fopen");
    
    free(tangledfile);
    
    line_l = 0;
    finish = false;
    while ((b = fgetc(fo)) != EOF) {
      c = b;
      if (c != '\n') {
        if (line_l + 1 > line_s) {
          line_s += 20;
          tmp = realloc(line, 1 + line_s * sizeof(char));
          if (tmp == NULL) err(1, "malloc");
          line = tmp;
        }
        line[line_l++] = c;
        continue;
      }

finish2:
      line[line_l] = '\0';
      line_l = 0; /* reset line length count */
      
      if (strncmp(line, "<<", 2) != 0
        || strncmp(line + strlen(line) - 2, ">>", 2) != 0) {
        fprintf(f, "%s\n", line);
        continue;
      }
      
      line += 2;
      line[strlen(line) - 2] = '\0';
      
      for (i = 0; i < strlen(line); i++)
        if (isspace(line[i])) {
          fprintf(f, "%s\n", line);
          continue;
        }
      
      if (strlen(line) > REFMAX)
        fprintf(stderr,
          "Warning: Truncating identifier exceeding %d characters\n", REFMAX);
      
      ref = line;
      
      found = false;
      for (i = 0; i < refs_c; i++)
        if (strcmp(refs[i], ref) == 0) { found = true; break; }
      if (!found) {
        fprintf(stderr, "Unreferenced destination: %s\n", ref);
        continue;
      }
      if (ins[i] == NULL) {
        fprintf(stderr, "Warning: Insertion for %s is empty\n", ref);
        continue;
      }
      if (strncmp(ins[i] + strlen(ins[i]) - 2, "\n\n", 2) == 0)
        ins[i][strlen(ins[i]) - 1] = '\0'; /* remove extra newline */
      fprintf(f, "%s", ins[i]);
    }
    if (c != '\n' && !finish) { finish = true; goto finish2; }
    
    fclose(f);
    fclose(fo);
  }
  
  return 0;
}