/* * main.cpp * * Created on: 07.10.2017 * Author: julian */ #include // for mmap() #include // for fstat() #include // for open(), O_RDONLY #include // for close() #include // for perror() #include #include #include #include #include #include #include #include "fs.hpp" #include "memory_string.hpp" using namespace std; class InputExtractor { public: class Exception : public std::runtime_error { public: Exception(const std::string &str) : std::runtime_error(str) {} }; typedef std::vector List; List operator()(const Path::Path &file, const MemoryString &str); std::string macroExpand(const std::string &input); List Include(Path::Path); protected: std::map macros; std::set includes; }; #include template std::string readTill(iterator &start, const iterator &end, std::function limiter) { iterator current = start; while(current != end && !limiter(current)) { ++current; } return std::string(start, current); } template std::string readBrackets(iterator ¤t, const iterator &end, const char * brackets) { if (current == end || *current != brackets[0]) { std::cout << brackets[0] << "!=" << *current; return std::string(); } // skip first opening bracket current++; int depth = 1; auto bbegin = current, bend = current; while(depth > 0 && current != end) { if (*current== brackets[0]) { depth++; } else if (*current == brackets[1]) { depth--; } else { bend = ++current; } } // advance beyond last bracket if (current != end) current++; return std::string(bbegin, bend); } std::string InputExtractor::macroExpand(const std::string &input) { std::string result; std::map::iterator lookup; //cout << "expanding: " << input << endl; std::string::const_iterator current = input.begin(); while(current != input.end()) { if (*current == '\\') { current++; std::string::const_iterator start = current; while(current != input.end() && *current != '\\') { if ((lookup = macros.find(std::string(start, current))) != macros.end()) { break; } } if (lookup == macros.end()) { throw Exception("unknown macro in macro expansion: " + std::string(start, current)); } result += lookup->second; } else { result += *current; } ++current; } return result; } typedef std::map> CommandList; InputExtractor::List InputExtractor::Include(Path::Path path) { path = Path::Clean(path); List list; std::cout << "including file " << path << "..."; // look for include if (includes.find(path) != includes.end()) { cout << "SKIP" << endl; return list; // already been there } cout << endl; // add to include list includes.insert(path); int fd = open(path.c_str(), O_RDONLY); if (fd == -1) { cerr << "cannot open " << path << endl; return list; } struct stat fileinfo; if (fstat(fd, &fileinfo) == -1) { perror("stat"); close(fd); return list; } void * memptr = mmap(NULL, fileinfo.st_size, PROT_READ, MAP_SHARED, fd, 0); if (memptr == NULL) { perror("mmap"); close(fd); return list; } // create iteratable memory object MemoryString file((char*)memptr, fileinfo.st_size); std::string basedir = Path::Dir(path); // evaluate memory region (aka the file) list = (*this)(basedir, file); // cleanup munmap(memptr, fileinfo.st_size); close(fd); return list; cout << path << "done" << endl; } // make a relative filepath absolute using the root filename // and/or the process working directory Path::Path fixFilename(const Path::Path &file, const Path::Path &root) { Path::Path temp(file); // try to make path absolute if (Path::isRelative(temp)) { // add current file's path temp = Path::Join(root, temp); } if (Path::isRelative(temp)) { // add process working directory temp = Path::Join(fs::cwd(), temp); } return Path::Clean(temp); } // evaluate a tex file searching for input statements InputExtractor::List InputExtractor::operator()(const Path::Path &file, const MemoryString &str){ List result; CommandList IncludeCommands; IncludeCommands["input"] = [&file, this](List &l, std::string a) { if (a.empty()) return; if (Path::Extension(a) != ".tex") a += ".tex"; a = fixFilename(a, file); l.push_back(a); // try to extract all inputs of that file auto sub = Include(a); if (!sub.empty()) std::copy(sub.begin(), sub.end(), std::inserter(l, l.end())); }; IncludeCommands["include"] = IncludeCommands["input"]; IncludeCommands["lstinputlisting"] = [file](List &l, std::string a){ if (!a.empty()) { l.push_back(fixFilename(a, file)); } }; MemoryString::const_iterator current = str.begin(); while(current != str.end()) { if (*current == '%') { // line commment while(current != str.end() && *current != '\n') current++; continue; } if (*current != '\\') { // skip non macros current++; continue; } // read macro name current++; if (current == str.end()) throw Exception("unexpected EOF"); auto start = current, end = current; auto limiter = [](char c) -> bool { return isspace(c) || c == '{' || c == '\\'; }; for(; current != str.end() && !limiter(*current); end = (++current+1)) { auto searchHit = IncludeCommands.find(std::string(start, end)); if (searchHit != IncludeCommands.end()) { // handle crosslink current++; if (current == str.end()) continue; cout << searchHit->first << "[" << readBrackets(current, str.end(), "[]") << "]"; auto inner = readBrackets(current, str.end(), "{}"); cout << ":" << inner << endl; // add to results searchHit->second(result, macroExpand(inner)); break; } else if (std::string(start, end) == std::string("def")) { // define new macro current++; if (current == str.end() || *current != '\\') { continue; } current++; std::function limiter = [](const MemoryString::const_iterator &it) -> bool { return std::string("{ \t\n").find(*it) != std::string::npos; }; std::string name = readTill(current, str.end(), limiter); cout << "new macro definition: " << name << endl; macros.insert(std::pair(name, readBrackets(current, str.end(), "{}"))); break; } } } return result; } struct Config { std::string targetName; Path::Path outfilePath; bool outfileOverride; } config ; std::ostream &writeTarget(std::ostream &stream, const std::string &target_name) { stream << target_name << ":"; return stream; } std::ostream &writeTargetDepends(std::ostream &stream, const Path::Path &path) { stream << path << "\\\n"; return stream; } int openOutfile(std::ofstream &stream) { cout << "writing dependency rules to " << config.outfilePath << endl; stream.close(); stream.open(config.outfilePath); if (!stream) { cerr << "could not write to " << config.outfilePath << endl; return 0; } if (!config.targetName.empty()) { writeTarget(stream, config.targetName); } return 1; } #include struct option long_opts[] = { {"output", required_argument, NULL, 'o'}, {"target", required_argument, NULL, 't'}, {}, // terminator }; void help(int argc, char ** args) { cout << args[0] << " [-o output] files..." << endl; cout << "--output" << endl; cout << "-o\tby default the program will create a depfile for every input" << endl; cout << "\tnaming it like the tex file with a .d extension. By giving the -o Option" << endl; cout << "\tthe output fill instead be put in this file exclusivly" << endl; } int main(int argc, char ** args) { // find all the files the given tex files depend on int fd = 0; struct stat filestat; int long_index = 0; int opt = 0; while((opt = getopt_long(argc, args, "o:t:", long_opts, &long_index)) != -1) { switch(opt) { case 'o': config.outfilePath = optarg; config.outfileOverride = true; break; case 't': config.targetName = optarg; break; case '?': help(argc, args); return 0; } } std::ofstream outfile; if (!config.outfilePath.empty() && !openOutfile(outfile)) { return -1; // failed to open outfile } // scan remaining arguments as input filenames for(; optind < argc; optind++) { Path::Path filename = args[optind]; InputExtractor parser; // parse file InputExtractor::List list = parser.Include(filename); // output results in makefile rule style // check for open outfile if (!config.outfileOverride) { config.outfilePath = Path::Basename(filename) + ".d"; if (!openOutfile(outfile)) { continue; // just skip this file } } // check for target override if (config.targetName.empty()) { config.targetName = filename; } for (auto it = list.begin(); it != list.end(); it++) { writeTargetDepends(outfile, *it); } // add newline for cleanness outfile << endl; cout << "done" << endl; } }