// smake -- simple make // // Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu> // Filename: smake.cpp // Creation Date: Fri Mar 29 21:43:39 PST 1996 // Last Modified: Fri May 8 16:33:29 PDT 2009 (updated c++ syntax for GCC 4) // Syntax: C++ // $Smake: g++ -O -o %b %f -static -Llib -lmuseinfo % // && strip %b // // Description: See ManPage() function. // #include "Array.h" #include "Options.h" #include <stdlib.h> #include <string.h> #include <ctype.h> #ifndef OLDCPP #include <iostream> #include <fstream> #include <sstream> #define SSTREAM stringstream #define CSTRING str().c_str() using namespace std; #else #include <iostream.h> #include <fstream.h> #ifdef VISUAL #include <strstrea.h> #else #include <strstream.h> #endif #define SSTREAM strstream #define CSTRING str() #endif // command-line option variables: int echoQ = 0; // used with -e option int quietQ = 0; // used with -x option int numSkip =4; // used with -n option const char* commandmarker = "$Smake:"; // used with -c option const char* versionSmake = ""; // used with -v option char MagicString[1024] = {0}; Array<Array<char> > passParams; int numPassParams; char* mybasename; char* filename; // function declarations: int backwardPosition(char, char*); void find(const char*, istream&); void getOptions(void); char nextChar(istream&); void readCommand(SSTREAM&, const char*, istream&); char* unsuffix(char*); void ManPage(void); void checkOptions(Options& opts); int usage(const char* command); //////////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { Options options(argc, argv); checkOptions(options); // find name of file to extract command from: filename = options.getArg(1); ifstream inFile(filename); if (!inFile) { cerr << "Cannot open file: " << filename << endl; exit(1); } mybasename = unsuffix(filename); // determine if any parameters are to be passed: numPassParams = options.getArgCount() - 1; passParams.setSize(numPassParams); int i, len; for (i=0; i<passParams.getSize(); i++) { len = strlen(options.getArg(i+2)); passParams[i].setSize(len+1); strcpy(passParams[i].getBase(), options.getArg(i+2)); } // create the commandstring and run it: SSTREAM strcommand; readCommand(strcommand, MagicString, inFile); if (strlen(strcommand.CSTRING) != 0) { if (!quietQ) { cout << strcommand.CSTRING << endl; } if (!echoQ) { system(strcommand.CSTRING); } } else { cerr << "No embedded command found in " << filename << endl; } if (mybasename != NULL) delete [] mybasename; return 0; } //////////////////////////////////////////////////////////////////////////////// ////////////////////////////// // // checkOptions -- // void checkOptions(Options& opts) { opts.define("c|command-marker=s:$Smake:", "set the default command marker"); opts.define("e|echo=b", "echo embedded command string without executing"); opts.define("q|quiet=b", "execute command w/o printing command to terminal"); opts.define("s|skip=i:4", "skip characters on new line when continuing"); opts.define("v|variant=s:", "run a particular variant of $Smake command"); opts.define("Z|author=b", "print author of program and exit"); opts.define("h|m|help|manpage=b", "print manpage and then exit"); opts.process(); if (opts.getArgCount() < 1) { usage(opts.getCommand()); exit(1); } if (opts.getBoolean("author")) { cerr << "Written by Craig Stuart Sapp" << "<craig@ccrma.stanford.edu>; 1996\n"; exit(1); } if (opts.getBoolean("manpage")) { ManPage(); exit(1); } commandmarker = opts.getString("command-marker"); versionSmake = opts.getString("variant"); echoQ = opts.getBoolean("echo"); quietQ = opts.getBoolean("quiet"); numSkip = opts.getInteger("skip"); strncpy(MagicString, commandmarker, 128); if (strlen(versionSmake) != 0) { int len = strlen(MagicString); if (MagicString[len-1] == ':') { MagicString[len-1] = '\0'; strcat(MagicString, "-"); strcat(MagicString, versionSmake); strcat(MagicString, ":"); } else { strcat(MagicString, versionSmake); } } } ////////////////////////////// // // usage // int usage(const char* command) { cerr << "\nUsage: " << command << " [-s skip][-v variant |-c][-e|-x] input-file [parameters]\n" << " Processes a file according the command string in the file. " << "The command\n" << " string starts with the pattern $Smake: and lasts until the " << "end of the line.\n" << " Symbolic patterns in the command are:\n" << " %f -- replaces with the filename string\n" << " %b -- replaces with the filename base (removes all " << "characters after\n last \".\"" << " of file name and also removes the dot.\n" << "Type " << command << " -h for the man page.\n" << endl; return 0; } ////////////////////////////// // // backwardPosition -- return the position of the last sought // character in the string. // int backwardPosition(char sought, char *string) { int i; for (i = strlen(string) - 1; i>=0; i--) { if (sought == string[i]) { break; } } return i; } ////////////////////////////// // // unsuffix -- remove the .* extension of a filename. // char* unsuffix(char *string) { int dotPosition; char *base; dotPosition = backwardPosition('.', string); if (dotPosition != -1) { base = new char[dotPosition+1]; if (base == NULL) { cerr << "Out of memory." << endl; exit(1); } strcpy(base, string); base[dotPosition] = '\0'; } else { base = new char[1]; base[0] = '\0'; } return base; } ////////////////////////////// // // find -- find a string in an istream, and position the istream after // that string or at the end of the file. // void find(const char* searchString, istream& inFile) { char c; int length = strlen(searchString); int position = 0; while (position < length && inFile.get(c)) { if (c == searchString[position]) position++; else position = 0; } } ////////////////////////////// // // nextChar -- return the next non-space, non-return character in the // (text) file. return '\0' if end-of-file or a return character. // char nextChar(istream& inFile) { char c = '\0'; while (inFile.get(c) && isspace(c)) { if (c == '\n') { c = '\0'; return c; } } return c; } ////////////////////////////// // // readCommand -- read the command string from the file. // void readCommand( SSTREAM& strcommand, const char* commandMarker, istream& inFile) { char c; find(commandMarker, inFile); c = nextChar(inFile); if (c == '\0') return; else strcommand << c; while (inFile.get(c) && c != '\n') { if (c == '%') { inFile.get(c); switch (c) { case '\n': for (int i=0; i<numSkip; i++) { inFile.get(c); if (c == '\n') { cerr << "Error: too many spaces to skip on line" << endl; exit(1); } } while (inFile.get(c) && isspace(c)) { if (c == '\n') return; } strcommand << c; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (numPassParams > (int)c - '0' - 1) { strcommand << passParams[(int)c - '0' - 1].getBase(); } else { strcommand << '%' << c; } break; case 'f': strcommand << filename; break; case 'b': strcommand << mybasename; break; default: strcommand << '%' << c; } } else { strcommand << c; } } } ////////////////////////////// // // ManPage // void ManPage(void) { cout << "\n" "SMAKE(1) UNIX Programmer's Manual SMAKE(1)\n" "\n" "\n" "\n" "NAME\n" " smake - Simple MAKE. Executes a command string embedded in\n" " a file.\n" "\n" "SYNOPSIS\n" " smake [-s skip][-v variant |-c][-e|-x] input-file [parame-\n" " ters]\n" "\n" "DESCRIPTION\n" " Smake searches for the character pattern $Smake: in a file.\n" " When it finds that pattern, smake will execute, as a shell\n" " command, the text following $Smake: to the end of the line.\n" "\n" " There are several symbolic patterns that can be used in the\n" " embedded command:\n" "\n" " %f If this character pattern is in the embedded com-\n" " mand, the input filename passed to smake will be\n" " substituted.\n" "\n" " %F If this character pattern is in the embedded com-\n" " mand, the input filename passed to smake will be\n" " substituted, removing any directory location.\n" "\n" " %b If this character pattern is in the command, the\n" " filename minus the characters after the last period\n" " will be removed, as well as the last period. For\n" " example, the basename of dir/test.c is dir/test .\n" "\n" " %B If this character pattern is in the command, the\n" " filename minus the characters after the last period\n" " will be removed, as well as the last period. For\n" " example, the Basename of dir/test.c is test .\n" "\n" " %<newl> Where <newl> is the newline character. If % comes\n" " at the end of the line with no other characters\n" " except the newline character following it, the\n" " embedded command continues on the following line.\n" " The -s option determines where the command resumes\n" " on the next line.\n" "\n" " %1--%9 If any parameters follow the input-file on the com-\n" " mand line, then these parameters get matched to the\n" " symbols %1, %2, etc. up to %9. If there is no\n" " passed parameter for a particular symbol, then it\n" " is left unchanged in the command.\n" "\n" "OPTIONS\n" " -s skip skip is the number of characters to skip at the\n" " beginning of a new line for the shell command\n" " line-continuation. The default value of skip is 4.\n" " It is an error to skip beyond the end of the line.\n" "\n" " -v variant\n" " variant is a string used to distinguish between\n" " different possible commands to extract. The form\n" " of the marker in the file is $Smake-variant:.\n" "\n" " -e echo the embedded command string without executing\n" " it.\n" "\n" " -x execute the command string only, without printing\n" " it to the terminal as well.\n" "\n" " -c pattern\n" " pattern is a string to replace the default marker\n" " $Smake: with pattern.\n" "\n" "EXAMPLES\n" " A command to compile a c program, and strip the executable:\n" "\n" " // $Smake: cc -O -o %b %f && strip %b\n" "\n" " A command to produce and display a PostScript file from a\n" " TeX file:\n" "\n" " % $Smake: tex %b && dvips -o %b.ps %b && open %b.ps\n" "\n" "FILES\n" " /user/c/craig/lang/c/smake.cc the source code\n" "\n" "BUGS\n" " Cannot use the -v option and the -c option at the same time.\n" << endl; } // md5sum: f27722754f53bfe620cad5b9d2f3b17d smake.cpp [20090511]