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

KittensBasket / dasboot / 15100462231

18 May 2025 10:02PM UTC coverage: 84.293% (+0.3%) from 83.958%
15100462231

Pull #48

github

web-flow
Merge 6493b0af2 into f3492c2b0
Pull Request #48: build and exec command upd

143 of 228 branches covered (62.72%)

Branch coverage included in aggregate %.

325 of 362 new or added lines in 5 files covered. (89.78%)

10 existing lines in 2 files now uncovered.

1467 of 1682 relevant lines covered (87.22%)

14.69 hits per line

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

77.43
/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)
56✔
8
    {}
56✔
9

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

14
    void TParser::AddGlobalFlag(const string& shortName, const string& longName, bool& flag, const string& description) {
56✔
15
        App.add_flag(BuildFullName(shortName, longName), flag, description);
56✔
16
    }
56✔
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){
488✔
27
        Commands[commandName] = App.add_subcommand(commandName, description);
488✔
28
    }
488✔
29

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

34
    void TParser::AddLocalOption(const string& commandName, const string& shortName, const string& longName, TValue& value, const string& description) {
758✔
35
        Commands[commandName]->add_option(BuildFullName(shortName, longName), value, description);
758✔
36
    }
758✔
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[]) {
52✔
52
        CLI11_PARSE(App, argc, argv);
52✔
53
        return 0;
52✔
54
    }
52✔
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

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

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

89
        AddGlobalCommand("info", InfoDescription);
54✔
90

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

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

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

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

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

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

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

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

125
    TSender::TSender(const string adress)
126
    : Controller(adress) {}
14✔
127

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

133
        else if (command == "info") {
14!
134
            //controller handle call
UNCOV
135
        }
×
136

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

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

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

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

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

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

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

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

185
        else {
×
UNCOV
186
            throw std::runtime_error("Unknown command");
×
187
        }
×
188

189
        
190
    }
14✔
191

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

201
    string TConverter::ReadDasbootFile(const string& path) {
8✔
202
        std::ifstream DasbootFile(path);
8✔
203
        nlohmann::json jsonDasbootFile, resultJson;
8✔
204
        std::vector<string> ScriptsCode;
8✔
205

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

214
        if (!jsonDasbootFile.contains("network") && jsonDasbootFile["network"].is_null()) {
8!
NEW
215
            throw std::runtime_error("Field 'network' is missing or null");
×
216
        } else if (!jsonDasbootFile["network"].is_boolean()) {
8!
NEW
217
            throw std::runtime_error("Field 'network' must be boolean (true or false)");
×
NEW
218
        }
×
219

220
        resultJson["network"] = jsonDasbootFile["network"];
8✔
221

222
        if (jsonDasbootFile.contains("script_file") && !jsonDasbootFile["script_file"].is_null()) {
8!
223
            if (jsonDasbootFile["script_file"].is_string()) {
8✔
224
                string result = GetReadFileResult(jsonDasbootFile["script_file"]);
6✔
225
                ScriptsCode.push_back(result);
6✔
226
            } 
6✔
227
            else if (jsonDasbootFile["script_file"].is_array()) {
2!
228
                for (const auto& scriptPath : jsonDasbootFile["script_file"]) {
4✔
229
                    if (scriptPath.is_string()) {
4!
230
                        string result = GetReadFileResult(scriptPath);
4✔
231
                        ScriptsCode.push_back(result);
4✔
232
                    } else {
4✔
NEW
233
                        throw std::runtime_error("Error: non-string element in script_file array");
×
NEW
234
                    }
×
235
                }
4✔
236
            } 
2✔
NEW
237
            else {
×
NEW
238
                throw std::runtime_error("Field 'script_file' must be either string or array");
×
NEW
239
            }
×
240
        } else {
8✔
NEW
241
            throw std::runtime_error("Field 'script_file' is missing or null");
×
NEW
242
        }
×
243

244

245
        resultJson["script_code"] = ScriptsCode;
8✔
246

247
        return resultJson.dump();
8✔
248
    }
8✔
249

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

264
        if (!jsonExecFile.contains("network") && jsonExecFile["network"].is_null()) {
6!
NEW
265
            throw std::runtime_error("Field 'network' is missing or null");
×
266
        } else if (!jsonExecFile["network"].is_boolean()) {
6!
NEW
267
            throw std::runtime_error("Field 'network' must be boolean (true or false)");
×
NEW
268
        }
×
269

270
        if (jsonExecFile.contains("copy_file") && !jsonExecFile["copy_file"].is_null()) {
6!
271
            if (jsonExecFile["copy_file"].is_string()) {
6✔
272
                string result = GetReadFileResult(jsonExecFile["copy_file"]);
4✔
273
                CopyFile.push_back(result);
4✔
274
            } 
4✔
275
            else if (jsonExecFile["copy_file"].is_array()) {
2!
276
                for (const auto& CodePath : jsonExecFile["copy_file"]) {
4✔
277
                    if (CodePath.is_string()) {
4!
278
                        string result = GetReadFileResult(CodePath);
4✔
279
                        CopyFile.push_back(result);
4✔
280
                    } else {
4✔
NEW
281
                        throw std::runtime_error("Error: non-string element in copy_file array");
×
NEW
282
                    }
×
283
                }
4✔
284
            } 
2✔
285
        } else {
6✔
NEW
286
            throw std::runtime_error("Field 'copy_file' is missing or null");
×
NEW
287
        }
