EmersonT1

Worksheet Generator

This program was created so that I could create markdown files from a c++ file, inspired by MATLAB's live scripts, but i thought they were a bit bare features, where this supports as many blocks as you like, and making new sections is not necessary. The sode is released under the MIT License To be very meta, this file is the code for the program, processessed by the program

main.cpp

fairly standard #includes

#include <filesystem>
#include <fstream>
#include <iostream>

This code uses these libraries json.hpp and args.hxx

#include <args.hxx>
#include <json.hpp>

just predeclare the function i'm going to use later

void make_worksheet(std::istream &, std::ostream &, nlohmann::json,
                    std::string);

and now for the main function, the first section is just declaring the command line arguments

int main(int argc, char **argv) {
  args::ArgumentParser parser(
      "Program to generate matlab styled worksheets for an arbitary code file",
      "The config json is formatted as follows {'cpp':{"
      "'extensions':['.cpp','.cc'], 'single-comment':['//%', '////'] "
      "}}");

  args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"});
  args::Group input_method(parser, "The method to input the source code from",
                           args::Group::Validators::Xor);

  args::Flag from_stdin(input_method, "stdin",
                        "load the source code from stdin", {'s', "stdin"});

  args::ValueFlag<std::string> from_file(
      input_method, "file", "load the source code from a file", {'f', "file"});

  args::ValueFlag<std::string> language(
      parser, "example", "The language that the source code is in",
      {'l', "lang", "language"});

  args::ValueFlag<std::string> config(parser, "config-file",
                                      "The additional config file to use",
                                      {"config-file"});

  try {
    parser.ParseCLI(argc, argv);
  } catch (const args::Completion &e) {
    std::cout << parser;
    return 0;
  } catch (const args::Help &) {
    std::cout << parser;
    return 0;
  } catch (const args::ParseError &e) {
    std::cerr << parser;
    return 1;
  } catch (const args::ValidationError &e) {
    std::cerr << parser;
    return 1;
  }

A note on configs: The file used is in json format designed to be easily extensible, a simple example solely for c++ is as follows

 {
   "cpp": {
     "extensions": [
       ".cpp",
       ".cc"
     ],
     "single-comment": [
       "//%",
       "////"
     ],
     "multicomment": {
       "/*%": "%*/"
     }
   }
 }

check for any errors in the input

  bool error = false;

  if (from_stdin && !language) {
    std::cerr << "ERROR: if using stdin, language must be specified"
              << std::endl;
    error = true;
  }

  std::string config_path;
  if (config) {
    config_path = args::get(config);
  } else {
    std::cerr << "ERROR: Default config not presently implemented" << std::endl;
    error = true;
  }

Quit if there is an error

  if (error)
    return 1;

Load Config in

  std::ifstream config_file(config_path);
  nlohmann::json j;
  config_file >> j;
  if (!j.is_object()) {
    std::cerr << "ERROR: Invalid Config" << std::endl;
    return 1;
  }

Now to find the language

  std::string language_name;
  if (language) {
    language_name = args::get(language);
  } else {
    std::filesystem::path p(args::get(from_file));
    auto obj = j.get<nlohmann::json::object_t>();
    for (auto &kvp : obj) {
      for (auto a : kvp.second["extensions"]) {
        if (p.extension().string() == a.get<std::string>()) {
          language_name = kvp.first;
          break;
        }
      }
      if (language_name != "")
        break;
    }
  }

if the langauge isn't found or an invalid one specified, error

  if (j.find(language_name) == j.end()) {
    std::cerr << "ERROR: Language '" << language_name
              << "' is not in the config. Valid Languages are:" << std::endl;
    auto obj = j.get<nlohmann::json::object_t>();
    for (auto &kvp : obj) {
      std::cout << "*\t" << kvp.first << std::endl;
    }
    return 1;
  }
  std::cerr << "Language is " << language_name << std::endl;

call make worksheet (output to cout)

  if (from_stdin) {
    make_worksheet(std::cin, std::cout, j, language_name);
  } else {
    std::ifstream file(args::get(from_file));
    if (!file.is_open()) {
      std::cerr << "ERROR: cannot open file" << std::endl;
      return 1;
    }
    make_worksheet(file, std::cout, j, language_name);
  }
  return 0;
}

Worksheet.cpp

fairly standard #includes

#include <filesystem>
#include <fstream>
#include <iostream>

This code uses these libraries json.hpp and args.hxx

#include <args.hxx>
#include <json.hpp>

This function sees if the first nonwhitespace charachters is that string, and will return the whitespace and the prefix in a string;

std::string starts_with_whitespace(std::string main, std::string key) {
  auto pos = main.find(key);
  if (pos == std::string::npos)
    return "";
  if (pos != 0) {
    for (auto c : main.substr(0, pos - 1)) {
      if (c != ' ' && c != '\t')
        return "";
    }
    return main.substr(0, pos) + key;
  } else
    return key;
}

void make_worksheet(std::istream &input, std::ostream &output,
                    nlohmann::json config, std::string lang) {
  std::string line;
  std::getline(input, line);

these strings go at the start and end of a code block (language specified for syntax highlighting)

  std::string start_code_block = "```" + lang;
  std::string end_code_block = "```";

now iterate over string

  bool code_block = false;
  do {

prevent creating empty code blocks when a line is left empty among a comment that uses markdown

    if (line != "" || code_block) {
      std::string prefix;
      bool is_comment = false;

      for (auto c : config[lang]["single-comment"]) {
        prefix = starts_with_whitespace(line, c.get<std::string>());
        if (prefix != "") {
          is_comment = true;
          break;
        }
      }

if changing from comment to not comment, output the blocks

      if (is_comment && code_block)
        output << end_code_block << std::endl;
      if (!is_comment && !code_block)
        output << start_code_block << std::endl;
      code_block = !is_comment;

output line

      output << line.substr(prefix.length()) << std::endl;
    }
  } while (std::getline(input, line));

close off code block if its still open

  if (code_block)
    output << end_code_block << std::endl;
}

The MIT License (MIT)

Copyright © 2019 Peter Taylor (Emersont1)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.