texdepends/main.cpp

383 lines
8.4 KiB
C++

/*
* main.cpp
*
* Created on: 07.10.2017
* Author: julian
*/
#include <sys/mman.h> // for mmap()
#include <sys/stat.h> // for fstat()
#include <fcntl.h> // for open()
#include <unistd.h> // for close()
#include <errno.h> // for perror()
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include <map>
#include <set>
#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<Path::Path> List;
List operator()(const Path::Path &file, const MemoryString &str);
std::string macroExpand(const std::string &input);
List Include(Path::Path);
protected:
std::map<std::string, std::string> macros;
std::set<Path::Path> includes;
};
#include <functional>
template <typename iterator>
std::string readTill(iterator &start, const iterator &end, std::function<bool(const iterator&)> limiter) {
while(start != end && !limiter(start)) {
++start;
}
return std::string(start, end);
}
template <typename iterator>
std::string readBrackets(iterator &begin, const iterator &end, const char * brackets) {
auto current = begin;
if (begin == end || *current != brackets[0]) {
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<std::string, std::string>::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;
}
#include <functional>
typedef std::map<std::string, std::function<void(InputExtractor::List&, std::string)>> CommandList;
#include <unistd.h>
#include <fstream>
bool Exists(std::string path) {
std::ifstream file(path);
if (!file) {
return false;
}
file.close();
return true;
}
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;
}
// Substring str((const char *)memptr, (const char*)memptr + fileinfo.st_size);
MemoryString file((char*)memptr, fileinfo.st_size);
std::string basedir = Path::Dir(path);
list = (*this)(basedir, file); // follow include
// cleanup
munmap(memptr, fileinfo.st_size);
close(fd);
return list;
cout << path << "done" << endl;
}
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";
// try to make path absolute
if (Path::isRelative(a)) {
// add current file's path
a = Path::Join(file, a);
}
if (Path::isRelative(a)) {
// add process working directory
a = Path::Join(fs::cwd(), a);
}
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"] = [](List &l, std::string a){ l.push_back(a); };
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<bool(const MemoryString::const_iterator&)> 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<std::string, std::string>(name, readBrackets(current, str.end(), "{}")));
break;
}
}
}
return result;
}
int main(int argc, char ** args) {
// find all the files the given tex files depend on
int fd = 0;
struct stat filestat;
for(;argc > 1; --argc) {
Path::Path filename = args[argc-1];
InputExtractor parser;
InputExtractor::List list = parser.Include(filename);
// output results in makefile rule style
Path::Path outfile_name = Path::Basename(filename) + ".d";
cout << "writing dependecy rules to " << outfile_name << "...";
std::ofstream outfile(outfile_name);
if (!outfile) {
cout << "could not create file!" << endl;
continue;
}
outfile << filename << ":";
for (auto it = list.begin(); it != list.end(); it++) {
outfile << *it << "\t\\\n";
}
cout << "done" << endl;
/*
char * filename = args[argc-1];
cout << "opening " << filename << "...";
// try to open file
fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("could not open input file");
continue;
}
fstat(fd, &filestat);
//cout << "file size: " << filestat.st_size << endl;
// try to mmap file
void * memory_area = mmap(NULL, filestat.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (memory_area == nullptr) {
perror("could not mmap the input");
continue;
}
cout << "start parsing" << endl;
MemoryString file((char*)memory_area, filestat.st_size);
try {
InputExtractor::List list = InputExtractor()(file);
Path::Path outfilename = Path::Basename(Path::Path(filename)) + ".d";
cout << "writing makedeps file to " << outfilename << "..." << endl;
// write in makefile style
std::ofstream output(outfilename);
if (!output) {
std::cout << "could not create output file" << std::endl;
} else {
output << filename << ": ";
for (auto it = list.begin(); it != list.end(); it++) {
if (Path::isRelative(*it)) {
if (Path::isRelative(filename)) {
*it = Path::Join(fs::cwd(), filename, *it);
} else {
*it = Path::Join(Path::Path(filename), *it);
}
}
cout << "depends: " << *it << endl;
output << '\t' << *it << "\t\\\n";
}
output << endl;
}
cout << filename << done;
} catch(InputExtractor::Exception &e) {
cout << e.what() << endl;
}
// cleanup
munmap(memory_area, filestat.st_size);
close(fd);
*/
}
}