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

GothenburgBitFactory / taskwarrior / 10152339701

29 Jul 2024 09:45PM UTC coverage: 84.437% (+0.07%) from 84.372%
10152339701

push

github

web-flow
Merge pull request #3566 from felixschurk/add-clang-format

Add clang-format to enforce style guide

12359 of 13760 new or added lines in 147 files covered. (89.82%)

123 existing lines in 42 files now uncovered.

19070 of 22585 relevant lines covered (84.44%)

19724.02 hits per line

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

98.98
/src/commands/CmdImport.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 <CmdImport.h>
31
#include <CmdModify.h>
32
#include <Context.h>
33
#include <format.h>
34
#include <shared.h>
35
#include <util.h>
36

37
#include <iostream>
38
#include <unordered_map>
39

40
////////////////////////////////////////////////////////////////////////////////
41
CmdImport::CmdImport() {
4,389✔
42
  _keyword = "import";
4,389✔
43
  _usage = "task          import [<file> ...]";
4,389✔
44
  _description = "Imports JSON files";
4,389✔
45
  _read_only = false;
4,389✔
46
  _displays_id = false;
4,389✔
47
  _needs_gc = false;
4,389✔
48
  _uses_context = false;
4,389✔
49
  _accepts_filter = false;
4,389✔
50
  _accepts_modifications = false;
4,389✔
51
  _accepts_miscellaneous = true;
4,389✔
52
  _category = Command::Category::migration;
4,389✔
53
}
4,389✔
54

55
////////////////////////////////////////////////////////////////////////////////
56
int CmdImport::execute(std::string&) {
52✔
57
  auto rc = 0;
52✔
58
  auto count = 0;
52✔
59

60
  // Get filenames from command line arguments.
61
  auto words = Context::getContext().cli2.getWords();
52✔
62
  if (!words.size() || (words.size() == 1 && words[0] == "-")) {
52✔
63
    std::cout << format("Importing '{1}'\n", "STDIN");
46✔
64

65
    std::string json;
46✔
66
    std::string line;
46✔
67
    while (std::getline(std::cin, line)) json += line + '\n';
327✔
68

69
    if (nontrivial(json)) count = import(json);
46✔
70
  } else {
52✔
71
    // Import tasks from all specified files.
72
    for (auto& word : words) {
11✔
73
      File incoming(word);
6✔
74
      if (!incoming.exists()) throw format("File '{1}' not found.", word);
6✔
75

76
      std::cout << format("Importing '{1}'\n", word);
5✔
77

78
      // Load the file.
79
      std::string json;
5✔
80
      incoming.read(json);
5✔
81
      if (nontrivial(json)) count += import(json);
5✔
82
    }
6✔
83
  }
84

85
  Context::getContext().footnote(format("Imported {1} tasks.", count));
45✔
86

87
  // Warn the user about multiple occurrences of the same UUID.
88
  auto duplicates = false;
45✔
89
  for (const auto& [uuid, occurrences] : uuid_occurrences) {
1,638✔
90
    if (occurrences > 1) {
1,593✔
91
      duplicates = true;
1✔
92
      Context::getContext().footnote(
2✔
93
          format("Input contains UUID '{1}' {2} times.", uuid, occurrences));
2✔
94
    }
95
  }
96
  if (duplicates)
45✔
97
    Context::getContext().footnote(
1✔
98
        "Tasks with the same UUID have been merged. Please check the results.");
99

100
  return rc;
45✔
101
}
52✔
102

