gamebook-checker.cpp 6.2 KB
Newer Older
aggsol's avatar
aggsol committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 Copyright (C) 2018  Kim HOANG <foss@aggsol.de>
 This file is part of gamebook-checker.

 xollox is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "CliArguments.hpp"
aggsol's avatar
aggsol committed
19
#include "Graph.hpp"
aggsol's avatar
aggsol committed
20
#include "Writer.hpp"
aggsol's avatar
aggsol committed
21
#include "Parser.hpp"
aggsol's avatar
aggsol committed
22
#include "Shuffle.hpp"
aggsol's avatar
aggsol committed
23
#include "rang.hpp"
aggsol's avatar
aggsol committed
24 25

#include <cassert>
aggsol's avatar
aggsol committed
26
#include <iostream>
aggsol's avatar
aggsol committed
27
#include <sstream>
aggsol's avatar
aggsol committed
28
#include <fstream>
aggsol's avatar
aggsol committed
29 30 31

#include <deque>
#include <map>
aggsol's avatar
aggsol committed
32
#include <numeric>
aggsol's avatar
aggsol committed
33
#include <set>
aggsol's avatar
aggsol committed
34
#include <string>
aggsol's avatar
aggsol committed
35

aggsol's avatar
aggsol committed
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
namespace
{
    void printUsage()
    {
        std::cout << "Usage: gamebook-checker [OPTIONS]\n"
                  << "Check gamebook sections in Asciidoctor format\n"
                  << "Example: gamebook-checker -i sections.adoc\n"
                  << "\n"
                  << "Options:\n"
                  << "  -i, --input <file>      Input file in Asciidoctor format\n"
                  << "  -o, --output <file>     Output file, default is stdout\n"
                  << "  -s, --seed <number>     Shuffle seed, default: 12345\n"
                  << "\n"
                  << "Flags:\n"
                  << "  -d, --dot-file      Create a dot file named graph.dot\n"
                  << "  -h, --help          Show this help text\n"
                  << "  -m, --mix-sections  Shuffle the sections\n"
aggsol's avatar
aggsol committed
53
                  << "  -n, --no-color      Disable colored output\n"
aggsol's avatar
aggsol committed
54 55 56 57 58 59 60 61
                  << "  -v, --verbose       Show additional information\n"
                  << "      --version       Show version\n"


                  << std::endl;
    }
}

