// // JPC. Fill and justify prefix comments. // // Copyright (C) 2005 James Moen. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation; either version 2 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., 59 // Temple Place, Suite 330, Boston, MA 02111-1307 USA. // // // The JPC command looks like this. // // jpc -l L -p P F1 F2 ... // // where L is the length of justified comment lines, P is the comment prefix, // and the F's are names of files to have their comments justified in place. L // and P take on default values if their corresponding options do not appear. // If no F's appear, JPC reads from STDIN and writes to STDOUT. // // Known bugs: comments inside comments may cause trouble. Also, the algorithm // used for filling and justification is very simple-minded. // // If you have questions or comments about JPC, write to moen@augsburg.edu. // #include #include #include #include #define blankChar ' ' // You guessed it. #define dashChar '-' // Right. #define defaultLength 79 // Length of comment lines. #define defaultPrefix "// " // Comment prefix for C++, GCC, Java. #define eofChar EOF // End of file. #define eolChar '\n' // End of line. #define eosChar '\0' // End of string. #define false 0x00 // Fake Boolean FALSE. #define nil NULL // The null pointer. #define true 0xFF // Fake Boolean TRUE. typedef char bool; // A fake Boolean type. typedef char *refChar; // Ref to CHAR. typedef char **refRefChar; // Ref to ref to CHAR. typedef FILE *refFile; // Ref to file. char ch; // Most recent read/written character. int blankWidth; // Number of spaces in a blank. bool direction; // Add blanks L-to-R or R-to-L? refChar line; // Queue up words to be justified. int lineBlanks; // Count blanks in LINE. refChar lineEnd; // Enqueue characters to LINE here. int lineLength = defaultLength; // Length of comment line. refChar lineStart; // Dequeue characters from LINE here. int longBlanks; // 1st LONG BLANKS blanks are longer. refChar prefix = defaultPrefix; // Comment prefix, from command line. int prefixLength; // Length of PREFIX. refChar prefixStart; // Dequeue characters from PREFIX here. int remainingChars; // Chars in LINE to be filled. refChar self; // Name of this program. int shortBlanks; // 1st SHORT BLANKS blanks are shorter. refFile source; // Current source file. refFile temp; // Scratch file. refChar word; // Queue up characters into words. refChar wordEnd; // Enqueue characters to WORD here. refChar wordStart; // Dequeue characters from WORD here. // WRITE BLANKS. Write COUNT blanks to TEMP. void writeBlanks(int count) { while (count > 0) { putc(blankChar, temp); count -= 1; }} // COPY SOURCE TO TEMP. Copy SOURCE to TEMP, all the while recognizing comment // lines and justifying them. We read a prefix from each SOURCE line into WORD // and test if WORD equals PREFIX. If it does, and PREFIX isn't followed by a // blank, we have a comment. // // If we have a comment, we repeatedly read WORDs from SOURCE and queue them // in LINE, separating them by blanks. When LINE gets full, we justify it // either by making the blanks on the left longer (if DIRECTION is true) or // else by making the blanks on the right longer (if DIRECTION is false), and // we alternate these two strategies to keep blank areas from building up on // one side or the other. // // If we don't have a comment, we first empty out LINE without justifying it, // (it may contain a line we were working on before), then empty out WORD (it // contains the prefix from our current line). Finally we copy the SOURCE line // without justifying that, either. // // We keep going in this way until we've read every line in SOURCE. When that // happens, we also must empty out LINE (it may contain a line we were working // on before). void copySourceToTemp() { ch = getc(source); direction = false; lineBlanks = 0; lineEnd = line; while (ch != eofChar) { prefixStart = prefix; wordEnd = word; while (ch != eofChar && ch != eolChar && wordEnd < word + prefixLength) { if (ch == *prefixStart) { *wordEnd = ch; wordEnd += 1; prefixStart += 1; ch = getc(source); } else { break; }} if (ch != blankChar && *prefixStart == eosChar) { while (ch != eofChar && ch != eolChar) { while (ch == blankChar) { ch = getc(source); } wordEnd = word; while (ch != eofChar && ch != eolChar && ch != blankChar) { *wordEnd = ch; wordEnd += 1; ch = getc(source); } if ((wordEnd - word) + (lineEnd - line) > lineLength) { lineStart = line; lineEnd -= 1; lineBlanks -= 1; remainingChars = lineLength - (lineEnd - line); blankWidth = 1 + remainingChars / lineBlanks; longBlanks = remainingChars % lineBlanks; shortBlanks = lineBlanks - longBlanks; fputs(prefix, temp); direction = ! direction; if (direction) { while (lineStart < lineEnd) { if (*lineStart == blankChar) { writeBlanks(blankWidth); if (longBlanks > 0) { putc(blankChar, temp); longBlanks -= 1; }} else { putc(*lineStart, temp); } lineStart += 1; }} else { while (lineStart < lineEnd) { if (*lineStart == blankChar) { writeBlanks(blankWidth); if (shortBlanks > 0) { shortBlanks -= 1; } else { putc(blankChar, temp); }} else { putc(*lineStart, temp); } lineStart += 1; }} putc(eolChar, temp); lineEnd = line; lineBlanks = 0; } wordStart = word; if (wordStart < wordEnd) { while (wordStart < wordEnd) { *lineEnd = *wordStart; lineEnd += 1; wordStart += 1; } *lineEnd = blankChar; lineEnd += 1; lineBlanks += 1; }}} else { if (lineEnd > line) { fputs(prefix, temp); lineStart = line; lineEnd -= 1; while (lineStart < lineEnd) { putc(*lineStart, temp); lineStart += 1; } putc(eolChar, temp); lineEnd = line; lineBlanks = 0; } wordStart = word; while (wordStart < wordEnd) { putc(*wordStart, temp); wordStart += 1; } while (ch != eofChar && ch != eolChar) { putc(ch, temp); ch = getc(source); } putc(eolChar, temp); } if (ch != eofChar) { ch = getc(source); }} if (lineEnd > line) { fputs(prefix, temp); lineStart = line; lineEnd -= 1; while (lineStart < lineEnd) { putc(*lineStart, temp); lineStart += 1; } putc(eolChar, temp); }} // COPY TEMP TO SOURCE. Simply copy TEMP (which holds the justified version of // SOURCE) back into SOURCE. void copyTempToSource() { ch = getc(temp); while (ch != eofChar) { putc(ch, source); ch = getc(temp); }} // IS DIGITAL. Test if STRING is a string of digits. bool isDigital(refChar string) { while (*string != eosChar) { if (isdigit(*string)) { string += 1; } else { return false; }} return true; } // MAIN. Parse command line options and allocate buffers. Then justify all the // files named on the command line. If no files were named, then justify STDIN // and write the results to STDOUT. int main(int argc, refRefChar argv) { self = *argv; argc -= 1; argv += 1; while (argc > 0 && **argv == dashChar) { if (strcmp(*argv, "-l") == 0) { if (argc > 1) { argc -= 1; argv += 1; if (isDigital(*argv)) { lineLength = atoi(*argv); } else { fprintf(stderr, "%s: invalid line length %s\n", self, *argv); return 1; } argc -= 1; argv += 1; } else { fprintf(stderr, "%s: missing line length\n", self); return 1; }} else if (strcmp(*argv, "-p") == 0) { if (argc > 1) { argc -= 1; argv += 1; prefix = *argv; argc -= 1; argv += 1; } else { fprintf(stderr, "%s: missing comment prefix\n", self); return 1; }} else { fprintf(stderr, "%s: invalid option %s\n", self, *argv); return 1; }} prefixLength = strlen(prefix); lineLength -= prefixLength; if (lineLength > 1) { line = malloc(lineLength); word = malloc(lineLength); } else { fprintf(stderr, "%s: lines are too short\n", self); return 1; } if (argc > 0) { while (argc > 0) { source = fopen(*argv, "r"); if (source == nil) { fprintf(stderr, "%s: cannot open %s\n", self, *argv); return 1; } temp = tmpfile(); if (temp == nil) { fprintf(stderr, "%s: cannot open temp file\n", self); return 1; } copySourceToTemp(); fclose(source); source = fopen(*argv, "w"); if (source == nil) { fprintf(stderr, "%s: cannot open %s\n", self, *argv); return 1; } rewind(temp); copyTempToSource(); fclose(source); fclose(temp); argc -= 1; argv += 1; }} else { source = stdin; temp = tmpfile(); if (temp == nil) { fprintf(stderr, "%s: cannot open temp file\n", self); return 1; } copySourceToTemp(); source = stdout; rewind(temp); copyTempToSource(); fclose(temp); } return 0; }