1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "idmap2/CommandLineOptions.h"
18 
19 #include <algorithm>
20 #include <iomanip>
21 #include <iostream>
22 #include <memory>
23 #include <set>
24 #include <sstream>
25 #include <string>
26 #include <vector>
27 
28 #include "android-base/macros.h"
29 #include "idmap2/Result.h"
30 
31 namespace android::idmap2 {
32 
ConvertArgvToVector(int argc,const char ** argv)33 std::unique_ptr<std::vector<std::string>> CommandLineOptions::ConvertArgvToVector(
34     int argc, const char** argv) {
35   return std::make_unique<std::vector<std::string>>(argv + 1, argv + argc);
36 }
37 
OptionalFlag(const std::string & name,const std::string & description,bool * value)38 CommandLineOptions& CommandLineOptions::OptionalFlag(const std::string& name,
39                                                      const std::string& description, bool* value) {
40   assert(value != nullptr);
41   auto func = [value](const std::string& arg ATTRIBUTE_UNUSED) -> void { *value = true; };
42   options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, false});
43   return *this;
44 }
45 
MandatoryOption(const std::string & name,const std::string & description,std::string * value)46 CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name,
47                                                         const std::string& description,
48                                                         std::string* value) {
49   assert(value != nullptr);
50   auto func = [value](const std::string& arg) -> void { *value = arg; };
51   options_.push_back(Option{name, description, func, Option::COUNT_EXACTLY_ONCE, true});
52   return *this;
53 }
54 
MandatoryOption(const std::string & name,const std::string & description,std::vector<std::string> * value)55 CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name,
56                                                         const std::string& description,
57                                                         std::vector<std::string>* value) {
58   assert(value != nullptr);
59   auto func = [value](const std::string& arg) -> void { value->push_back(arg); };
60   options_.push_back(Option{name, description, func, Option::COUNT_ONCE_OR_MORE, true});
61   return *this;
62 }
63 
OptionalOption(const std::string & name,const std::string & description,std::string * value)64 CommandLineOptions& CommandLineOptions::OptionalOption(const std::string& name,
65                                                        const std::string& description,
66                                                        std::string* value) {
67   assert(value != nullptr);
68   auto func = [value](const std::string& arg) -> void { *value = arg; };
69   options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, true});
70   return *this;
71 }
72 
OptionalOption(const std::string & name,const std::string & description,std::vector<std::string> * value)73 CommandLineOptions& CommandLineOptions::OptionalOption(const std::string& name,
74                                                        const std::string& description,
75                                                        std::vector<std::string>* value) {
76   assert(value != nullptr);
77   auto func = [value](const std::string& arg) -> void { value->push_back(arg); };
78   options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL_ONCE_OR_MORE, true});
79   return *this;
80 }
81 
Parse(const std::vector<std::string> & argv) const82 Result<Unit> CommandLineOptions::Parse(const std::vector<std::string>& argv) const {
83   const auto pivot = std::partition(options_.begin(), options_.end(), [](const Option& opt) {
84     return opt.count != Option::COUNT_OPTIONAL && opt.count != Option::COUNT_OPTIONAL_ONCE_OR_MORE;
85   });
86   std::set<std::string> mandatory_opts;
87   std::transform(options_.begin(), pivot, std::inserter(mandatory_opts, mandatory_opts.end()),
88                  [](const Option& opt) -> std::string { return opt.name; });
89 
90   const size_t argv_size = argv.size();
91   for (size_t i = 0; i < argv_size; i++) {
92     const std::string arg = argv[i];
93     if ("--help" == arg || "-h" == arg) {
94       std::stringstream stream;
95       Usage(stream);
96       return Error("%s", stream.str().c_str());
97     }
98     bool match = false;
99     for (const Option& opt : options_) {
100       if (opt.name == arg) {
101         match = true;
102 
103         if (opt.argument) {
104           i++;
105           if (i >= argv_size) {
106             std::stringstream stream;
107             Usage(stream);
108             return Error("%s: missing argument\n%s", opt.name.c_str(), stream.str().c_str());
109           }
110         }
111         opt.action(argv[i]);
112         mandatory_opts.erase(opt.name);
113         break;
114       }
115     }
116     if (!match) {
117       std::stringstream stream;
118       Usage(stream);
119       return Error("%s: unknown option\n%s", arg.c_str(), stream.str().c_str());
120     }
121   }
122 
123   if (!mandatory_opts.empty()) {
124     std::stringstream stream;
125     bool separator = false;
126     for (const auto& opt : mandatory_opts) {
127       if (separator) {
128         stream << ", ";
129       }
130       separator = true;
131       stream << opt << ": missing mandatory option";
132     }
133     stream << std::endl;
134     Usage(stream);
135     return Error("%s", stream.str().c_str());
136   }
137   return Unit{};
138 }
139 
Usage(std::ostream & out) const140 void CommandLineOptions::Usage(std::ostream& out) const {
141   size_t maxLength = 0;
142   out << "usage: " << name_;
143   for (const Option& opt : options_) {
144     const bool mandatory =
145         opt.count != Option::COUNT_OPTIONAL && opt.count != Option::COUNT_OPTIONAL_ONCE_OR_MORE;
146     out << " ";
147     if (!mandatory) {
148       out << "[";
149     }
150     if (opt.argument) {
151       out << opt.name << " arg";
152       maxLength = std::max(maxLength, opt.name.size() + 4);
153     } else {
154       out << opt.name;
155       maxLength = std::max(maxLength, opt.name.size());
156     }
157 
158     if (opt.count == Option::COUNT_OPTIONAL_ONCE_OR_MORE) {
159       out << " [..]";
160     }
161 
162     if (!mandatory) {
163       out << "]";
164     }
165 
166     if (opt.count == Option::COUNT_ONCE_OR_MORE) {
167       out << " [" << opt.name << " arg [..]]";
168     }
169   }
170   out << std::endl << std::endl;
171   for (const Option& opt : options_) {
172     out << std::left << std::setw(maxLength);
173     if (opt.argument) {
174       out << (opt.name + " arg");
175     } else {
176       out << opt.name;
177     }
178     out << "    " << opt.description;
179     if (opt.count == Option::COUNT_ONCE_OR_MORE ||
180         opt.count == Option::COUNT_OPTIONAL_ONCE_OR_MORE) {
181       out << " (can be provided multiple times)";
182     }
183     out << std::endl;
184   }
185 }
186 
187 }  // namespace android::idmap2
188