texdepends/main.cpp

475 lines
9.5 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>
using namespace std;
struct Substring {
const char * start, * end;
Substring(const char * start, const char * end) :
start(start), end(end) {}
Substring(const char * str):
start(str), end(str + strlen(str))
{}
Substring():
start(nullptr), end(nullptr)
{}
std::string toString() const {
std::string temp;
temp.reserve(size());
const char * it = start;
while(it != end) {
temp += *it;
it++;
}
return temp;
}
operator std::string() {
return toString();
}
std::string::size_type size() const {
return (std::string::size_type)(end - start);
}
friend std::ostream &operator<<(std::ostream &out, const Substring &str);
template <class IterA, class IterB>
int __compare(IterA a, IterA aend, IterB b, IterB bend) const {
while(a != aend && b != bend) {
if (*a < * b) {
return -1;
}
if (*a > *b) {
return 1;
}
a++;
b++;
}
if (a == aend && b == bend) {
return 0;
}
if (a != aend) {
return 1;
}
return -1;
}
int compare(const Substring &other) const {
return __compare(start, end, other.start, other.end);
}
int compare(const std::string &other) const {
return __compare(start, end, other.begin(), other.end());
}
template <typename T>
bool operator<(const T &other) const {
return compare(other) < 0;
}
template <typename T>
bool operator>(const T &other) const {
return compare(other) > 0;
}
template <typename T>
bool operator==(const T &other) const {
return compare(other) == 0;
}
template <typename T>
bool operator!=(const T &other) const {
return !(*this == other);
}
};
std::ostream &operator<<(std::ostream &out, const Substring &str) {
auto temp = str.start;
while(temp != str.end) {
out << *temp;
temp++;
}
return out;
}
class InputExtractor
{
public:
class Exception : public std::runtime_error {
public:
Exception(const std::string &str) : std::runtime_error(str) {}
};
typedef std::vector<std::string> List;
List operator()(const Substring &input);
std::string macroExpand(Substring input);
protected:
Substring file;
std::map<std::string, Substring> macros;
};
Substring readBrackets(Substring &input, const char * brackets) {
if (input.size() <= 0 || *input.start != brackets[0]) {
cout << "expected " << brackets[0] << ", got: '" << *input.start << "'";
return Substring();
}
input.start++;
int depth = 1;
Substring result(input.start, input.start);
while(depth > 0 && input.size() > 0) {
result.end = ++input.start;
if (*input.start == brackets[0]) {
depth++;
}
if (*input.start == brackets[1]) {
depth--;
}
}
// advance beyond last bracket
if (input.size())
input.start++;
return result;
}
std::string InputExtractor::macroExpand(Substring input) {
std::string result;
Substring name;
std::map<std::string, Substring>::iterator lookup;
//cout << "expanding: " << input << endl;
while(input.size() > 0) {
if (*input.start == '\\') {
input.start++;
name.start = name.end = input.start;
while(input.size() > 0) {
name.end = ++input.start;
if ((lookup = macros.find(name.toString())) != macros.end()) {
break;
}
}
if (lookup == macros.end()) {
throw Exception("unknown macro in macro expansion: " + name.toString());
}
result += lookup->second.toString();
} else {
result += *input.start;
}
input.start++;
}
return result;
}
#include <functional>
typedef std::map<std::string, std::function<void(InputExtractor::List&, std::string)>> CommandList;
std::string Extension(std::string str) {
auto it = str.end();
while(it != str.begin() && *it != '.' && *it != '/' && *it != '\\') {
it--;
}
return std::string(it, str.end());
}
std::string Basedir(std::string path) {
auto it = path.end();
while(it != path.begin() && *it != '/' && *it != '\\') {
it--;
}
return std::string(path.begin(), it);
}
std::string Name(std::string path) {
auto it = path.end();
while(it != path.begin() && *it != '/' && *it != '\\') {
it--;
}
return std::string(it, path.end());
}
std::string Basename(std::string path) {
std::string temp = Name(path);
auto it = temp.end();
while(it != temp.begin() && *it != '.') {
it--;
}
if (it == temp.begin()) {
return path;
}
return std::string(temp.begin(), it);
}
inline bool PathRelative(std::string path) {
return path.size() && path[0] != '/';
}
#include <unistd.h>
std::string cwd() {
ssize_t size = 100, nsize;
while(1) {
char buffer[size];
if ((nsize = readlink("/proc/self/cwd", buffer, size)) < size) {
buffer[size+1] = 0;
return std::string(buffer);
}
size = nsize + 100;
}
//
// buffer[rsize+1] = 0;
//
// std::string result;
// result.assign(buffer);
// return result;
}
#include <fstream>
bool Exists(std::string path) {
std::ifstream file(path);
if (!file) {
return false;
}
file.close();
return true;
}
InputExtractor::List Include(std::string path) {
InputExtractor::List list;
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);
std::string basedir = Basedir(path);
list = InputExtractor()(str);
// add basedir to list for all relative paths
for (auto it = list.begin(); it != list.end(); it++) {
if (PathRelative(*it))
*it = basedir + '/' + *it;
}
// cleanup
munmap(memptr, fileinfo.st_size);
close(fd);
return list;
}
InputExtractor::List InputExtractor::operator()(const Substring &input){
file = input;
List result;
CommandList IncludeCommands;
IncludeCommands["input"] = [](List &l, std::string a) {
if (a.empty()) return;
if (Extension(a) != ".tex") a += ".tex";
l.push_back(a);
// try to extract all inputs of that file
auto sub = Include(a);
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); };
// skip normie text
while(file.size()) {
if (*file.start == '%') {
// line commment
while(file.size() > 0 && *file.start != '\n')
file.start++;
continue;
}
if (*file.start != '\\') {
file.start++;
continue;
}
// read macro name
file.start++;
// TODO: throw exception
if (!file.size())
throw Exception("unexpected EOF");
Substring name;
name.start = name.end = file.start;
for(name.end = ++file.start; file.size() > 0 && !isspace(*file.start) && *file.start != '{' && *file.start != '\\'; name.end = (++file.start+1)) {
auto searchHit = IncludeCommands.find(name);
if ((searchHit = IncludeCommands.find(name)) != IncludeCommands.end()) {
file.start++;
cout << searchHit->first;
cout << "[" << readBrackets(file, "[]") << "]";
auto args = readBrackets(file, "{}");
cout << ":" << args << endl;
searchHit->second(result, macroExpand(args));
break;
} else if (name == std::string("def")) {
file.start++;
if (file.size() <= 0 || *file.start != '\\') {
continue;
}
file.start++;
Substring name(file.start, file.end);
while (file.size() > 0 && *file.start != '{' && !isspace(*file.start)) {
name.end = ++file.start;
}
cout << "new macro definition: " << name << endl;
macros.insert(std::pair<std::string, Substring>(name.toString(), readBrackets(file, "{}")));
break;
}
}
}
return result;
}
#include <experimental/filesystem>
int main(int argc, char ** args) {
// find all the files the given tex files depend on
cout << cwd() << std::endl;
int fd = 0;
struct stat filestat;
for(;argc > 1; --argc) {
char * filename = args[argc-1];
cout << "looking at " << filename << std::endl;
// 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 << "mmap success, parse file" << endl;
Substring file((const char*)memory_area, (const char*)memory_area + filestat.st_size);
try {
auto list = InputExtractor()(file);
// write in makefile style
std::ofstream output(Basename(filename) + ".d");
if (!output) {
std::cout << "could not create output file" << std::endl;
} else {
output << filename << ": ";
for (auto it = list.begin(); it != list.end(); it++) {
output << '\t';
if (PathRelative(*it)) {
if (PathRelative(filename)) {
output << cwd();
} else {
output << Basename(filename);
}
output << "/" << *it;
} else {
output << *it;
}
output << "\t\\\n";
}
output << endl;
}
output.close();
} catch(InputExtractor::Exception &e) {
cout << e.what() << endl;
}
// cleanup
munmap(memory_area, filestat.st_size);
close(fd);
}
printf("done\n");
}