1 #include "metacommands.h"
2
3 #include <algorithm>
4 #include <cerrno>
5 #include <cstdlib>
6 #include <cstring>
7 #include <functional>
8 #include <iostream>
9 #include <map>
10 #include <memory>
11
12 #include "argparse.h"
13 #include "attribute_.h"
14 #include "command.h"
15 #include "completion.h"
16 #include "finite.h"
17 #include "ipc-protocol.h"
18
19 using std::endl;
20 using std::function;
21 using std::pair;
22 using std::string;
23 using std::stringstream;
24 using std::unique_ptr;
25 using std::vector;
26
27 extern char** environ;
28
MetaCommands(Object & root_)29 MetaCommands::MetaCommands(Object& root_) : root(root_) {
30 }
31
get_attr_cmd(Input in,Output output)32 int MetaCommands::get_attr_cmd(Input in, Output output) {
33 string attrName;
34 if (!(in >> attrName)) {
35 return HERBST_NEED_MORE_ARGS;
36 }
37
38 Attribute* a = getAttribute(attrName, output);
39 if (!a) {
40 return HERBST_INVALID_ARGUMENT;
41 }
42 output << a->str();
43 return 0;
44 }
45
set_attr_cmd(Input in,Output output)46 int MetaCommands::set_attr_cmd(Input in, Output output) {
47 string path, new_value;
48 if (!(in >> path >> new_value)) {
49 return HERBST_NEED_MORE_ARGS;
50 }
51
52 Attribute* a = getAttribute(path, output);
53 if (!a) {
54 return HERBST_INVALID_ARGUMENT;
55 }
56 string error_message = a->change(new_value);
57 if (error_message.empty()) {
58 return 0;
59 } else {
60 output << in.command() << ": \""
61 << new_value << "\" is not a valid value for "
62 << a->name() << ": "
63 << error_message << endl;
64 return HERBST_INVALID_ARGUMENT;
65 }
66 }
67
attr_cmd(Input in,Output output)68 int MetaCommands::attr_cmd(Input in, Output output) {
69 string path = "", new_value = "";
70 in >> path >> new_value;
71 std::ostringstream dummy_output;
72 Object* o = &root;
73 auto p = Path::split(path);
74 if (!p.empty()) {
75 while (p.back().empty()) {
76 p.pop_back();
77 }
78 o = o->child(p);
79 }
80 if (o && new_value.empty()) {
81 o->ls(output);
82 return 0;
83 }
84
85 Attribute* a = getAttribute(path, output);
86 if (!a) {
87 return HERBST_INVALID_ARGUMENT;
88 }
89 if (new_value.empty()) {
90 // no more arguments -> return the value
91 output << a->str();
92 return 0;
93 } else {
94 // another argument -> set the value
95 string error_message = a->change(new_value);
96 if (error_message.empty()) {
97 return 0;
98 } else {
99 output << in.command() << ": \""
100 << new_value << "\" is not a valid value for "
101 << a->name() << ": "
102 << error_message << endl;
103 return HERBST_INVALID_ARGUMENT;
104 }
105 }
106 }
107
getAttribute(string path,Output output)108 Attribute* MetaCommands::getAttribute(string path, Output output) {
109 auto attr_path = Object::splitPath(path);
110 auto child = root.child(attr_path.first);
111 if (!child) {
112 output << "No such object " << attr_path.first.join('.') << endl;
113 return nullptr;
114 }
115 Attribute* a = child->attribute(attr_path.second);
116 if (!a) {
117 auto object_path = attr_path.first.join('.');
118 if (object_path.empty()) {
119 object_path = "The root object";
120 } else {
121 // equip object_path with quotes
122 object_path = "Object \"" + object_path + "\"";
123 }
124 output << object_path
125 << " has no attribute \"" << attr_path.second << "\""
126 << endl;
127 return nullptr;
128 }
129 return a;
130 }
131
print_object_tree_command(Input in,Output output)132 int MetaCommands::print_object_tree_command(Input in, Output output) {
133 auto path = Path(in.empty() ? string("") : in.front()).toVector();
134 while (!path.empty() && path.back().empty()) {
135 path.pop_back();
136 }
137 auto child = root.child(path);
138 if (!child) {
139 output << "No such object " << Path(path).join('.') << endl;
140 return HERBST_INVALID_ARGUMENT;
141 }
142 child->printTree(output, Path(path).join('.'));
143 return 0;
144 }
145
print_object_tree_complete(Completion & complete)146 void MetaCommands::print_object_tree_complete(Completion& complete) {
147 if (complete == 0) {
148 completeObjectPath(complete);
149 } else {
150 complete.none();
151 }
152 }
153
154
substitute_cmd(Input input,Output output)155 int MetaCommands::substitute_cmd(Input input, Output output)
156 {
157 string ident, path;
158 if (!(input >> ident >> path )) {
159 return HERBST_NEED_MORE_ARGS;
160 }
161 Attribute* a = getAttribute(path, output);
162 if (!a) {
163 return HERBST_INVALID_ARGUMENT;
164 }
165
166 auto carryover = input.fromHere();
167 carryover.replace(ident, a->str());
168 return Commands::call(carryover, output);
169 }
170
substitute_complete(Completion & complete)171 void MetaCommands::substitute_complete(Completion& complete)
172 {
173 if (complete == 0) {
174 // no completion for the identifier
175 } else if (complete == 1) {
176 completeAttributePath(complete);
177 } else {
178 // later, complete the identifier
179 complete.full(complete[0]);
180 complete.completeCommands(2);
181 }
182 }
183
foreachCmd(Input input,Output output)184 int MetaCommands::foreachCmd(Input input, Output output)
185 {
186 string ident, pathString;
187 if (!(input >> ident >> pathString)) {
188 return HERBST_NEED_MORE_ARGS;
189 }
190 // remove trailing dots to avoid parsing issues:
191 while (!pathString.empty() && pathString.back() == OBJECT_PATH_SEPARATOR) {
192 // while the last character is a dot, erase it:
193 pathString.erase(pathString.size() - 1);
194 }
195 Path path { pathString, OBJECT_PATH_SEPARATOR };
196 Object* object = root.child(path, output);
197 if (!object) {
198 return HERBST_INVALID_ARGUMENT;
199 }
200
201 // collect the paths of all children of this object
202 vector<string> childPaths;
203 // collect the children's names first to ensure that
204 // object->children() is not changed by the commands we are
205 // calling.
206 if (!pathString.empty()) {
207 pathString += OBJECT_PATH_SEPARATOR;
208 }
209 for (const auto& entry : object->children()) {
210 childPaths.push_back(pathString + entry.first);
211 }
212 int lastStatusCode = 0;
213 for (const auto& child : childPaths) {
214 Input carryover = input.fromHere();
215 carryover.replace(ident, child);
216 lastStatusCode = Commands::call(carryover, output);
217 }
218 return lastStatusCode;
219 }
220
foreachComplete(Completion & complete)221 void MetaCommands::foreachComplete(Completion& complete)
222 {
223 if (complete == 0) {
224 // no completion for the identifier
225 } else if (complete == 1) {
226 completeObjectPath(complete);
227 } else {
228 // later, complete the identifier
229 complete.full(complete[0]);
230 complete.completeCommands(2);
231 }
232 }
233
234 //! parse a format string or throw an exception
parseFormatString(const string & format)235 MetaCommands::FormatString MetaCommands::parseFormatString(const string &format)
236 {
237 FormatString blobs;
238 size_t lastpos = 0; // the position where the last plaintext blob started
239 for (size_t i = 0; i < format.size(); i++) {
240 if (format[i] == '%') {
241 if (i + 1 >= format.size()) {
242 throw std::invalid_argument(
243 "dangling % at the end of format \"" + format + "\"");
244 } else {
245 if (i > lastpos) {
246 // add literal text blob
247 blobs.push_back({ true, format.substr(lastpos, i - lastpos)});
248 }
249 char format_type = format[i+1];
250 lastpos = i + 2;
251 i++; // also consume the format_type
252 if (format_type == '%') {
253 blobs.push_back({true, "%"});
254 } else if (format_type == 's') {
255 blobs.push_back({false, "s"});
256 } else if (format_type == 'c') {
257 blobs.push_back({false, "c"});
258 } else {
259 stringstream msg;
260 msg << "invalid format type %"
261 << format_type << " at position "
262 << i << " in format string \""
263 << format << "\"";
264 throw std::invalid_argument(msg.str());
265 }
266 }
267 }
268 }
269 if (lastpos < format.size()) {
270 blobs.push_back({true, format.substr(lastpos, format.size()-lastpos)});
271 }
272 return blobs;
273 }
274
sprintf_cmd(Input input,Output output)275 int MetaCommands::sprintf_cmd(Input input, Output output)
276 {
277 string ident, formatStringSrc;
278 if (!(input >> ident >> formatStringSrc)) {
279 return HERBST_NEED_MORE_ARGS;
280 }
281 FormatString format;
282 try {
283 format = parseFormatString(formatStringSrc);
284 } catch (const std::invalid_argument& e) {
285 output << input.command() << ": " << e.what() << endl;
286 return HERBST_INVALID_ARGUMENT;
287 }
288 // evaluate placeholders in the format string
289 string replacedString = "";
290 for (const auto& blob : format) {
291 if (blob.literal_) {
292 replacedString += blob.data_;
293 } else if (blob.data_ == "c") {
294 // a constant string argument
295 string constString;
296 if (!(input >> constString)) {
297 return HERBST_NEED_MORE_ARGS;
298 }
299 // just copy the plain string to the output
300 replacedString += constString;
301 } else {
302 // we reached %s
303 string path;
304 if (!(input >> path)) {
305 return HERBST_NEED_MORE_ARGS;
306 }
307 Attribute* a = getAttribute(path, output);
308 if (!a) {
309 return HERBST_INVALID_ARGUMENT;
310 }
311 replacedString += a->str();
312 }
313 }
314 auto carryover = input.fromHere();
315 carryover.replace(ident, replacedString);
316 return Commands::call(carryover, output);
317 }
318
sprintf_complete(Completion & complete)319 void MetaCommands::sprintf_complete(Completion& complete)
320 {
321 if (complete == 0) {
322 // no completion for arg name
323 } else if (complete == 1) {
324 // no completion for format string
325 } else {
326 FormatString fs;
327 try {
328 fs = parseFormatString(complete[1]);
329 } catch (const std::invalid_argument&) {
330 complete.invalidArguments();
331 return;
332 }
333 int indexOfNextArgument = 2;
334 for (const auto& b : fs) {
335 if (b.literal_ == true) {
336 continue;
337 }
338 if (complete == indexOfNextArgument) {
339 if (b.data_ == "s") {
340 completeAttributePath(complete);
341 } else if (b.data_ == "c") {
342 // no completion for constant strings
343 }
344 }
345 indexOfNextArgument++;
346 }
347 if (complete >= indexOfNextArgument) {
348 complete.full(complete[0]); // complete string replacement
349 complete.completeCommands(indexOfNextArgument);
350 }
351 }
352 }
353
354 static std::map<string, function<Attribute*(string)>> name2constructor {
__anon52be4b550102(string n) 355 { "bool", [](string n) { return new Attribute_<bool>(n, false); }},
__anon52be4b550202(string n) 356 { "color", [](string n) { return new Attribute_<Color>(n, {"black"}); }},
__anon52be4b550302(string n) 357 { "int", [](string n) { return new Attribute_<int>(n, 0); }},
__anon52be4b550402(string n) 358 { "string",[](string n) { return new Attribute_<string>(n, ""); }},
__anon52be4b550502(string n) 359 { "uint", [](string n) { return new Attribute_<unsigned long>(n, 0); }},
360 };
361
newAttributeWithType(string typestr,string attr_name,Output output)362 Attribute* MetaCommands::newAttributeWithType(string typestr, string attr_name, Output output) {
363 auto it = name2constructor.find(typestr);
364 if (it == name2constructor.end()) {
365 output << "error: unknown type \"" << typestr << "\"";
366 return nullptr;
367 }
368 auto attr = it->second(attr_name);
369 attr->setWritable(true);
370 return attr;
371 }
372
completeAttributeType(Completion & complete)373 void MetaCommands::completeAttributeType(Completion& complete)
374 {
375 for (const auto& t : name2constructor) {
376 complete.full(t.first);
377 }
378 }
379
380
381
new_attr_cmd(Input input,Output output)382 int MetaCommands::new_attr_cmd(Input input, Output output)
383 {
384 string type, path, initialValue;
385 bool initialValueSupplied = false;
386 ArgParse ap;
387 ap.mandatory(type).mandatory(path);
388 ap.optional(initialValue, &initialValueSupplied);
389 if (ap.parsingFails(input, output)) {
390 return HERBST_NEED_MORE_ARGS;
391 }
392 auto obj_path_and_attr = Object::splitPath(path);
393 string attr_name = obj_path_and_attr.second;
394 Object* obj = root.child(obj_path_and_attr.first, output);
395 if (!obj) {
396 return HERBST_INVALID_ARGUMENT;
397 }
398 if (attr_name.substr(0,strlen(USER_ATTRIBUTE_PREFIX)) != USER_ATTRIBUTE_PREFIX) {
399 output
400 << input.command() << ": attribute name must start with \""
401 << USER_ATTRIBUTE_PREFIX << "\""
402 << " but is actually \"" << attr_name << "\"" << endl;
403 return HERBST_INVALID_ARGUMENT;
404 }
405 if (obj->attribute(attr_name)) {
406 output
407 << input.command() << ": object \"" << obj_path_and_attr.first.join()
408 << "\" already has an attribute named \"" << attr_name
409 << "\"" << endl;
410 return HERBST_INVALID_ARGUMENT;
411 }
412 // create the new attribute and add it
413 Attribute* a = newAttributeWithType(type, attr_name, output);
414 if (!a) {
415 return HERBST_INVALID_ARGUMENT;
416 }
417 obj->addAttribute(a);
418 userAttributes_.push_back(unique_ptr<Attribute>(a));
419 // try to write the attribute
420 if (initialValueSupplied) {
421 string msg = a->change(initialValue);
422 if (!msg.empty()) {
423 output << input.command() << ": \""
424 << initialValue << "\" is an invalid "
425 << "value for " << path << ": " << msg << endl;
426 return HERBST_INVALID_ARGUMENT;
427 }
428 }
429 return 0;
430 }
431
new_attr_complete(Completion & complete)432 void MetaCommands::new_attr_complete(Completion& complete)
433 {
434 if (complete == 0) {
435 completeAttributeType(complete);
436 } else if (complete == 1) {
437 completeObjectPath(complete, false);
438 auto obj_path = Object::splitPath(complete[1]).first;
439 if (obj_path.empty()) {
440 complete.partial(USER_ATTRIBUTE_PREFIX);
441 } else {
442 char s[] = {OBJECT_PATH_SEPARATOR, '\0'};
443 complete.partial(obj_path.join(OBJECT_PATH_SEPARATOR) + s + USER_ATTRIBUTE_PREFIX);
444 }
445 } else if (complete == 2) {
446 // no completion for the initial value
447 } else {
448 complete.none();
449 }
450 }
451
remove_attr_cmd(Input input,Output output)452 int MetaCommands::remove_attr_cmd(Input input, Output output)
453 {
454 string path;
455 if (!(input >> path )) {
456 return HERBST_NEED_MORE_ARGS;
457 }
458 Attribute* a = root.deepAttribute(path, output);
459 if (!a) {
460 return HERBST_INVALID_ARGUMENT;
461 }
462 if (a->name().substr(0,strlen(USER_ATTRIBUTE_PREFIX)) != USER_ATTRIBUTE_PREFIX) {
463 output << input.command() << ": Cannot remove built-in attribute \"" << path << "\"" << endl;
464 return HERBST_INVALID_ARGUMENT;
465 }
466 a->detachFromOwner();
467 userAttributes_.erase(
468 std::remove_if(
469 userAttributes_.begin(),
470 userAttributes_.end(),
471 [a](const unique_ptr<Attribute>& p) { return p.get() == a; }),
472 userAttributes_.end());
473 return 0;
474 }
475
remove_attr_complete(Completion & complete)476 void MetaCommands::remove_attr_complete(Completion& complete) {
477 if (complete == 0) {
478 completeObjectPath(complete, true, [] (Attribute* a) {
479 return a->name().substr(0,strlen(USER_ATTRIBUTE_PREFIX))
480 == USER_ATTRIBUTE_PREFIX;
481 });
482 } else {
483 complete.none();
484 }
485 }
486
do_comparison(const T & a,const T & b)487 template <typename T> int do_comparison(const T& a, const T& b) {
488 return (a == b) ? 0 : 1;
489 }
490
do_comparison(const int & a,const int & b)491 template <> int do_comparison<int>(const int& a, const int& b) {
492 if (a < b) {
493 return -1;
494 } else if (a > b) {
495 return 1;
496 } else {
497 return 0;
498 }
499 }
do_comparison(const unsigned long & a,const unsigned long & b)500 template <> int do_comparison<unsigned long>(const unsigned long& a, const unsigned long& b) {
501 if (a < b) {
502 return -1;
503 } else if (a > b) {
504 return 1;
505 } else {
506 return 0;
507 }
508 }
509
parse_and_compare(string a,string b,Output o)510 template <typename T> int parse_and_compare(string a, string b, Output o) {
511 vector<T> vals;
512 for (auto &x : {a, b}) {
513 try {
514 vals.push_back(Converter<T>::parse(x));
515 } catch(std::exception& e) {
516 o << "cannot parse \"" << x << "\" to "
517 << typeid(T).name() << ": " << e.what() << endl;
518 return (int) HERBST_INVALID_ARGUMENT;
519 }
520 }
521 return do_comparison<T>(vals[0], vals[1]);
522 }
523
524 //! operators of the 'compare' command
525 using CompareOperator = pair<bool, vector<int> >;
526
527 template <>
528 struct is_finite<CompareOperator> : std::true_type {};
529 template<> Finite<CompareOperator>::ValueList Finite<CompareOperator>::values = {
530 // map operator names to "for numeric types only" and possible return codes
531 { { false, { 0 }, }, "=", },
532 { { false, { -1, 1 } }, "!=", },
533 { { true, { 1, 0 } }, "ge", },
534 { { true, { 1 } }, "gt", },
535 { { true, { -1, 0 } }, "le", },
536 { { true, { -1 } }, "lt", },
537 };
538
compare_cmd(Input input,Output output)539 int MetaCommands::compare_cmd(Input input, Output output)
540 {
541 string path, value;
542 CompareOperator oper;
543 ArgParse ap = ArgParse().mandatory(path).mandatory(oper).mandatory(value);
544 if (ap.parsingFails(input, output)) {
545 return ap.exitCode();
546 }
547 Attribute* a = root.deepAttribute(path, output);
548 if (!a) {
549 return HERBST_INVALID_ARGUMENT;
550 }
551 // the following compare functions returns
552 // -1 if the first value is smaller
553 // 1 if the first value is greater
554 // 0 if the the values match
555 // HERBST_INVALID_ARGUMENT if there was a parsing error
556 std::map<Type, pair<bool, function<int(string,string,Output)>>> type2compare {
557 // map a type name to "is it numeric" and a comperator function
558 { Type::INT, { true, parse_and_compare<int> }, },
559 { Type::ULONG, { true, parse_and_compare<int> }, },
560 { Type::BOOL, { false, parse_and_compare<bool> }, },
561 { Type::COLOR, { false, parse_and_compare<Color> }, },
562 };
563 // the default comparison is simply string based:
564 pair<bool, function<int(string,string,Output)>> comparator =
565 { false, parse_and_compare<string> };
566 auto it = type2compare.find(a->type());
567 if (it != type2compare.end()) {
568 comparator = it->second;
569 }
570 if (oper.first && !comparator.first) {
571 output << "operator \"" << Converter<CompareOperator>::str(oper) << "\" "
572 << "only allowed for numeric types, but the attribute "
573 << path << " is of non-numeric type "
574 << Entity::typestr(a->type()) << endl;
575 return HERBST_INVALID_ARGUMENT;
576 }
577 int comparison_result = comparator.second(a->str(), value, output);
578 if (comparison_result > 1) {
579 return comparison_result;
580 }
581 vector<int>& possible_values = oper.second;
582 auto found = std::find(possible_values.begin(),
583 possible_values.end(),
584 comparison_result);
585 return (found == possible_values.end()) ? 1 : 0;
586 }
587
compare_complete(Completion & complete)588 void MetaCommands::compare_complete(Completion &complete) {
589 if (complete == 0) {
590 completeAttributePath(complete);
591 } else if (complete == 1) {
592 Converter<CompareOperator>::complete(complete);
593 } else if (complete == 2) {
594 // no completion suggestions for the 'value' field
595 } else {
596 complete.none();
597 }
598 }
599
600
completeObjectPath(Completion & complete,Object * rootObject,bool attributes,function<bool (Attribute *)> attributeFilter)601 void MetaCommands::completeObjectPath(Completion& complete, Object* rootObject,
602 bool attributes,
603 function<bool(Attribute*)> attributeFilter)
604 {
605 ArgList objectPathArgs = std::get<0>(Object::splitPath(complete.needle()));
606 string objectPath = objectPathArgs.join(OBJECT_PATH_SEPARATOR);
607 if (!objectPath.empty()) {
608 objectPath += OBJECT_PATH_SEPARATOR;
609 }
610 Object* object = rootObject->child(objectPathArgs);
611 if (!object) {
612 return;
613 }
614 if (attributes) {
615 for (auto& it : object->attributes()) {
616 if (attributeFilter && !attributeFilter(it.second)) {
617 continue;
618 }
619 complete.full(objectPath + it.first);
620 }
621 }
622 for (auto& it : object->children()) {
623 complete.partial(objectPath + it.first + OBJECT_PATH_SEPARATOR);
624 }
625 }
626
completeObjectPath(Completion & complete,bool attributes,function<bool (Attribute *)> attributeFilter)627 void MetaCommands::completeObjectPath(Completion& complete, bool attributes,
628 function<bool(Attribute*)> attributeFilter)
629 {
630 MetaCommands::completeObjectPath(complete, &root, attributes, attributeFilter);
631 }
632
completeAttributePath(Completion & complete)633 void MetaCommands::completeAttributePath(Completion& complete) {
634 completeObjectPath(complete, true);
635 }
636
helpCommand(Input input,Output output)637 int MetaCommands::helpCommand(Input input, Output output)
638 {
639 string needle;
640 ArgParse args = ArgParse().mandatory(needle);
641 if (args.parsingAllFails(input, output)) {
642 return args.exitCode();
643 }
644 function<string(string)> header =
645 [] (const string& text) {
646 string buf = text + "\n";
647 for (size_t i = 0; i < text.size(); i++) {
648 buf += "-";
649 }
650 buf += "\n";
651 return buf;
652 };
653 // drop trailing '.'
654 if (!needle.empty() && *needle.rbegin() == '.') {
655 needle.pop_back();
656 }
657 // split the needle into the path to the owning object
658 // and the name of the entry.
659 auto path = Object::splitPath(needle);
660 Object* parent = root.child(path.first, output);
661 const HasDocumentation* childDoc = nullptr;
662 Object* object = nullptr;
663 if (parent) {
664 object = parent->child(path.second);
665 }
666 if (needle.empty()) {
667 object = &root;
668 }
669 bool helpFound = false;
670 if (parent) {
671 Attribute* attribute = parent->attribute(path.second);
672 if (attribute) {
673 helpFound = true;
674 output << header("Attribute \'" + path.second + "\' of \'"
675 + path.first.join() + "\'");
676 output << endl; // empty line
677 output << "Type: " << Entity::typestr(attribute->type()) << endl;
678 output << "Current value: " << attribute->str() << endl;
679 output << endl; // empty line
680 const string& doc = attribute->doc();
681 if (!doc.empty()) {
682 output << doc;
683 output << endl;
684 output << endl; // empty line
685 }
686 }
687 // only print documentation on the entry if there
688 // is anything particular to be said.
689 childDoc = parent->childDoc(path.second);
690 if (childDoc && !childDoc->doc().empty()) {
691 helpFound = true;
692 output << header("Entry \'" + path.second + "\' of \'"
693 + path.first.join() + "\'");
694 output << childDoc->doc() << endl;
695 output << endl; // empty line
696 }
697 }
698 if (object || childDoc) {
699 helpFound = true;
700 output << header("Object \'" + needle + "\'");
701 }
702 if (childDoc && !object) {
703 // if the entry may exist at some time but not at the
704 // moment then make the user aware of this
705 output << "(Entry does not exist at the moment)" << endl;
706 } else if (object) {
707 object->ls(output);
708 }
709 if (!helpFound) {
710 output << "No help found for \'" << needle << "\'" << endl;
711 return HERBST_INVALID_ARGUMENT;
712 }
713 return 0;
714 }
715
helpCompletion(Completion & complete)716 void MetaCommands::helpCompletion(Completion& complete)
717 {
718 if (complete == 0) {
719 completeAttributePath(complete);
720 } else {
721 complete.none();
722 }
723 }
724
get_attr_complete(Completion & complete)725 void MetaCommands::get_attr_complete(Completion& complete) {
726 if (complete == 0) {
727 completeAttributePath(complete);
728 } else {
729 complete.none();
730 }
731 }
732
set_attr_complete(Completion & complete)733 void MetaCommands::set_attr_complete(Completion& complete) {
734 if (complete == 0) {
735 completeObjectPath(complete, true,
736 [](Attribute* a) { return a->writable(); } );
737 } else if (complete == 1) {
738 Attribute* a = root.deepAttribute(complete[0]);
739 if (a) {
740 a->complete(complete);
741 }
742 } else {
743 complete.none();
744 }
745 }
746
attr_complete(Completion & complete)747 void MetaCommands::attr_complete(Completion& complete)
748 {
749 if (complete == 0) {
750 completeAttributePath(complete);
751 } else if (complete == 1) {
752 Attribute* a = root.deepAttribute(complete[0]);
753 if (!a) {
754 return;
755 }
756 if (a->writable()) {
757 a->complete(complete);
758 } else {
759 complete.none();
760 }
761 } else {
762 complete.none();
763 }
764 }
765
tryCommand(Input input,Output output)766 int MetaCommands::tryCommand(Input input, Output output) {
767 Commands::call(input.fromHere(), output); // pass output
768 return 0; // ignore exit code
769 }
770
silentCommand(Input input,Output output)771 int MetaCommands::silentCommand(Input input, Output output) {
772 stringstream dummyOutput;
773 // drop output but pass exit code
774 return Commands::call(input.fromHere(), dummyOutput);
775 }
776
negateCommand(Input input,Output output)777 int MetaCommands::negateCommand(Input input, Output output)
778 {
779 return ! Commands::call(input.fromHere(), output);
780 }
781
completeCommandShifted1(Completion & complete)782 void MetaCommands::completeCommandShifted1(Completion& complete) {
783 complete.completeCommands(0);
784 }
785
echoCommand(Input input,Output output)786 int MetaCommands::echoCommand(Input input, Output output)
787 {
788 string token;
789 bool first = true;
790 while (input >> token) {
791 output << (first ? "" : " ") << token;
792 first = false;
793 }
794 output << endl;
795 return 0;
796 }
797
798
setenvCommand(Input input,Output output)799 int MetaCommands::setenvCommand(Input input, Output output) {
800 string name, value;
801 if (!(input >> name >> value)) {
802 return HERBST_NEED_MORE_ARGS;
803 }
804 if (setenv(name.c_str(), value.c_str(), 1) != 0) {
805 output << input.command()
806 << ": Could not set environment variable: "
807 << strerror(errno) << endl;
808 return HERBST_UNKNOWN_ERROR;
809 }
810 return 0;
811 }
812
setenvCompletion(Completion & complete)813 void MetaCommands::setenvCompletion(Completion& complete) {
814 if (complete == 0) {
815 return completeEnvName(complete);
816 } else if (complete == 1) {
817 // no completion for the value
818 } else {
819 complete.none();
820 }
821 }
822
823 //! a wraper around setenv with the usual 'export' syntax in posix
exportEnvCommand(Input input,Output output)824 int MetaCommands::exportEnvCommand(Input input, Output output)
825 {
826 string arg;
827 if (!(input >> arg )) {
828 return HERBST_NEED_MORE_ARGS;
829 }
830 auto pos = arg.find("=");
831 if (pos >= arg.size()) {
832 return HERBST_NEED_MORE_ARGS;
833 }
834 // if "=" has been found in the arg, split it there
835 auto newInput = Input(input.command(), {arg.substr(0, pos), arg.substr(pos + 1)});
836 return setenvCommand(newInput, output);
837 }
838
exportEnvCompletion(Completion & complete)839 void MetaCommands::exportEnvCompletion(Completion &complete)
840 {
841 for (char** env = environ; *env; ++env) {
842 vector<string> chunks = ArgList::split(*env, '=');
843 if (!chunks.empty()) {
844 complete.partial(chunks[0] + "=");
845 }
846 }
847 }
848
getenvCommand(Input input,Output output)849 int MetaCommands::getenvCommand(Input input, Output output) {
850 string name;
851 if (!(input >> name)) {
852 return HERBST_NEED_MORE_ARGS;
853 }
854 char* envvar = getenv(name.c_str());
855 if (!envvar) {
856 return HERBST_ENV_UNSET;
857 }
858 output << envvar << endl;
859 return 0;
860 }
861
862 //! completion for unsetenv and getenv
getenvUnsetenvCompletion(Completion & complete)863 void MetaCommands::getenvUnsetenvCompletion(Completion& complete) {
864 if (complete == 0) {
865 return completeEnvName(complete);
866 } else {
867 complete.none();
868 }
869 }
870
unsetenvCommand(Input input,Output output)871 int MetaCommands::unsetenvCommand(Input input, Output output) {
872 string name;
873 if (!(input >> name)) {
874 return HERBST_NEED_MORE_ARGS;
875 }
876 if (unsetenv(name.c_str()) != 0) {
877 output << input.command()
878 << ": Could not unset environment variable: "
879 << strerror(errno) << endl;
880 return HERBST_UNKNOWN_ERROR;
881 }
882 return 0;
883 }
884
completeEnvName(Completion & complete)885 void MetaCommands::completeEnvName(Completion& complete) {
886 for (char** env = environ; *env; ++env) {
887 vector<string> chunks = ArgList::split(*env, '=');
888 if (!chunks.empty()) {
889 complete.full(chunks[0]);
890 }
891 }
892 }
893
chainCommand(Input input,Output output)894 int MetaCommands::chainCommand(Input input, Output output)
895 {
896 vector<vector<string>> commands = splitCommandList(input.toVector());
897 int returnCode = 0;
898 // the condition that has to be fulfilled if we want to continue
899 // execuding commands. the default (for 'chain') is to always continue
900 function<bool(int)> conditionContinue = [](int) { return true; };
901 if (input.command() == "and") {
902 // continue executing commands while they are successful
903 conditionContinue = [](int code) { return code == 0; };
904 }
905 if (input.command() == "or") {
906 returnCode = 1;
907 // continue executing commands while they are failing
908 conditionContinue = [](int code) { return code >= 1; };
909 }
910 for (auto& cmd : commands) {
911 if (cmd.empty()) {
912 // if command range is empty, do nothing
913 continue;
914 }
915 Input cmdinput = Input(cmd[0], cmd.begin() + 1, cmd.end());
916 returnCode = Commands::call(cmdinput, output);
917 if (!conditionContinue(returnCode)) {
918 break;
919 }
920 }
921 return returnCode;
922 }
923
chainCompletion(Completion & complete)924 void MetaCommands::chainCompletion(Completion& complete)
925 {
926 if (complete == 0) {
927 // no completion for the separator
928 } else {
929 size_t lastsep = 0;
930 for (size_t i = 1; i < complete.needleIndex(); i++) {
931 if (complete[i] == complete[0]) {
932 lastsep = i;
933 }
934 }
935 if (complete > lastsep + 1) {
936 complete.full(complete[0]);
937 }
938 complete.completeCommands(lastsep + 1);
939 }
940 }
941
942
splitCommandList(ArgList::Container input)943 vector<vector<string>> MetaCommands::splitCommandList(ArgList::Container input) {
944 vector<vector<string>> res;
945 if (input.empty()) {
946 return res;
947 }
948 vector<string> current;
949 string separator = input[0];
950 for (size_t i = 1; i < input.size(); i++) {
951 if (input[i] == separator) {
952 res.push_back(current);
953 current = {};
954 } else {
955 current.push_back(input[i]);
956 }
957 }
958 res.push_back(current);
959 return res;
960 }
961
962