1 #include "rulemanager.h"
2 
3 #include <algorithm>
4 #include <string>
5 
6 #include "completion.h"
7 #include "globals.h"
8 #include "ipc-protocol.h"
9 #include "utils.h"
10 
11 using std::string;
12 using std::to_string;
13 using std::endl;
14 using std::unique_ptr;
15 
16 /**
17  * @brief RuleManager::parseRule
18  * @param input
19  * @param output
20  * @param the rule to modify
21  * @param whether the 'prepend' flag was given in input
22  * @return
23  */
parseRule(Input input,Output output,Rule & rule,bool & prepend)24 int RuleManager::parseRule(Input input, Output output, Rule& rule, bool& prepend)
25 {
26     // Possible flags that apply to the rule as a whole:
27     std::map<string, bool> ruleFlags = {
28         {"once", false},
29         {"printlabel", false},
30         {"prepend", false},
31     };
32 
33     for (auto argIter = input.begin(); argIter != input.end(); argIter++) {
34         auto arg = *argIter;
35 
36         // Whether this argument is negated (only applies to conditions)
37         bool negated = false;
38 
39         // Check if arg is a flag for the whole rule
40         if (ruleFlags.count(arg)) {
41             ruleFlags[arg] = true;
42             continue;
43         }
44 
45         // Check if arg is a condition negator
46         if (arg == "not" || arg == "!") {
47             // Make sure there is another argument coming:
48             if (argIter + 1 == input.end()) {
49                 output << "Expected another argument after \""<< arg << "\" flag\n";
50                 return HERBST_INVALID_ARGUMENT;
51             }
52 
53             // Skip forward to next argument, but remember that it is negated:
54             negated = true;
55             arg = *(++argIter);
56         }
57 
58         // Tokenize arg, expect something like foo=bar or foo~bar:
59         char oper;
60         string lhs, rhs;
61         try {
62             std::tie(lhs, oper, rhs) = tokenizeArg(arg);
63         } catch (std::invalid_argument &error) {
64             output << input.command() << ": " << error.what() << endl;
65             return HERBST_INVALID_ARGUMENT;
66         }
67 
68         // Check if lhs is a condition name
69         if (Condition::matchers.count(lhs)) {
70             bool success = rule.addCondition(lhs, oper, rhs.c_str(), negated, output);
71             if (!success) {
72                 return HERBST_INVALID_ARGUMENT;
73             }
74             continue;
75         }
76 
77         // Check if lhs is a consequence name
78         if (Consequence::appliers.count(lhs)) {
79             if (oper == '~') {
80                 output << input.command() << ": Operator ~ not valid for consequence \"" << lhs << "\"\n";
81                 return HERBST_INVALID_ARGUMENT;
82             }
83 
84             bool success = rule.addConsequence(lhs, oper, rhs.c_str(), output);
85             if (!success) {
86                 return HERBST_INVALID_ARGUMENT;
87             }
88             continue;
89         }
90 
91 
92         // Check if arg is a custom label for this rule
93         if (lhs == "label") {
94             bool success = rule.setLabel(oper, rhs, output);
95             if (!success) {
96                 return HERBST_INVALID_ARGUMENT;
97             }
98             continue;
99         }
100 
101         output << input.command() << ": Unknown argument \"" << arg << "\"\n";
102         return HERBST_INVALID_ARGUMENT;
103     }
104 
105     // Store "once" flag in rule
106     rule.once = ruleFlags["once"];
107 
108     // Comply with "printlabel" flag
109     if (ruleFlags["printlabel"]) {
110        output << rule.label << "\n";
111     }
112     prepend = ruleFlags["prepend"];
113     return 0;
114 }
115 
addRuleCommand(Input input,Output output)116 int RuleManager::addRuleCommand(Input input, Output output) {
117     Rule rule;
118     bool prepend = false;
119 
120     // Assign default label (index will be incremented if adding the rule
121     // actually succeeds)
122     rule.label = to_string(rule_label_index_);
123     int status = parseRule(input, output, rule, prepend);
124     if (status != 0) {
125         return status;
126     }
127 
128     // At this point, adding the rule will be successful, so increment the
129     // label index (as it says in the docs):
130     rule_label_index_++;
131 
132     // Insert rule into list according to "prepend" flag
133     auto insertAt = prepend ? rules_.begin() : rules_.end();
134     rules_.insert(insertAt, make_unique<Rule>(rule));
135 
136     return HERBST_EXIT_SUCCESS;
137 }
138 
139 /*!
140  * Implements the "unrule" IPC command
141  */
unruleCommand(Input input,Output output)142 int RuleManager::unruleCommand(Input input, Output output) {
143     string arg;
144     if (!(input >> arg)) {
145         return HERBST_NEED_MORE_ARGS;
146     }
147 
148     if (arg == "--all" || arg == "-F") {
149         rules_.clear();
150         rule_label_index_ = 0;
151     } else {
152         // Remove rule specified by argument
153         auto removedCount = removeRules(arg);
154         if (removedCount == 0) {
155             output << "Couldn't find any rules with label \"" << arg << "\"";
156             return HERBST_INVALID_ARGUMENT;
157         }
158     }
159 
160     return HERBST_EXIT_SUCCESS;
161 }
162 
163 /*!
164  * Implements the "list_rules" IPC command
165  */
listRulesCommand(Output output)166 int RuleManager::listRulesCommand(Output output) {
167     for (auto& rule : rules_) {
168         rule->print(output);
169     }
170 
171     return HERBST_EXIT_SUCCESS;
172 }
173 
174 /*!
175  * Removes all rules with the given label
176  *
177  * \returns number of removed rules
178  */
removeRules(string label)179 size_t RuleManager::removeRules(string label) {
180     auto countBefore = rules_.size();
181 
182     for (auto ruleIter = rules_.begin(); ruleIter != rules_.end();) {
183         if ((*ruleIter)->label == label) {
184             ruleIter = rules_.erase(ruleIter);
185         } else {
186             ruleIter++;
187         }
188     }
189 
190     auto countAfter = rules_.size();
191 
192     return countAfter - countBefore;
193 }
194 
tokenizeArg(string arg)195 std::tuple<string, char, string> RuleManager::tokenizeArg(string arg) {
196     if (arg.substr(0, 2) == "--") {
197         arg.erase(0, 2);
198     }
199 
200     auto operPos = arg.find_first_of("~=");
201     if (operPos == string::npos) {
202         throw std::invalid_argument("No operator in given arg: " + arg);
203     }
204     auto lhs = arg.substr(0, operPos);
205     auto oper = arg[operPos];
206     auto rhs = arg.substr(operPos + 1);
207 
208     return std::make_tuple(lhs, oper, rhs);
209 }
210 
unruleCompletion(Completion & complete)211 void RuleManager::unruleCompletion(Completion& complete) {
212     complete.full({ "-F", "--all" });
213     for (auto& it : rules_) {
214         complete.full(it->label);
215     }
216 }
217 
addRuleCompletion(Completion & complete)218 void RuleManager::addRuleCompletion(Completion& complete) {
219     complete.full({ "not", "!", "prepend", "once", "printlabel" });
220     complete.partial("label=");
221     for (auto&& matcher : Condition::matchers) {
222         auto condName = matcher.first;
223         complete.partial(condName + "=");
224         complete.partial(condName + "~");
225     }
226     for (auto&& applier : Consequence::appliers) {
227         complete.partial(applier.first + "=");
228     }
229 }
230 
231 
232 //! Evaluate rules against a given client
evaluateRules(Client * client,Output output,ClientChanges changes)233 ClientChanges RuleManager::evaluateRules(Client* client, Output output, ClientChanges changes) {
234     // go through all rules and remove those that expired.
235     // Here, we use erase + remove_if because it uses a Forward Iterator
236     // and so it is ensured that the rules are evaluated in the correct order.
237     auto forEachRule = [&](unique_ptr<Rule>& rule) {
238         rule->evaluate(client, changes, output);
239         return rule->expired();
240     };
241     rules_.erase(std::remove_if(rules_.begin(), rules_.end(), forEachRule),
242                  rules_.end());
243     return changes;
244 }
245 
246 
247