103
////////////////////////////////////////////////////////////////////////////////
104
int CmdImport::import(const std::string& input) {
51✔
105
  auto count = 0;
51✔
106
  try {
107
    json::value* root = json::parse(input);
51✔
108
    if (root) {
46✔
109
      // Single object parse. Input looks like:
110
      //   { ... }
111
      if (root->type() == json::j_object) {
46✔
112
        // For each object element...
113
        auto root_obj = (json::object*)root;
16✔
114
        importSingleTask(root_obj);
16✔
115
        ++count;
11✔
116
      }
117

118
      // Multiple object array. Input looks like:
119
      //   [ { ... } , { ... } ]
120
      else if (root->type() == json::j_array) {
30✔
121
        auto root_arr = (json::array*)root;
30✔
122

123
        // For each object element...
124
        for (auto& element : root_arr->_data) {
210✔
125
          // For each object element...
126
          auto root_obj = (json::object*)element;
181✔
127
          importSingleTask(root_obj);
181✔
128
          ++count;
180✔
129
        }
130
      }
131

132
      delete root;
40✔
133
    }
134
  }
135

136
  // If an exception is caught, then it is because the free-form JSON
137
  // objects/array above failed to parse. This may be an indication that the input
138
  // is an old-style line-by-line set of JSON objects, because both an array of
139
  // objects, and a single object have failed to parse..
140
  //
141
  // Input looks like:
142
  //   { ... }
143
  //   { ... }
144
  catch (std::string& e) {
11✔
145
    // Make a very cursory check for the old-style format.
146
    if (input[0] != '{') {
11✔
147
      throw e;
1✔
148
    }
149

150
    // Read the entire file, so that errors do not result in a partial import.
151
    std::vector<std::unique_ptr<json::object>> objects;
10✔
152
    for (auto& line : split(input, '\n')) {
1,428✔
153
      if (line.length()) {
1,418✔
154
        json::value* root = json::parse(line);
1,408✔
155
        if (root && root->type() == json::j_object)
1,408✔
156
          objects.push_back(std::unique_ptr<json::object>((json::object*)root));
1,408✔
157
        else
NEW
158
          throw format("Invalid JSON: {1}", line);
×
159
      }
160
    }
10✔
161

162
    // Import the tasks.
163
    for (auto& root : objects) {
1,413✔
164
      importSingleTask(root.get());
1,408✔
165
      ++count;
1,403✔
166
    }
167
  }
16✔
168

169
  return count;
45✔
170
}
171

172
////////////////////////////////////////////////////////////////////////////////
173
void CmdImport::importSingleTask(json::object* obj) {
1,605✔
174
  // Parse the whole thing, validate the data.
175
  Task task(obj);
1,605✔
176

177
  auto hasGeneratedEntry = not task.has("entry");
1,603✔
178
  auto hasExplicitEnd = task.has("end");
1,603✔
179

180
  task.validate();
1,603✔
181

182
  auto hasGeneratedEnd = not hasExplicitEnd and task.has("end");
1,594✔
183

184
  // Check whether the imported task is new or a modified existing task.
185
  Task before;
1,594✔
186
  auto uuid = task.get("uuid");
3,188✔
187
  uuid_occurrences[uuid]++;
1,594✔
188
  if (Context::getContext().tdb2.get(uuid, before)) {
1,594✔
189
    // We need to neglect updates from attributes with dynamic defaults
190
    // unless they have been explicitly specified on import.
191
    //
192
    // There are three attributes with dynamic defaults, besides uuid:
193
    //   - modified: Ignored in any case.
194
    //   - entry: Ignored if generated.
195
    //   - end: Ignored if generated.
196

197
    // The 'modified' attribute is ignored in any case, since if it
198
    // were the only difference between the tasks, it would have been
199
    // neglected anyway, since it is bumped on each modification.
200
    task.set("modified", before.get("modified"));
8✔
201

202
    // Other generated values are replaced by values from existing task,
203
    // so that they are ignored on comparison.
204
    if (hasGeneratedEntry) task.set("entry", before.get("entry"));
8✔
205

206
    if (hasGeneratedEnd) task.set("end", before.get("end"));
8✔
207

208
    if (before != task) {
8✔
209
      CmdModify modHelper;
7✔
210
      modHelper.checkConsistency(before, task);
7✔
211
      modHelper.modifyAndUpdate(before, task);
7✔
212
      std::cout << " mod  ";
7✔
213
    } else {
7✔
214
      std::cout << " skip ";
1✔
215
    }
216
  } else {
217
    Context::getContext().tdb2.add(task);
1,586✔
218
    std::cout << " add  ";
1,586✔
219
  }
220

221
  std::cout << task.get("uuid") << ' ' << task.get("description") << '\n';
1,594✔
222
}
1,603✔
223

224
////////////////////////////////////////////////////////////////////////////////
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