• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GothenburgBitFactory / taskwarrior / 10813596573

11 Sep 2024 02:20PM CUT coverage: 84.841% (-0.02%) from 84.856%
10813596573

push

github

web-flow
Throw error when task config write is unsuccessfully (#3620)

4 of 6 new or added lines in 1 file covered. (66.67%)

2 existing lines in 1 file now uncovered.

18990 of 22383 relevant lines covered (84.84%)

23085.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.92
/src/commands/CmdConfig.cpp
1
////////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
4
//
5
// Permission is hereby granted, free of charge, to any person obtaining a copy
6
// of this software and associated documentation files (the "Software"), to deal
7
// in the Software without restriction, including without limitation the rights
8
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
// copies of the Software, and to permit persons to whom the Software is
10
// furnished to do so, subject to the following conditions:
11
//
12
// The above copyright notice and this permission notice shall be included
13
// in all copies or substantial portions of the Software.
14
//
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
// SOFTWARE.
22
//
23
// https://www.opensource.org/licenses/mit-license.php
24
//
25
////////////////////////////////////////////////////////////////////////////////
26

27
#include <cmake.h>
28
// cmake.h include header must come first
29

30
#include <CmdConfig.h>
31
#include <Context.h>
32
#include <JSON.h>
33
#include <format.h>
34
#include <shared.h>
35

36
#include <algorithm>
37
#include <sstream>
38

39
////////////////////////////////////////////////////////////////////////////////
40
CmdConfig::CmdConfig() {
4,495✔
41
  _keyword = "config";
4,495✔
42
  _usage = "task          config [name [value | '']]";
4,495✔
43
  _description = "Change settings in the task configuration";
4,495✔
44
  _read_only = true;
4,495✔
45
  _displays_id = false;
4,495✔
46
  _needs_gc = false;
4,495✔
47
  _uses_context = false;
4,495✔
48
  _accepts_filter = false;
4,495✔
49
  _accepts_modifications = false;
4,495✔
50
  _accepts_miscellaneous = true;
4,495✔
51
  _category = Command::Category::config;
4,495✔
52
}
4,495✔
53

54
////////////////////////////////////////////////////////////////////////////////
55
bool CmdConfig::setConfigVariable(const std::string& name, const std::string& value,
900✔
56
                                  bool confirmation /* = false */) {
57
  // Read .taskrc (or equivalent)
58
  std::vector<std::string> contents;
900✔
59
  File::read(Context::getContext().config.file(), contents);
900✔
60

61
  auto found = false;
900✔
62
  auto change = false;
900✔
63

64
  for (auto& line : contents) {
5,789✔
65
    // Get l-trimmed version of the line
66
    auto trimmed_line = trim(line, " ");
9,778✔
67

68
    // If there is a comment on the line, it must follow the pattern.
69
    auto comment = line.find('#');
4,889✔
70
    auto pos = trimmed_line.find(name + '=');
4,889✔
71

72
    // TODO: Use std::regex here
73
    if (pos == 0) {
4,889✔
74
      found = true;
56✔
75
      if (!confirmation ||
103✔
76
          confirm(format("Are you sure you want to change the value of '{1}' from '{2}' to '{3}'?",
206✔
77
                         name, Context::getContext().config.get(name), value))) {
103✔
78
        auto new_line = line.substr(0, pos + name.length() + 1) + json::encode(value);
112✔
79

80
        // Preserve the comment
81
        if (comment != std::string::npos) new_line += "  " + line.substr(comment);
56✔
82

83
        // Rewrite the line
84
        line = new_line;
56✔
85
        change = true;
56✔
86
      }
56✔
87
    }
88
  }
4,889✔
89

90
  // Not found, so append instead.
91
  if (!found &&
1,744✔
92
      (!confirmation ||
844✔
93
       confirm(format("Are you sure you want to add '{1}' with a value of '{2}'?", name, value)))) {
1,672✔
94
    contents.push_back(name + '=' + json::encode(value));
818✔
95
    change = true;
818✔
96
  }
97

98
  if (change)
900✔
99
    if (!File::write(Context::getContext().config.file(), contents))
874✔
NEW
100
      throw format("Could not write to '{1}'.", Context::getContext().config.file());
×
101

102
  return change;
900✔
103
}
900✔
104

105
////////////////////////////////////////////////////////////////////////////////
106
int CmdConfig::unsetConfigVariable(const std::string& name, bool confirmation /* = false */) {
21✔
107
  // Read .taskrc (or equivalent)
108
  std::vector<std::string> contents;
21✔
109
  File::read(Context::getContext().config.file(), contents);
21✔
110

111
  auto found = false;
21✔
112
  auto change = false;
21✔
113

114
  for (auto line = contents.begin(); line != contents.end();) {
140✔
115
    auto lineDeleted = false;
119✔
116

117
    // Get l-trimmed version of the line
118

119
    // If there is a comment on the line, it must follow the pattern.
120
    auto pos = trim(*line, " ").find(name + '=');
119✔
121

122
    // TODO: Use std::regex here
123
    if (pos == 0) {
119✔
124
      found = true;
12✔
125

126
      // Remove name
127
      if (!confirmation || confirm(format("Are you sure you want to remove '{1}'?", name))) {
12✔
128
        // vector::erase method returns a valid iterator to the next object
129
        line = contents.erase(line);
12✔
130
        lineDeleted = true;
12✔
131
        change = true;
12✔
132
      }
133
    }
134

135
    if (!lineDeleted) line++;
119✔
136
  }
137

138
  if (change)
21✔
139
    if (!File::write(Context::getContext().config.file(), contents))
12✔
NEW
140
      throw format("Could not write to '{1}'.", Context::getContext().config.file());
×
141

142
  if (change && found)
21✔
143
    return 0;
12✔
144
  else if (found)
9✔
145
    return 1;
×
146
  else
147
    return 2;
9✔
148
}
21✔
149

150
////////////////////////////////////////////////////////////////////////////////
151
int CmdConfig::execute(std::string& output) {
772✔
152
  auto rc = 0;
772✔
153
  std::stringstream out;
772✔
154

155
  // Get the non-attribute, non-fancy command line arguments.
156
  std::vector<std::string> words = Context::getContext().cli2.getWords();
772✔
157

158
  // Support:
159
  //   task config name value    # set name to value
160
  //   task config name ""       # set name to blank
161
  //   task config name          # remove name
162
  if (words.size()) {
772✔
163
    auto confirmation = Context::getContext().config.getBoolean("confirmation");
771✔
164
    auto found = false;
771✔
165

166
    auto name = words[0];
771✔
167
    std::string value = "";
771✔
168

169
    // Join the remaining words into config variable's value
170
    if (words.size() > 1) {
771✔
171
      for (unsigned int i = 1; i < words.size(); ++i) {
1,532✔
172
        if (i > 1) value += ' ';
766✔
173

174
        value += words[i];
766✔
175
      }
176
    }
177

178
    if (name != "") {
771✔
179
      auto change = false;
771✔
180

181
      // task config name value
182
      // task config name ""
183
      if (words.size() > 1) change = setConfigVariable(name, value, confirmation);
771✔
184

185
      // task config name
186
      else {
187
        rc = unsetConfigVariable(name, confirmation);
5✔
188
        if (rc == 0) {
5✔
189
          change = true;
4✔
190
          found = true;
4✔
191
        } else if (rc == 1)
1✔
192
          found = true;
×
193

194
        if (!found) throw format("No entry named '{1}' found.", name);
5✔
195
      }
196

197
      // Show feedback depending on whether .taskrc has been rewritten
198
      if (change) {
770✔
199
        out << format("Config file {1} modified.", Context::getContext().config.file()) << '\n';
770✔
200
      } else
201
        out << "No changes made.\n";
×
202
    } else
203
      throw std::string("Specify the name of a config variable to modify.");
×
204

205
    output = out.str();
770✔
206
  } else
772✔
207
    throw std::string("Specify the name of a config variable to modify.");
1✔
208

209
  return rc;
770✔
210
}
774✔
211

212
////////////////////////////////////////////////////////////////////////////////
213
CmdCompletionConfig::CmdCompletionConfig() {
4,495✔
214
  _keyword = "_config";
4,495✔
215
  _usage = "task          _config";
4,495✔
216
  _description = "Lists all supported configuration variables, for completion purposes";
4,495✔
217
  _read_only = true;
4,495✔
218
  _displays_id = false;
4,495✔
219
  _needs_gc = false;
4,495✔
220
  _uses_context = false;
4,495✔
221
  _accepts_filter = false;
4,495✔
222
  _accepts_modifications = false;
4,495✔
223
  _accepts_miscellaneous = false;
4,495✔
224
  _category = Command::Category::internal;
4,495✔
225
}
4,495✔
226

227
////////////////////////////////////////////////////////////////////////////////
228
int CmdCompletionConfig::execute(std::string& output) {
1✔
229
  auto configs = Context::getContext().config.all();
1✔
230
  std::sort(configs.begin(), configs.end());
1✔
231

232
  for (const auto& config : configs) output += config + '\n';
246✔
233

234
  return 0;
1✔
235
}
1✔
236

237
////////////////////////////////////////////////////////////////////////////////
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc