// (c) 2008 Sebastian Redl
// Released under the GPLv2.
#define BOOST_DISABLE_THREADS
#include <ostream>
#include <iostream>
#include <string>
#include <algorithm>
#include <utility>
#include <tr1/unordered_map>
#include <boost/program_options.hpp>
#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/filesystem.hpp>
namespace po = boost::program_options;
namespace ip = boost::interprocess;
namespace fs = boost::filesystem;
// The possible modes of sorting footnotes.
enum sort_mode
{
sort_by_text,
sort_by_footnote
};
// Input operator for the enum.
std::istream &operator >>(std::istream &is, sort_mode &sm)
{
std::string s;
is >> s;
if(s == "text") {
sm = sort_by_text;
} else if(s == "footnote") {
sm = sort_by_footnote;
} else {
is.setstate(std::ios_base::failbit);
}
return is;
}
// Output operator for the enum.
std::ostream &operator <<(std::ostream &os, sort_mode sm)
{
return os << (sm == sort_by_text ? "text" : "footnote");
}
bool parse_command_line(int argc, char **argv,
sort_mode &sm, std::string &input_file);
std::tr1::unordered_map< int, int>
g_footnote_number_translations(10000);
int g_footnote_count = 0;
inline int lookup_footnote_translation(int number)
{
int &r = g_footnote_number_translations[number];
if(r == 0) {
r = ++g_footnote_count;
}
return r;
}
inline bool isdig(char c)
{
return c >= '0' && c <= '9';
}
void load_footnote_order(const char *first, const char *last);
void copy_text(const char *first, const char *last, std::ostream &os);
void sort_footnotes(const char *first, const char *last, std::ostream &os);
int main(int argc, char **argv)
{
std::ios::sync_with_stdio(false);
sort_mode sm = sort_by_text;
std::string input_file;
if(!parse_command_line(argc, argv, sm, input_file)) {
return 1;
}
// Memory-map the input file.
ip::file_mapping input_map(input_file.c_str(), ip::read_only);
std::size_t input_size = fs::file_size(fs::path(input_file));
ip::mapped_region mapping(input_map, ip::read_only, 0, input_size);
const char *text_start = static_cast<const char*>(mapping.get_address());
const char *footnote_end = text_start + input_size;
// Locate the footnote marker. Search in reverse, since it's probably close
// to the end.
const char * const MARKER_START = "\n@footnote:\n";
const char * const MARKER_END = MARKER_START + 12;
const char* loc = std::find_end(text_start, footnote_end,
MARKER_START, MARKER_END);
if(loc == footnote_end) {
throw std::runtime_error("No footnote section.");
}
// Include the last newline.
const char *text_end = loc + 1;
const char *footnote_start = loc + 12;
if(sm == sort_by_footnote) {
load_footnote_order(footnote_start, footnote_end);
}
copy_text(text_start, text_end, std::cout);
std::cout << "@footnote:\n";
sort_footnotes(footnote_start, footnote_end, std::cout);
}
bool parse_command_line(int argc, char **argv,
sort_mode &sm, std::string &input_file)
{
po::options_description visible_desc("Allowed options");
visible_desc.add_options()
("help,h", "produce help message")
("sort-by,s", po::value<sort_mode>(&sm)->default_value(sort_by_text),
"sort by order of in-text appearance ('text') or in-footnotes "
"appearance ('footnote')")
;
po::options_description hidden_desc("positional options");
hidden_desc.add_options()("input", po::value<std::string>(&input_file),
"input file (must be real file)");
po::positional_options_description positional;
positional.add("input", 1);
po::options_description merged("all options");
merged.add(visible_desc).add(hidden_desc);
po::variables_map vm;
try {
po::store(
po::command_line_parser(argc, argv)
.options(merged).positional(positional).run(),
vm);
po::notify(vm);
} catch(po::error &e) {
std::cerr
<< "Command line error: " << e.what() << "\n\n"
<< "Usage: babylon [<options>] <input-file>\n"
<< visible_desc
<< '\n';
return false;
}
if(vm.count("help")) {
std::cerr
<< "Babylon Challenge in C++, Sebastian Redl "
"<
[email protected]>\n"
<< "Usage: babylon [<options>] <input-file>\n"
<< visible_desc
<< '\n';
return false;
}
return true;
}
void load_footnote_order(const char *first, const char *last)
{
for( ; first != last; ++first) {
if(*first == '[') {
int num = 0;
for(++first; first != last && isdig(*first); ++first) {
num *= 10;
num += *first - '0';
}
if(first == last) {
break;
}
if(*first == ']' && num != 0) {
lookup_footnote_translation(num);
}
}
}
}
void copy_text(const char *first, const char *last, std::ostream &os)
{
const char *last_end = first;
for( ; first != last; ++first) {
if(*first == '[') {
int num = 0;
const char *f = first + 1;
for( ; f != last && isdig(*f); ++f) {
num *= 10;
num += *f - '0';
}
if(f == last) {
break;
}
if(*f == ']' && num != 0) {
os.write(last_end, first - last_end + 1);
os << lookup_footnote_translation(num);
last_end = first = f;
}
}
}
os.write(last_end, last - last_end);
}
void sort_footnotes(const char *first, const char *last, std::ostream &os)
{
typedef std::pair<const char*, const char*> txtrange;
typedef std::vector<txtrange> txtmap;
txtmap order(g_footnote_count, txtrange(0, 0));
for( ; first != last ; ++first) {
if(*first == '[') {
int num = 0;
++first;
for( ; first != last && isdig(*first); ++first) {
num *= 10;
num += *first - '0';
}
if(first == last) {
break;
}
const char *fnbegin = first;
for( ; first != last && *first != '\n'; ++first) {
}
int idx = lookup_footnote_translation(num);
order[idx-1] = txtrange(fnbegin, first);
if(first == last) {
std::cerr << "Warning: File doesn't end with a newline.\n";
break;
}
} else {
//std::cerr << "Warning: Bad '" << *first << "' in footnotes.\n";
}
}
for(std::size_t i = 0, len = order.size(); i < len; ++i) {
const txtrange &r = order[i];
if(r.first == 0) {
os << '[' << (i+1) << "] Error: No footnote text found.\n";
std::cerr << "Warning: No footnote for reference " << (i+1) << '\n';
continue;
}
os << '[' << (i+1);
os.write(r.first, r.second - r.first);
os << '\n';
}
}