blob: 09411b95bcfe6528bf8fa38075007d76b2dec677 [file] [log] [blame]
Ryan Mitchell833a1a62018-07-10 13:51:36 -07001/*
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 "Command.h"
18
19#include <iomanip>
20#include <iostream>
21#include <string>
22#include <vector>
23
24#include "androidfw/StringPiece.h"
25
26#include "util/Util.h"
27
28using android::StringPiece;
29
30namespace aapt {
31
32void Command::AddRequiredFlag(const StringPiece& name,
33 const StringPiece& description, std::string* value) {
34 auto func = [value](const StringPiece& arg) -> bool {
35 *value = arg.to_string();
36 return true;
37 };
38
39 flags_.push_back(Flag{name.to_string(), description.to_string(), func, true, 1, false});
40}
41
42void Command::AddRequiredFlagList(const StringPiece& name,
43 const StringPiece& description,
44 std::vector<std::string>* value) {
45 auto func = [value](const StringPiece& arg) -> bool {
46 value->push_back(arg.to_string());
47 return true;
48 };
49
50 flags_.push_back(Flag{name.to_string(), description.to_string(), func, true, 1, false});
51}
52
53void Command::AddOptionalFlag(const StringPiece& name,
54 const StringPiece& description,
55 Maybe<std::string>* value) {
56 auto func = [value](const StringPiece& arg) -> bool {
57 *value = arg.to_string();
58 return true;
59 };
60
61 flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false});
62}
63
64void Command::AddOptionalFlagList(const StringPiece& name,
65 const StringPiece& description,
66 std::vector<std::string>* value) {
67 auto func = [value](const StringPiece& arg) -> bool {
68 value->push_back(arg.to_string());
69 return true;
70 };
71
72 flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false});
73}
74
75void Command::AddOptionalFlagList(const StringPiece& name,
76 const StringPiece& description,
77 std::unordered_set<std::string>* value) {
78 auto func = [value](const StringPiece& arg) -> bool {
79 value->insert(arg.to_string());
80 return true;
81 };
82
83 flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 1, false});
84}
85
86void Command::AddOptionalSwitch(const StringPiece& name,
87 const StringPiece& description, bool* value) {
88 auto func = [value](const StringPiece& arg) -> bool {
89 *value = true;
90 return true;
91 };
92
93 flags_.push_back(Flag{name.to_string(), description.to_string(), func, false, 0, false});
94}
95
96void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand) {
97 subcommand->fullname_ = name_ + " " + subcommand->name_;
98 subcommands_.push_back(std::move(subcommand));
99}
100
101void Command::SetDescription(const android::StringPiece& description) {
102 description_ = description.to_string();
103}
104
105void Command::Usage(std::ostream* out) {
106 constexpr size_t kWidth = 50;
107
108 *out << fullname_;
109
110 if (!subcommands_.empty()) {
111 *out << " [subcommand]";
112 }
113
114 *out << " [options]";
115 for (const Flag& flag : flags_) {
116 if (flag.required) {
117 *out << " " << flag.name << " arg";
118 }
119 }
120
121 *out << " files...\n";
122
123 if (!subcommands_.empty()) {
124 *out << "\nSubcommands:\n";
125 for (auto& subcommand : subcommands_) {
126 std::string argline = subcommand->name_;
127
128 // Split the description by newlines and write out the argument (which is
129 // empty after the first line) followed by the description line. This will make sure
130 // that multiline descriptions are still right justified and aligned.
131 for (StringPiece line : util::Tokenize(subcommand->description_, '\n')) {
132 *out << " " << std::setw(kWidth) << std::left << argline << line << "\n";
133 argline = " ";
134 }
135 }
136 }
137
138 *out << "\nOptions:\n";
139
140 for (const Flag& flag : flags_) {
141 std::string argline = flag.name;
142 if (flag.num_args > 0) {
143 argline += " arg";
144 }
145
146 // Split the description by newlines and write out the argument (which is
147 // empty after the first line) followed by the description line. This will make sure
148 // that multiline descriptions are still right justified and aligned.
149 for (StringPiece line : util::Tokenize(flag.description, '\n')) {
150 *out << " " << std::setw(kWidth) << std::left << argline << line << "\n";
151 argline = " ";
152 }
153 }
154 *out << " " << std::setw(kWidth) << std::left << "-h"
155 << "Displays this help menu\n";
156 out->flush();
157}
158
159int Command::Execute(const std::vector<android::StringPiece>& args, std::ostream* out_error) {
160 std::vector<std::string> file_args;
161
162 for (size_t i = 0; i < args.size(); i++) {
163 StringPiece arg = args[i];
164 if (*(arg.data()) != '-') {
165 // Continue parsing as the sub command if the first argument matches one of the subcommands
166 if (i == 0) {
167 for (auto& subcommand : subcommands_) {
168 if (arg == subcommand->name_ || arg==subcommand->short_name_) {
169 return subcommand->Execute(
170 std::vector<android::StringPiece>(args.begin() + 1, args.end()), out_error);
171 }
172 }
173 }
174
175 file_args.push_back(arg.to_string());
176 continue;
177 }
178
179 if (arg == "-h" || arg == "--help") {
180 Usage(out_error);
181 return 1;
182 }
183
184 bool match = false;
185 for (Flag& flag : flags_) {
186 if (arg == flag.name) {
187 if (flag.num_args > 0) {
188 i++;
189 if (i >= args.size()) {
190 *out_error << flag.name << " missing argument.\n\n";
191 Usage(out_error);
192 return false;
193 }
194 flag.action(args[i]);
195 } else {
196 flag.action({});
197 }
198 flag.parsed = true;
199 match = true;
200 break;
201 }
202 }
203
204 if (!match) {
205 *out_error << "unknown option '" << arg << "'.\n\n";
206 Usage(out_error);
207 return 1;
208 }
209 }
210
211 for (const Flag& flag : flags_) {
212 if (flag.required && !flag.parsed) {
213 *out_error << "missing required flag " << flag.name << "\n\n";
214 Usage(out_error);
215 return 1;
216 }
217 }
218
219 return Action(file_args);
220}
221
222} // namespace aapt