aggsol's avatar
aggsol committed
62 63 64
int main(int argc, char* argv[])
{
    bodhi::CliArguments args(argc, argv);
aggsol's avatar
aggsol committed
65 66 67 68 69 70 71 72 73 74 75
    if(args.getOpt("", "version"))
    {
        std::cout << "v0.1.0\n";
        return 0;
    }

    if(args.getOpt("h", "help"))
    {
        printUsage();
        return 0;
    }
aggsol's avatar
aggsol committed
76 77

    auto input = args.getOpt<std::string>("i", "input", "<missing input file>");
aggsol's avatar
aggsol committed
78 79 80
    auto output = args.getOpt<std::string>("o", "output", "stdout");
    auto seed = args.getOpt<unsigned>("s", "seed", 12345);
    auto mix = args.getOpt("m", "mix-sections");
aggsol's avatar
aggsol committed
81
    auto dot = args.getOpt("d", "dot-file");
aggsol's avatar
aggsol committed
82
    auto verbose = args.getOpt("v", "verbose");
aggsol's avatar
aggsol committed
83 84 85 86 87 88
    auto noColor = args.getOpt("n", "no-color");

    if(noColor)
    {
        rang::setControlMode(rang::control::Off);
    }
aggsol's avatar
aggsol committed
89

aggsol's avatar
aggsol committed
90 91
    if(mix && dot)
    {
aggsol's avatar
aggsol committed
92 93 94
        std::cerr << rang::fg::red
        << "Error  : Cannot combine --mix-sections and --dot-file\n"
        << rang::fg::reset;
aggsol's avatar
aggsol committed
95 96 97
        return 101;
    }

aggsol's avatar
aggsol committed
98 99
    std::map<int, bodhi::Section> sections;

aggsol's avatar
aggsol committed
100
    try
aggsol's avatar
aggsol committed
101
    {
aggsol's avatar
aggsol committed
102
        bodhi::Parser parser(input);
aggsol's avatar
aggsol committed
103

aggsol's avatar
aggsol committed
104
        if(parser.parse(sections))
aggsol's avatar
aggsol committed
105
        {
aggsol's avatar
aggsol committed
106
            return 101;
aggsol's avatar
aggsol committed
107
        }
aggsol's avatar
aggsol committed
108

aggsol's avatar
aggsol committed
109
        for(auto& s: sections)
aggsol's avatar
aggsol committed
110
        {
aggsol's avatar
aggsol committed
111
            // Set parents
aggsol's avatar
aggsol committed
112 113
            assert(s.first == s.second.m_number);
            const int parent = s.first;
aggsol's avatar
aggsol committed
114
            for(auto child: s.second.m_children)
aggsol's avatar
aggsol committed
115
            {
aggsol's avatar
aggsol committed
116 117
                auto it = sections.find(child);
                if(it != sections.end())
aggsol's avatar
aggsol committed
118
                {
aggsol's avatar
aggsol committed
119
                    it->second.m_parents.insert(parent);
aggsol's avatar
aggsol committed
120 121 122
                }
                else
                {
aggsol's avatar
aggsol committed
123 124
                    std::cerr << rang::fg::red
                    << "Error  : Missing section: " << child
aggsol's avatar
aggsol committed
125
                    <<  " linked from section: " << parent << "\n"
aggsol's avatar
aggsol committed
126
                    << rang::fg::reset;
aggsol's avatar
aggsol committed
127
                }
aggsol's avatar
aggsol committed
128 129
            }

aggsol's avatar
aggsol committed
130
            // Append empty line for section padding
aggsol's avatar
aggsol committed
131
            if(s.second.m_lines.back() != "")
aggsol's avatar
aggsol committed
132
            {
aggsol's avatar
aggsol committed
133
                s.second.m_lines.push_back("");
aggsol's avatar
aggsol committed
134 135 136
            }
        }

aggsol's avatar
aggsol committed
137 138
        // Check parents
        int last = -1;
aggsol's avatar
aggsol committed
139 140
        for(auto& s: sections)
        {
aggsol's avatar
aggsol committed
141
            if(s.first > 1)
aggsol's avatar
aggsol committed
142
            {
aggsol's avatar
aggsol committed
143
                if(s.first - last != 1)
aggsol's avatar
aggsol committed
144
                {
aggsol's avatar
aggsol committed
145
                    std::cerr << rang::fg::yellow
146
                    << "Warning: Missing section(s) "  << last+1 << " to " << s.first-1 << "\n"
aggsol's avatar
aggsol committed
147
                    << rang::fg::reset;
aggsol's avatar
aggsol committed
148
                }
aggsol's avatar
aggsol committed
149 150

                if(s.second.m_parents.size() == 0)
aggsol's avatar
aggsol committed
151
                {
aggsol's avatar
aggsol committed
152
                    assert(s.first == s.second.m_number);
aggsol's avatar
aggsol committed
153 154 155
                    std::cerr << rang::fg::yellow
                    << "Warning: No reference to section: " << s.first << "\n"
                    << rang::fg::reset;
aggsol's avatar
aggsol committed
156 157
                }
            }
aggsol's avatar
aggsol committed
158 159
            last = s.first;
        }
aggsol's avatar
aggsol committed
160

aggsol's avatar
aggsol committed
161 162 163 164
        if(verbose)
        {
            std::cout << "Line count: " << parser.lineCount() << "\n"
                      << "Number of sections: " << sections.size() << "\n";
aggsol's avatar
aggsol committed
165

aggsol's avatar
aggsol committed
166 167
            for(auto& s: sections)
            {
aggsol's avatar
aggsol committed
168 169 170 171 172
                std::cout << "Section: " << s.second.m_number
                    << "\t classes=" << s.second.m_classes.size()
                    << "\t lines=" << s.second.m_lines.size()
                    << "\t children=" << s.second.m_children.size()
                    << "\t parents=" << s.second.m_parents.size() << "\n";
aggsol's avatar
aggsol committed
173 174
            }
        }
aggsol's avatar
aggsol committed
175

aggsol's avatar
aggsol committed
176
        if(dot)
aggsol's avatar
aggsol committed
177
        {
aggsol's avatar
aggsol committed
178 179 180
            bodhi::Graph graph;
            graph.createDotFile(sections);
            return 0;
aggsol's avatar
aggsol committed
181 182 183 184
        }

        if(mix)
        {
aggsol's avatar
aggsol committed
185 186
            bodhi::Writer writer(verbose);

aggsol's avatar
aggsol committed
187 188 189 190
            auto resultMapping = bodhi::createSectionMapping(sections, seed);

            if(output == "stdout")
            {
aggsol's avatar
aggsol committed
191
                writer.writeSections(sections, resultMapping, std::cout);
aggsol's avatar
aggsol committed
192 193 194 195 196
            }
            else
            {
                std::ofstream mixFile(output);
                if(not mixFile.is_open())
aggsol's avatar
aggsol committed
197
                {
aggsol's avatar
aggsol committed
198
                    throw std::runtime_error("Cannot open output file.");
aggsol's avatar
aggsol committed
199
                }
aggsol's avatar
aggsol committed
200
                writer.writeSections(sections, resultMapping, mixFile);
aggsol's avatar
aggsol committed
201 202 203
            }
        }
    }
aggsol's avatar
aggsol committed
204 205
    catch(const std::exception& ex)
    {
aggsol's avatar
aggsol committed
206 207 208 209
        std::cerr << rang::fg::red
        << ex.what()
        << rang::fg::reset
        << "\n"
aggsol's avatar
aggsol committed
210 211
        << "Try -h/--help\n";
    }
aggsol's avatar
aggsol committed
212 213
    return 0;
}