// 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]