×
288

289
        if (jsonExecFile.contains("script_file") && !jsonExecFile["script_file"].is_null()) {
6!
290
            if (jsonExecFile["script_file"].is_string()) {
6✔
291
                string result = GetReadFileResult(jsonExecFile["script_file"]);
4✔
292
                ScriptsCode.push_back(result);
4✔
293
            } 
4✔
294
            else if (jsonExecFile["script_file"].is_array()) {
2!
295
                for (const auto& scriptPath : jsonExecFile["script_file"]) {
4✔
296
                    if (scriptPath.is_string()) {
4!
297
                        string result = GetReadFileResult(scriptPath);
4✔
298
                        ScriptsCode.push_back(result);
4✔
299
                    } else {
4✔
NEW
300
                        throw std::runtime_error("Error: non-string element in script_file array");
×
NEW
301
                    }
×
302
                }
4✔
303
            } 
2✔
NEW
304
            else {
×
NEW
305
                throw std::runtime_error("Field 'script_file' must be either string or array");
×
NEW
306
            }
×
307
        } else {
6✔
NEW
308
            throw std::runtime_error("Field 'script_file' is missing or null");
×
NEW
UNCOV
309
        }
×
310

311
        resultJson["network"] = jsonExecFile["network"];
6✔
312
        resultJson["copy_file"] = CopyFile;
6✔
313
        resultJson["script_code"] = ScriptsCode;
6✔
314

315
        return resultJson.dump();
6✔
316
    }
6✔
317
    
318
    NMessages::TBuildOptions TConverter::ConvertBuildOptions(const NCli::TBuildOptions& options, NMessages::TBuildOptions& protoOptions) {
12✔
319
        if (options.Name.has_value()) {
12✔
320
            protoOptions.set_name(options.Name.value());
6✔
321
        }
6✔
322

323
        if (options.PathToDasbootFile.has_value()) {
12✔
324
            string DasbootFile = ReadDasbootFile(options.PathToDasbootFile.value());
8✔
325
            protoOptions.set_dasboot_file(DasbootFile);
8✔
326
        }
8✔
327

328
        return protoOptions;
12✔
329
    }
12✔
330

331
    NMessages::TRunOptions TConverter::ConvertRunOptions(const NCli::TRunOptions& options, NMessages::TRunOptions& protoOptions) {
8✔
332
        if (options.Name.has_value()) {
8✔
333
            protoOptions.set_name(options.Name.value());
6✔
334
        }
6✔
335

336
        return protoOptions;
8✔
337
    }
8✔
338

339
    NMessages::TStartOptions TConverter::ConvertStartOptions(const NCli::TStartOptions& options, NMessages::TStartOptions& protoOptions) {
8✔
340
        if (options.Name.has_value()) {
8✔
341
            protoOptions.set_name(options.Name.value());
4✔
342
        }
4✔
343

344
        if (options.Id.has_value()) {
8✔
345
            protoOptions.set_id(options.Id.value());
2✔
346
        }
2✔
347

348
        return protoOptions;
8✔
349
    }
8✔
350
    
351
    NMessages::TStopOptions TConverter::ConvertStopOptions(const NCli::TStopOptions& options, NMessages::TStopOptions& protoOptions) {
10✔
352
        if (options.Name.has_value()) {
10✔
353
            protoOptions.set_name(options.Name.value());
6✔
354
        }
6✔
355

356
        if (options.Id.has_value()) {
10✔
357
            protoOptions.set_id(options.Id.value());
4✔
358
        }
4✔
359

360
        return protoOptions;
10✔
361
    }
10✔
362
    
363
    NMessages::TPsOptions TConverter::ConvertPsOptions(const NCli::TPsOptions& options, NMessages::TPsOptions& protoOptions) {
6✔
364
        if (options.ShowAll) {
6✔
365
            protoOptions.set_show_all(options.ShowAll);
4✔
366
        }
4✔
367

368
        return protoOptions;
6✔
369
    }
6✔
370
    
371
    NMessages::TRmOptions TConverter::ConvertRmOptions(const NCli::TRmOptions& options, NMessages::TRmOptions& protoOptions) {
10✔
372
        if (options.Name.has_value()) {
10✔
373
            protoOptions.set_name(options.Name.value());
4✔
374
        }
4✔
375

376
        if (options.Id.has_value()) {
10✔
377
            protoOptions.set_id(options.Id.value());
6✔
378
        }
6✔
379

380
        return protoOptions;
10✔
381
    }
10✔
382
    
383
    NMessages::TExecOptions TConverter::ConvertExecOptions(const NCli::TExecOptions& options, NMessages::TExecOptions& protoOptions) {
10✔
384
        if (options.Name.has_value()) {
10✔
385
            protoOptions.set_name(options.Name.value());
4✔
386
        }
4✔
387

388
        if (options.Id.has_value()) {
10✔
389
            protoOptions.set_id(options.Id.value());
4✔
390
        }
4✔
391

392
        if (options.ExecFile.has_value()) {
10✔
393
            string result = ReadExecFile(options.ExecFile.value());
6✔
394
            protoOptions.set_exec_file(result);
6✔
395

396
        }
6✔
397

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

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

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

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

418
        return protoOptions;
2✔
419
    }
2✔
420

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