4
\$\begingroup\$

This is a command-line interface wrap for any application involving conditional logic. I have posted the source here on bitbucket.

Usage:

  1. Myself using as a starting point for a time series processing application for weather data. The command line options allow to jump between a number of unrelated functions, and to populate objects of inputs manually. I can load data into app`s memory via several methods, excel, csv, or populate manually, and then choose between which test to run, i.e. once a weather time series is loaded, I can choose via command-line whether to calculate standard deviation, means. In the posted code I have kept all the mechanisms / classes for doing this.
  2. I think this will be helpful to people learning programming / school projects - it takes away a lot of work when normally starting from a blank file and having to add commonly used functions and interface
    for invoking various functions

Any feedback on the layout of the code would be appreciated, but in particular:

  1. How files are structured
  2. Structure of the Settings singleton and the cli namespace which contains functions to process command-based input from user
  3. Feedback on whether using a DEFINES file as I did is good practice
  4. Whether the way I populate privileges is good practice
  5. Whether it is a good idea to use a shell like this to start any basic project

File Structure:

  • 0_UTILITIES - some basic utility functions which often pop up and are commonly presented in intro to C++ books
  • 1_COMMAND_LINE_INTERFACE - collection of functions which allows to control the interface
  • 2_ACTUAL_APP - actual app code you want to use goes here (some dummy functions populated to illustrate how information collected from user is packaged into objects and passed around the application

Code item 1 - declaration of Settings singleton and associated functions:

struct command_info_container{
    void* container_1;
    void* container_2;
}; // this is an object to pass information between commands user invokes. each next node always knows what to cast the void container to

class command;

class user{
public:
    user(){}
    user(std::string pw):password(pw){}
    user(std::string pw,std::map<std::string,int> cp):password(pw),command_permissions(cp){}
    std::string password;
    std::map<std::string,int> command_permissions;
}; // allows for different user with customized command_permissions objects. The command_permissions matches the text command invoke and allows to remove user's access to certain commands



namespace cli{ /// cli - command line interface. Certain functions which could also be in Settings singleton are implemented here. Not sure if this is the right design practice

void run_authorisation(command_info_container*); /// every function which is potentially packageable into a vector of functions takes the same shape, with an input of command_info_container pointer
void display_welcome_message(command_info_container*);
void display_completion_message(command_info_container*);

void run_command_junction(COMMAND_JUNCTION_KEY key,command_info_container* user_data); /// this pulls out the vector of commands packaged to a particular key. Commands are stored in Singleton Settings
void display_available_commands(COMMAND_JUNCTION_KEY key); /// at each stage, user is presented with a printout of available commands
std::string prompt_for_command(std::string message_to_user_if_different_from_default);
command match_input_text_to_a_command(std::string cmd,COMMAND_JUNCTION_KEY key);

void exit_program(command_info_container* info_to_pass);

void run_command_junction_C_MAIN(command_info_container* info_to_pass);
void run_command_junction_C_QUIT(command_info_container* info_to_pass);
void run_command_junction_C_GO_BACK(command_info_container* info_to_pass);
void run_command_junction_C_TEST(command_info_container* info_to_pass);
void run_command_junction_C_CHANGE_USER(command_info_container* info_to_pass); /// these functions are wrappers around run_command_junction, invoking a particular key - i.e. run_command_junction_C_MAIN invokes the C_MAIN command junction

void test_code_snippet(command_info_container* info_to_pass); /// this is for testing a snippet, for debugging purposes

}



struct command{ /// commands are contained in this class, main contents being pointer to immediate function to execute, and pointer to the function to execute after first function finishes running
    command(){}
    command(std::vector<std::string> inv, std::string desc,ptr_to_command_func p,ptr_to_command_func pn = &cli::run_command_junction_C_MAIN,std::string w = EMPTY_STRING,std::string cm = EMPTY_STRING):
        text_of_command(inv),description_of_command(desc),func_to_execute(p),next_func_to_execute(pn){}
    command(std::string inv, std::string desc,ptr_to_command_func p,ptr_to_command_func pn = &cli::run_command_junction_C_MAIN,std::string w = EMPTY_STRING,std::string cm = EMPTY_STRING):
        description_of_command(desc),func_to_execute(p),next_func_to_execute(pn){text_of_command.push_back(inv);}

std::vector<std::string> text_of_command;
std::string description_of_command;

ptr_to_command_func func_to_execute = nullptr;
ptr_to_command_func next_func_to_execute = nullptr;

};

class settings{
settings();
    map_of_command_junctions command_storage;
    std::map<std::string,user> usernames_passwords_privileges;

public:
    static settings& get(){static settings S; return S;}

    bool perform_authorisation(std::string username,std::string password);
    void set_active_user(std::string username);

    void populate_available_usernames_and_passwords();
    void initialise_command_tree();
    void apply_permissions_to_command_availability();
    void add_auto_commands_to_junction(COMMAND_JUNCTION_KEY key);
    void add_numerical_command_invokes(COMMAND_JUNCTION_KEY key);
    command match_input_text_to_a_command(std::string cmd,COMMAND_JUNCTION_KEY key);


    bool quit_flag;
    bool require_password_on_startup;
    bool use_admin_permissions_by_default;

    map_of_command_junctions permissioned_command_tree;

    std::string active_user;

    COMMAND_JUNCTION_KEY previous_junction;
    COMMAND_JUNCTION_KEY current_junction;

    command cmd_J1_quit_confirmation;
    command cmd_J0_main_menu;
    command cmd_J1_go_back;
    command cmd_login;
};

Code item 2 - implementation of Settings singleton:

#include "0_UTILITIES/Includes.h"
#include COMMAND_LINE_INTERFACE

settings::settings(){

quit_flag = false;
require_password_on_startup = true;
use_admin_permissions_by_default = true;

populate_available_usernames_and_passwords();
initialise_command_tree();

} 

void settings::populate_available_usernames_and_passwords(){

usernames_passwords_privileges["admin"] = user("adminpassword");



std::map<std::string,int> normaluser_permissions;
normaluser_permissions["LOGIN"] = 0; 

usernames_passwords_privileges["normaluser"] = user("normalpassword",normaluser_permissions);

if (use_admin_permissions_by_default) {active_user = "admin";} else {active_user = "normaluser";}
}

Code item 3 - populating the commands user is able to use in Settings singleton:

void settings::initialise_command_tree(){

command_storage.clear();
command cmd_continue_with_default_user("CONTD","Continue with default user",&cli::run_command_junction_C_MAIN);
cmd_login = command("LOGIN","Change user",&cli::run_authorisation);

command cmd_F_exit_program("FINALQUIT","Exit for good",&cli::exit_program);
cmd_J0_main_menu = command ("MAIN","Revert to main menu",&cli::run_command_junction_C_MAIN);
command cmd_J1_test_snippet("SNPT","Test a code snippet",&cli::test_code_snippet);
cmd_J1_go_back = command("GOBACK","Go back",&cli::run_command_junction_C_GO_BACK);

command cmd_J1_prompt_for_test_inputs("RTST","Input data to run Test",&cli::run_command_junction_C_TEST);

cmd_J1_quit_confirmation = command("EXIT","Confirm exiting program",&cli::run_command_junction_C_QUIT);

command cmd_F0_J1_test_manual_input("TSTM","Test - input via console",&cli::prompt_for_single_test_inputs,&cli::run_test);
command cmd_F1_J1_test_from_xls("TXLS","Test - input via xls",&cli::load_test_input_xls,&cli::run_test);
command cmd_F2_J1_test_from_csv("TCSV","Test - input via csv",&cli::load_test_input_csv,&cli::run_test);

command_storage[COMMAND_JUNCTION_KEY::C_MAIN].push_back(cmd_J1_quit_confirmation);
command_storage[COMMAND_JUNCTION_KEY::C_MAIN].push_back(cmd_J1_test_snippet);
command_storage[COMMAND_JUNCTION_KEY::C_MAIN].push_back(cmd_J1_prompt_for_test_inputs);
command_storage[COMMAND_JUNCTION_KEY::C_MAIN].push_back(cmd_login);

command_storage[COMMAND_JUNCTION_KEY::C_QUIT].push_back(cmd_F_exit_program);

command_storage[COMMAND_JUNCTION_KEY::C_TEST].push_back(cmd_F0_J1_test_manual_input);
command_storage[COMMAND_JUNCTION_KEY::C_TEST].push_back(cmd_F1_J1_test_from_xls);
command_storage[COMMAND_JUNCTION_KEY::C_TEST].push_back(cmd_F2_J1_test_from_csv);

command_storage[COMMAND_JUNCTION_KEY::C_CHANGE_USER].push_back(cmd_continue_with_default_user);
command_storage[COMMAND_JUNCTION_KEY::C_CHANGE_USER].push_back(cmd_login);

apply_permissions_to_command_availability();

for (auto& command_keys: permissioned_command_tree) { 
    add_auto_commands_to_junction(command_keys.first);
    add_numerical_command_invokes(command_keys.first);
}

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.