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

KittensBasket / dasboot / 15101902050

19 May 2025 12:52AM UTC coverage: 80.408% (-3.6%) from 83.958%
15101902050

Pull #48

github

web-flow
Merge e3fddfb2d into f83ca2bc3
Pull Request #48: build and exec command upd

139 of 240 branches covered (57.92%)

Branch coverage included in aggregate %.

267 of 318 new or added lines in 5 files covered. (83.96%)

104 existing lines in 4 files now uncovered.

1400 of 1674 relevant lines covered (83.63%)

14.55 hits per line

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

72.6
/dasboot/cli/cli.cpp
1
#include <dasboot/cli/cli.hpp>
2

3
namespace NCli {
4
    using std::string;
5

6
    TParser::TParser(const string& description)
7
    : App(description)
50✔
8
    {}
50✔
9

10
    string TParser::BuildFullName(const string& shortName, const string& longName) {
918✔
11
        return shortName + "," + longName;
918✔
12
    }
918✔
13

14
    void TParser::AddGlobalFlag(const string& shortName, const string& longName, bool& flag, const string& description) {
50✔
15
        App.add_flag(BuildFullName(shortName, longName), flag, description);
50✔
16
    }
50✔
17

18
    void TParser::AddGlobalOption(const string& shortName, const string& longName, TValue& value, const string& description) {
×
19
        App.add_option(BuildFullName(shortName, longName), value, description);
×
20
    }
×
21

22
    void TParser::AddGlobalOption(const string& shortName, const string& longName, TValue& value, const string& description, CLI::Validator&& validator) {
×
23
        App.add_option(BuildFullName(shortName, longName), value, description)->transform(std::move(validator));
×
24
    }
×
25

26
    void TParser::AddGlobalCommand(const string& commandName, const string& description){
434✔
27
        Commands[commandName] = App.add_subcommand(commandName, description);
434✔
28
    }
434✔
29

30
    void TParser::AddLocalFlag(const string& commandName, const string& shortName, const string& longName, bool& flag, const string& description) {
194✔
31
        Commands[commandName]->add_option(BuildFullName(shortName, longName), flag, description);
194✔
32
    }
194✔
33

34
    void TParser::AddLocalOption(const string& commandName, const string& shortName, const string& longName, TValue& value, const string& description) {
674✔
35
        Commands[commandName]->add_option(BuildFullName(shortName, longName), value, description);
674✔
36
    }
674✔
37

38
    void TParser::AddLocalOption(const string& commandName, const string& shortName, const string& longName, TValue& value, const string& description, CLI::Validator&& validator) {
×
39
        Commands[commandName]->add_option(BuildFullName(shortName, longName), value, description)->transform(validator);
×
40
    }
×
41

42
    string TParser::FindCalledCommand() {
×
43
        for (const auto& [command, interface] : Commands) {
×
44
            if (interface->parsed() && (!interface->get_help_ptr()->as<bool>())) {
×
45
                return command;
×
46
            }
×
47
        }
×
48
        return "";
×
49
    }
×
50
    
51
    int TParser::Parse(int argc, char* argv[]) {
46✔
52
        CLI11_PARSE(App, argc, argv);
46✔
53
        return 0;
46✔
54
    }
46✔
55

56
    string TParser::GetHelp() const {
4✔
57
        return App.help();
4✔
58
    }
4✔
59

60
    namespace {
61
        // Dasboot description
62
        static const string DasbootDescription = "A small containerization utility, written in C/C++. Made as team pet project.";
63
    
64
        // Common option descriptions
65
        static const string ContainerNameDescription = "Container name";
66
        static const string ContainerIdDescription = "Container ID";
67
        static const string BuildFromFileDescription = "Create a container from a DasbootFile";
68
        static const string ShowAllContainersDescription = "List all containers, including stopped ones.";
69
        static const string DetachFlagDescription = "Detached mode: run command in the background";
70
        static const string NoStdinFlagDescription = "Do not attach STDIN";
71
        static const string ExecFileDescription = "Path to ExecFile";
72
        static const string InteractiveContainerDescription = "Run container in interactive mode"; //maybe edit
73

74
        // Command descriptions
75
        static const string VersionDescription = "Print version information and quit";
76
        static const string InfoDescription = "Display system-wide information";
77
        static const string BuildDescription = "Create a container";
78
        static const string RunDescription = "Run container in interactive mode";
79
        static const string StartDescription = "Launch a container by name or ID depending on specified options";
80
        static const string StopDescription = "Terminate a running container";
81
        static const string PsDescription = "Display all available containers.";
82
        static const string RmDescription = "Delete a container by name or ID depending on specified options";
83
        static const string ExecDescription = "Run one or multiple commands inside an already running container";
84
        static const string AttachDescription = "Connect a terminal to a running container by its ID or name for interactive management and output monitoring";
85
    } // anonymous namespace
86

87
    void TParser::RegisterCommands(TMainSettings& mainSettings) {
48✔
88
        AddGlobalFlag("-v", "--version", mainSettings.Version.PrintVersion, VersionDescription);
48✔
89

90
        AddGlobalCommand("info", InfoDescription);
48✔
91

92
        AddGlobalCommand("build", BuildDescription);
48✔
93
        AddLocalOption("build", "-n", "--name", mainSettings.BuildOptions.Name, ContainerNameDescription);
48✔
94
        AddLocalOption("build", "-f", "--file", mainSettings.BuildOptions.PathToDasbootFile, BuildFromFileDescription);
48✔
95

96
        AddGlobalCommand("run", RunDescription);
48✔
97
        AddLocalOption("run", "-n", "--name", mainSettings.RunOptions.Name, ContainerNameDescription);
48✔
98

99
        AddGlobalCommand("start", StartDescription);
48✔
100
        AddLocalOption("start", "-n", "--name",  mainSettings.StartOptions.Name, ContainerNameDescription);
48✔
101
        AddLocalOption("start", "-i", "--id",  mainSettings.StartOptions.Id, ContainerIdDescription);
48✔
102

103
        AddGlobalCommand("stop", StopDescription);
48✔
104
        AddLocalOption("stop", "-n", "--name", mainSettings.StopOptions.Name, ContainerNameDescription);
48✔
105
        AddLocalOption("stop", "-i", "--id", mainSettings.StopOptions.Id, ContainerIdDescription);
48✔
106

107
        AddGlobalCommand("ps", PsDescription);
48✔
108
        AddLocalFlag("ps", "-a", "--all", mainSettings.PsOptions.ShowAll, ShowAllContainersDescription);
48✔
109

110
        AddGlobalCommand("rm", RmDescription);
48✔
111
        AddLocalOption("rm", "-n", "--name", mainSettings.RmOptions.Name, ContainerNameDescription);
48✔
112
        AddLocalOption("rm", "-i", "--id", mainSettings.RmOptions.Id, ContainerIdDescription);
48✔
113

114
        AddGlobalCommand("exec", ExecDescription);
48✔
115
        AddLocalOption("exec", "-n", "--name", mainSettings.ExecOptions.Name, ContainerNameDescription);
48✔
116
        AddLocalOption("exec", "-i", "--id", mainSettings.ExecOptions.Id, ContainerIdDescription);
48✔
117
        AddLocalFlag("exec", "", "--interactive", mainSettings.ExecOptions.IsInteractive, InteractiveContainerDescription);
48✔
118
        AddLocalOption("exec", "-f", "--file", mainSettings.ExecOptions.ExecFile, ExecFileDescription);
48✔
119
        AddLocalFlag("exec", "-d", "--detach", mainSettings.ExecOptions.Detach, DetachFlagDescription);
48✔
120

121
        AddGlobalCommand("attach", AttachDescription);
48✔
122
        AddLocalOption("attach", "-n", "--name", mainSettings.AttachOptions.Name, ContainerNameDescription);
48✔
123
        AddLocalOption("attach", "-i", "--id", mainSettings.AttachOptions.Id, ContainerIdDescription);
48✔
124
        AddLocalFlag("attach", "", "--no-stdin", mainSettings.AttachOptions.NoStdin, NoStdinFlagDescription);
48✔
125
    }
48✔
126

127
    TSender::TSender(const string adress)
128
    : Controller(adress) {}
14✔
129

130
    void TSender::SendMainSettings(const TMainSettings& mainSettings, const string& command) {
14✔
131
        if (mainSettings.Version.PrintVersion) {
14!
132
            //print version -- function in controller
133
        }
×
134

135
        else if (command == "info") {
14!
136
            //controller handle call
137
        }
×
138

139
        else if (command == "build") {
14✔
140
            NMessages::TBuildOptions ProtoBuildOptions;
2✔
141
            ProtoBuildOptions = TConverter::ConvertBuildOptions(mainSettings.BuildOptions, ProtoBuildOptions);
2✔
142
            Controller.Build(ProtoBuildOptions);
2✔
143
        } 
2✔
144

145
        else if (command == "run") {
12✔
146
            NMessages::TRunOptions ProtoRunOptions;
2✔
147
            ProtoRunOptions = TConverter::ConvertRunOptions(mainSettings.RunOptions, ProtoRunOptions);
2✔
148
            Controller.Run(ProtoRunOptions);
2✔
149
        } 
2✔
150

151
        else if (command == "start") {
10✔
152
            NMessages::TStartOptions ProtoStartOptions;
2✔
153
            ProtoStartOptions = TConverter::ConvertStartOptions(mainSettings.StartOptions, ProtoStartOptions);
2✔
154
            Controller.Start(ProtoStartOptions);
2✔
155
        }
2✔
156

157
        else if (command == "stop") {
8✔
158
            NMessages::TStopOptions ProtoStopOptions;
2✔
159
            ProtoStopOptions = TConverter::ConvertStopOptions(mainSettings.StopOptions, ProtoStopOptions);
2✔
160
            Controller.Stop(ProtoStopOptions);
2✔
161
        }
2✔
162

163
        else if (command == "ps") {
6✔
164
            NMessages::TPsOptions ProtoPsOptions;
2✔
165
            ProtoPsOptions = TConverter::ConvertPsOptions(mainSettings.PsOptions, ProtoPsOptions);
2✔
166
            Controller.Ps(ProtoPsOptions);
2✔
167
        }
2✔
168

169
        else if (command == "rm") {
4✔
170
            NMessages::TRmOptions ProtoRmOptions;
2✔
171
            ProtoRmOptions = TConverter::ConvertRmOptions(mainSettings.RmOptions, ProtoRmOptions);
2✔
172
            Controller.Rm(ProtoRmOptions);
2✔
173
        }
2✔
174

175
        else if (command == "exec") {
2!
176
            NMessages::TExecOptions ProtoExecOptions;
2✔
177
            ProtoExecOptions = TConverter::ConvertExecOptions(mainSettings.ExecOptions, ProtoExecOptions);
2✔
178
            Controller.Exec(ProtoExecOptions);
2✔
179
        }
2✔
180

181
        else if (command == "attach") {
×
182
            NMessages::TAttachOptions ProtoAttachOptions;
×
183
            ProtoAttachOptions = TConverter::ConvertAttachOptions(mainSettings.AttachOptions, ProtoAttachOptions);
×
184
            //controller handle call
185
        }
×
186

187
        else {
×
188
            throw std::runtime_error("Unknown command");
×
189
        }
×
190

191
        
192
    }
14✔
193

194
    string GetReadFileResult(const string& path) {
20✔
195
        auto [result, status] = NOs::ReadFile(path);
20✔
196
        auto [statusCode, errorMsg] = status;
20✔
197
        if (statusCode == NCommon::TStatus::ECode::Failed) {
20!
NEW
198
            throw std::runtime_error("Error: " + errorMsg);
×
NEW
199
        }
×
200
        return result;
20✔
201
    }
20✔
202

203
    string TConverter::ReadDasbootFile(const string& path) {
6✔
204
        std::ifstream DasbootFile(path);
6✔
205
        nlohmann::json jsonDasbootFile, resultJson;
6✔
206
        std::vector<string> CopyFile;
6✔
207

208
        try {
6✔
209
            jsonDasbootFile = nlohmann::json::parse(DasbootFile);
6✔
210
        } catch (const nlohmann::json::parse_error& e) {
6✔
NEW
211
            throw std::runtime_error("JSON parse error: " + string(e.what()));
×
NEW
212
        } catch (const std::exception& e) {
×
NEW
213
            throw std::runtime_error("Error reading JSON: " + string(e.what()));
×
NEW
214
        }
×
215

216
        if (jsonDasbootFile.contains("network")) {
6!
217
            if (!jsonDasbootFile["network"].is_boolean()) {
6!
NEW
218
                throw std::runtime_error("Field 'network' must be boolean (true or false)");
×
NEW
219
            }
×
220
            else {
6✔
221
                resultJson["network"] = jsonDasbootFile["network"];
6✔
222
            }
6✔
223
        }
6✔
224

225
        if (jsonDasbootFile.contains("script_file") && !jsonDasbootFile["script_file"].is_null()) {
6!
226
            if (jsonDasbootFile["script_file"].is_string()) {
6!
227
                string result = GetReadFileResult(jsonDasbootFile["script_file"]);
6✔
228
                resultJson["script_code"] = result;
6✔
229
            } 
6✔
230
        }
6✔
231

232
        if (jsonDasbootFile.contains("copy_file") && !jsonDasbootFile["copy_file"].is_null()) {
6!
NEW
233
            if (jsonDasbootFile["copy_file"].is_string()) {
×
NEW
234
                string result = GetReadFileResult(jsonDasbootFile["copy_file"]);
×
NEW
235
                CopyFile.push_back(result);
×
NEW
236
            } 
×
NEW
237
            else if (jsonDasbootFile["copy_file"].is_array()) {
×
NEW
238
                for (const auto& CodePath : jsonDasbootFile["copy_file"]) {
×
NEW
239
                    if (CodePath.is_string()) {
×
NEW
240
                        string result = GetReadFileResult(CodePath);
×
NEW
241
                        CopyFile.push_back(result);
×
NEW
242
                    } else {
×
NEW
243
                        throw std::runtime_error("Error: non-string element in copy_file array");
×
NEW
244
                    }
×
NEW
245
                }
×
NEW
246
            }
×
NEW
247
            resultJson["copy_file"] = CopyFile;
×
NEW
248
        }
×
249

250
        return resultJson.dump();
6✔
251
    }
6✔
252

253
    string TConverter::ReadExecFile(const string& path, const bool& isInteractive) {
6✔
254
        std::ifstream ExecFile(path);
6✔
255
        nlohmann::json jsonExecFile, resultJson;
6✔
256
        string pathToScript, pathToCopyFile;
6✔
257
        std::vector<string> CopyFile;
6✔
258
        
259
        try {
6✔
260
            jsonExecFile = nlohmann::json::parse(ExecFile);
6✔
261
        } catch (const nlohmann::json::parse_error& e) {
6✔
NEW
262
            throw std::runtime_error("JSON parse error: " + string(e.what()));
×
NEW
263
        } catch (const std::exception& e) {
×
NEW
264
            throw std::runtime_error("Error reading JSON: " + string(e.what()));
×
NEW
265
        }
×
266

267
        if (jsonExecFile.contains("network")) {
6!
268
            if (!jsonExecFile["network"].is_boolean()) {
6!
NEW
269
                throw std::runtime_error("Field 'network' must be boolean (true or false)");
×
NEW
270
            }
×
271
            else {
6✔
272
                resultJson["network"] = jsonExecFile["network"];
6✔
273
            }
6✔
274
        }
6✔
275

276
        if (jsonExecFile.contains("copy_file")) {
6!
277
            if (jsonExecFile["copy_file"].is_string()) {
6✔
278
                string result = GetReadFileResult(jsonExecFile["copy_file"]);
4✔
279
                resultJson["copy_file"] = result;
4✔
280
            } 
4✔
281
            else if (jsonExecFile["copy_file"].is_array()) {
2!
282
                for (const auto& CodePath : jsonExecFile["copy_file"]) {
4✔
283
                    if (CodePath.is_string()) {
4!
284
                        string result = GetReadFileResult(CodePath);
4✔
285
                        CopyFile.push_back(result);
4✔
286
                    } else {
4✔
NEW
287
                        throw std::runtime_error("Error: non-string element in copy_file array");
×
NEW
288
                    }
×
289
                }
4✔
290
                resultJson["copy_file"] = CopyFile;
2✔
291
            }
2✔
292
        }
6✔
293

294
        if (jsonExecFile.contains("script_file")) {
6!
295
            if (!isInteractive) {
6!
296
                string result = GetReadFileResult(jsonExecFile["script_file"]);
6✔
297
                resultJson["script_code"] = result;
6✔
298
            } 
6✔
NEW
299
            else {
×
NEW
300
                throw std::runtime_error("When --interactive flag is used, 'script_file' field must not be present");
×
NEW
301
            }
×
302
        } else if (!jsonExecFile.contains("script_file") && !isInteractive) {
6!
NEW
303
            throw std::runtime_error("There must be 'script_file' field");
×
NEW
304
        }
×
305

306
        return resultJson.dump();
6✔
307
    }
6✔
308
    
309
    NMessages::TBuildOptions TConverter::ConvertBuildOptions(const NCli::TBuildOptions& options, NMessages::TBuildOptions& protoOptions) {
6✔
310
        if (options.Name.has_value()) {
6✔
311
            protoOptions.set_name(options.Name.value());
4✔
312
        }
4✔
313

314
        if (options.PathToDasbootFile.has_value()) {
6!
315
            string DasbootFile = ReadDasbootFile(options.PathToDasbootFile.value());
6✔
316
            protoOptions.set_dasboot_file(DasbootFile);
6✔
317
        }
6✔
NEW
318
        else {
×
NEW
319
            throw std::runtime_error("Path to DasbootFile must be specified");
×
UNCOV
320
        }
×
321
        return protoOptions;
6✔
322
    }
6✔
323

324
    NMessages::TRunOptions TConverter::ConvertRunOptions(const NCli::TRunOptions& options, NMessages::TRunOptions& protoOptions) {
8✔
325
        if (options.Name.has_value()) {
8✔
326
            protoOptions.set_name(options.Name.value());
6✔
327
        }
6✔
328

329
        return protoOptions;
8✔
330
    }
8✔
331

332
    NMessages::TStartOptions TConverter::ConvertStartOptions(const NCli::TStartOptions& options, NMessages::TStartOptions& protoOptions) {
8✔
333
        if (options.Name.has_value()) {
8✔
334
            protoOptions.set_name(options.Name.value());
4✔
335
        }
4✔
336

337
        if (options.Id.has_value()) {
8✔
338
            protoOptions.set_id(options.Id.value());
2✔
339
        }
2✔
340

341
        return protoOptions;
8✔
342
    }
8✔
343
    
344
    NMessages::TStopOptions TConverter::ConvertStopOptions(const NCli::TStopOptions& options, NMessages::TStopOptions& protoOptions) {
10✔
345
        if (options.Name.has_value()) {
10✔
346
            protoOptions.set_name(options.Name.value());
6✔
347
        }
6✔
348

349
        if (options.Id.has_value()) {
10✔
350
            protoOptions.set_id(options.Id.value());
4✔
351
        }
4✔
352

353
        return protoOptions;
10✔
354
    }
10✔
355
    
356
    NMessages::TPsOptions TConverter::ConvertPsOptions(const NCli::TPsOptions& options, NMessages::TPsOptions& protoOptions) {
6✔
357
        if (options.ShowAll) {
6✔
358
            protoOptions.set_show_all(options.ShowAll);
4✔
359
        }
4✔
360

361
        return protoOptions;
6✔
362
    }
6✔
363
    
364
    NMessages::TRmOptions TConverter::ConvertRmOptions(const NCli::TRmOptions& options, NMessages::TRmOptions& protoOptions) {
10✔
365
        if (options.Name.has_value()) {
10✔
366
            protoOptions.set_name(options.Name.value());
4✔
367
        }
4✔
368

369
        if (options.Id.has_value()) {
10✔
370
            protoOptions.set_id(options.Id.value());
6✔
371
        }
6✔
372

373
        return protoOptions;
10✔
374
    }
10✔
375
    
376
    NMessages::TExecOptions TConverter::ConvertExecOptions(const NCli::TExecOptions& options, NMessages::TExecOptions& protoOptions) {
10✔
377
        if (options.Name.has_value()) {
10✔
378
            protoOptions.set_name(options.Name.value());
4✔
379
        }
4✔
380

381
        if (options.Id.has_value()) {
10✔
382
            protoOptions.set_id(options.Id.value());
4✔
383
        }
4✔
384

385
        if (options.IsInteractive) {
10✔
386
            if (options.ExecFile.has_value()) {
4!
NEW
387
                string result = ReadExecFile(options.ExecFile.value(), true);
×
NEW
388
                protoOptions.set_exec_file(result);
×
NEW
389
            }
×
390
            protoOptions.set_is_interactive(options.IsInteractive);
4✔
391
        }
4✔
392
        else if (options.ExecFile.has_value()) {
6!
393
            string result = ReadExecFile(options.ExecFile.value(), false);
6✔
394
            protoOptions.set_exec_file(result);
6✔
395
        }
6✔
NEW
396
        else {
×
NEW
397
            throw std::runtime_error("You forgot flag --interactive or ExecFile");
×
NEW
398
        }
×
399

400
        if (options.Detach) {
10✔
401
            protoOptions.set_detach(options.Detach);
4✔
402
        }
4✔
403

404
        return protoOptions;
10✔
405
    }
10✔
406
    
407
    NMessages::TAttachOptions TConverter::ConvertAttachOptions(const NCli::TAttachOptions& options, NMessages::TAttachOptions& protoOptions) {
2✔
408
        if (options.Name.has_value()) {
2!
409
            protoOptions.set_name(options.Name.value());
2✔
410
        }
2✔
411

412
        if (options.Id.has_value()) {
2!
413
            protoOptions.set_id(options.Id.value());
2✔
414
        }
2✔
415

416
        if (options.NoStdin) {
2!
417
            protoOptions.set_nostdin(options.NoStdin);
2✔
418
        }
2✔
419

420
        return protoOptions;
2✔
421
    }
2✔
422

423
    std::unique_ptr<TParser> MakeDasbootParser(TMainSettings& settings) {
48✔
424
        std::unique_ptr<TParser> parser(new TParser{DasbootDescription});
48✔
425
        parser->RegisterCommands(settings);
48✔
426
        return parser;
48✔
427
    }
48✔
428
}; // namespace NCli
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

© 2026 Coveralls, Inc