Add first version
[ric-plt/sdl.git] / src / cli / commandparserandexecutor.cpp
1 #include "config.h"
2 #include "private/cli/commandparserandexecutor.hpp"
3 #include <ostream>
4 #include <set>
5 #include <string>
6 #include <exception>
7 #include <boost/program_options.hpp>
8 #include "private/cli/commandmap.hpp"
9
10 using namespace shareddatalayer;
11 using namespace shareddatalayer::cli;
12 namespace po = boost::program_options;
13
14 namespace
15 {
16     void outputBashCompletion(std::ostream& os,
17                               const po::options_description& commonOpts,
18                               CommandMap& commandMap)
19     {
20         const auto commandNames(commandMap.getCommandNames());
21         std::set<std::string> optionNames;
22         for (const auto& i : commonOpts.options())
23             optionNames.insert(i->long_name());
24         for (const auto& i : commandNames)
25             for (const auto& j : commandMap.getCommandOptions(i).options())
26                 optionNames.insert(j->long_name());
27
28         os << "_sdltool()\n{\n    COMPREPLY=()\n    local commands=\"";
29         for (const auto& i : commandNames)
30             os << i << ' ';
31         os << "\"\n    local options=\"";
32         for (const auto& i : optionNames)
33             os << "--" << i << ' ';
34         os <<
35 R"bash("
36     local cur="${COMP_WORDS[COMP_CWORD]}"
37
38     if [[ ${cur} == -* ]] ; then
39         COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
40         return 0
41     else
42         COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )
43         return 0
44     fi
45 }
46 complete -F _sdltool sdltool
47 )bash"
48            << std::flush;
49     }
50
51     void printShortHelp(std::ostream& os,
52                         const po::options_description& commonOpts,
53                         CommandMap& commandMap)
54     {
55         os << "Usage: sdltool [OPTIONS] COMMAND [COMMAND SPECIFIC OPTIONS]\n\n";
56         commonOpts.print(os);
57         os << "\nAvailable commands:\n";
58         commandMap.shortHelp(os);
59     }
60
61     int privateParseAndExecute(int argc,
62                                char** argv,
63                                std::ostream& out,
64                                std::ostream& err,
65                                CommandMap& commandMap)
66     {
67         po::options_description commonHiddenOptsDescription("Common hidden options");
68         commonHiddenOptsDescription.add_options()
69             ("pos-command", po::value<std::string>(), "sdltool command to run")
70             ("pos-subargs", po::value<std::vector<std::string>>(), "Arguments for command");
71
72         po::options_description commonVisibleOptsDescription("Common options");
73         commonVisibleOptsDescription.add_options()
74             ("help", "Show help for COMMAND")
75             ("repeat", po::value<size_t>()->default_value(1U), "Times to repeat the command")
76             ("bash-completion", "Generate bash completion script")
77             ("version", "Show version information");
78
79         po::options_description commonOptsDescription("All common options");
80         commonOptsDescription.add(commonHiddenOptsDescription);
81         commonOptsDescription.add(commonVisibleOptsDescription);
82
83         po::positional_options_description commonPosOptsDescription;
84         commonPosOptsDescription
85             .add("pos-command", 1)
86             .add("pos-subargs", -1); // all positional arguments after 'command'
87
88         po::variables_map commonVarsMap;
89         po::parsed_options commonParsed(po::command_line_parser(argc, argv)
90                                         .options(commonOptsDescription)
91                                         .positional(commonPosOptsDescription)
92                                         .allow_unregistered()
93                                         .run());
94         po::store(commonParsed, commonVarsMap);
95         po::notify(commonVarsMap);
96
97         if (!commonVarsMap.count("pos-command"))
98         {
99             if (commonVarsMap.count("help"))
100             {
101                 printShortHelp(out, commonVisibleOptsDescription, commandMap);
102                 return EXIT_SUCCESS;
103             }
104             if (commonVarsMap.count("bash-completion"))
105             {
106                 outputBashCompletion(out, commonVisibleOptsDescription, commandMap);
107                 return EXIT_SUCCESS;
108             }
109             if (commonVarsMap.count("version"))
110             {
111                 out << PACKAGE_STRING << std::endl;
112                 return EXIT_SUCCESS;
113             }
114             err << "missing command\n\n";
115             printShortHelp(err, commonVisibleOptsDescription, commandMap);
116             return EXIT_FAILURE;
117         }
118
119         auto leftOverOpts = po::collect_unrecognized(commonParsed.options, po::include_positional);
120         if (!leftOverOpts.empty())
121             leftOverOpts.erase(leftOverOpts.begin());
122
123         const auto commandName(commonVarsMap["pos-command"].as<std::string>());
124
125         if (commonVarsMap.count("help"))
126         {
127             bool found(false);
128             try
129             {
130                 commandMap.longHelp(out, commandName);
131                 out << '\n';
132                 found = true;
133             }
134             catch (const CommandMap::UnknownCommandName&)
135             {
136             }
137             if (!found)
138             {
139                 err << "unknown command: \"" << commandName << "\"\n";
140                 return EXIT_FAILURE;
141             }
142             return EXIT_SUCCESS;
143         }
144
145         const auto& commandOptions(commandMap.getCommandOptions(commandName));
146
147         po::variables_map subcmdVarsMap;
148         auto subcmdParsed(po::command_line_parser(leftOverOpts).options(commandOptions).run());
149         po::store(subcmdParsed, subcmdVarsMap);
150         po::notify(subcmdVarsMap);
151
152         return commandMap.execute(commandName,
153                                   out,
154                                   err,
155                                   subcmdVarsMap,
156                                   commonVarsMap["repeat"].as<size_t>());
157     }
158 }
159
160 int shareddatalayer::cli::parseAndExecute(int argc,
161                                          char** argv,
162                                          std::ostream& out,
163                                          std::ostream& err,
164                                          CommandMap& commandMap)
165 {
166     try
167     {
168         return privateParseAndExecute(argc, argv, out, err, commandMap);
169     }
170     catch (const std::exception& e)
171     {
172         err << e.what() << std::endl;
173         return EXIT_FAILURE;
174     }
175 }
176