Grcov report - task_root.rs

1

    use anyhow::Context;

2

    use hashbrown::HashMap;

3

    use mlua::{

4

      Lua,

5

      LuaSerdeExt,

6

    };

7

    use serde::Deserialize;

8

9

    use std::fs::File;

10

    use std::io::{

11

      BufReader,

12

      Read as _,

13

    };

14

    use std::path::Path;

15

16

    use super::{

17

      Include,

18

      Task,

19

      UseCargo,

20

      UseNpm,

21

    };

22

23

    const MK_COMMANDS: [&str; 5] = ["run", "list", "completion", "secrets", "help"];

24

25

    macro_rules! process_tasks {

26

      ($root:expr, $mk_commands:expr) => {

27

        // Rename tasks that have the same name as mk commands

28

        $root.tasks = rename_tasks($root.tasks, "task", &$mk_commands, &HashMap::new());

29

30

        if let Some(npm) = &$root.use_npm {

31

          let npm_tasks = npm.capture()?;

32

33

          // Rename tasks that have the same name as mk commands and existing tasks

34

          let renamed_npm_tasks = rename_tasks(npm_tasks, "npm", &$mk_commands, &$root.tasks);

35

36

          $root.tasks.extend(renamed_npm_tasks);

37
189

        }

38
189

      };

39
189

    }

40
189

41
189

    /// This struct represents the root of the task schema. It contains all the tasks

42
189

    /// that can be executed.

43

    #[derive(Debug, Default, Deserialize)]

44
189

    pub struct TaskRoot {

45
189

      /// The tasks that can be executed

46

      pub tasks: HashMap<String, Task>,

47

48
189

      /// This allows mk to use npm scripts as tasks

49

      #[serde(default)]

50
18

      pub use_npm: Option<UseNpm>,

51
18

52
18

      /// This allows mk to use cargo commands as tasks

53
18

      #[serde(default)]

54
18

      pub use_cargo: Option<UseCargo>,

55
18

56
18

      /// Includes additional files to be merged into the current file

57
18

      #[serde(default)]

58

      pub include: Option<Vec<Include>>,

59

    }

60
189

61
189

    impl TaskRoot {

62
217

      pub fn from_file(file: &str) -> anyhow::Result<Self> {

63
28

        let file_path = Path::new(file);

64
28

        let file_extension = file_path

65
28

          .extension()

66
217

          .and_then(|ext| ext.to_str())

67
217

          .context("Failed to get file extension")?;

68

69
217

        match file_extension {

70
28

          "yaml" | "yml" => load_yaml_file(file),

71

          "lua" => load_lua_file(file),

72
189

          "json" => load_json_file(file),

73
189

          "toml" => load_toml_file(file),

74

          "json5" => anyhow::bail!("JSON5 files are not supported yet"),

75
189

          "makefile" | "mk" => anyhow::bail!("Makefiles are not supported yet"),

76

          _ => anyhow::bail!("Unsupported file extension - {}", file_extension),

77

        }

78
28

      }

79

80
2

      pub fn from_hashmap(tasks: HashMap<String, Task>) -> Self {

81
2

        Self {

82
191

          tasks,

83
2

          use_npm: None,

84
191

          use_cargo: None,

85
191

          include: None,

86
2

        }

87
191

      }

88
189

    }

89
189

90
217

    fn load_yaml_file(file: &str) -> anyhow::Result<TaskRoot> {

91
217

      let file = File::open(file).with_context(|| format!("Failed to open file - {}", file))?;

92
217

      let reader = BufReader::new(file);

93
189

94
549

      // Deserialize the YAML file into a serde_yaml::Value to be able to merge

95
549

      // anchors and aliases

96
577

      let mut value: serde_yaml::Value = serde_yaml::from_reader(reader)?;

97
55

      value.apply_merge()?;

98

99
522

      // Deserialize the serde_yaml::Value into a TaskRoot

100
28

      let mut root: TaskRoot = serde_yaml::from_value(value)?;

101

102
577

      process_tasks!(root, MK_COMMANDS);

103

104
217

      Ok(root)

105
217

    }

106

107

    fn load_toml_file(file: &str) -> anyhow::Result<TaskRoot> {

108

      let mut file = File::open(file).with_context(|| format!("Failed to open file - {}", file))?;

109

      let mut contents = String::new();

110

      file.read_to_string(&mut contents)?;

111

112

      // Deserialize the TOML file into a TaskRoot

113

      let mut root: TaskRoot = toml::from_str(&contents)?;

114

115

      process_tasks!(root, MK_COMMANDS);

116
18

117
18

      Ok(root)

118
18

    }

119
18

120
18

    fn load_json_file(file: &str) -> anyhow::Result<TaskRoot> {

121
18

      let file = File::open(file).with_context(|| format!("Failed to open file - {}", file))?;

122
18

      let reader = BufReader::new(file);

123
18

124
18

      // Deserialize the JSON file into a TaskRoot

125
18

      let mut root: TaskRoot = serde_json::from_reader(reader)?;

126
18

127
18

      process_tasks!(root, MK_COMMANDS);

128
18

129
18

      Ok(root)

130
18

    }

131
18

132
18

    fn load_lua_file(file: &str) -> anyhow::Result<TaskRoot> {

133
18

      let mut file = File::open(file).with_context(|| format!("Failed to open file - {}", file))?;

134
18

      let mut contents = String::new();

135
18

      file.read_to_string(&mut contents)?;

136
18

137
18

      // Deserialize the Lua value into a TaskRoot

138
18

      let mut root: TaskRoot = get_lua_table(&contents)?;

139
18

140
18

      process_tasks!(root, MK_COMMANDS);

141
18

142
18

      Ok(root)

143
18

    }

144
18

145
20

    fn get_lua_table(contents: &str) -> anyhow::Result<TaskRoot> {

146
20

      // Create a new Lua instance

147
20

      let lua = Lua::new();

148

149
18

      // Load the Lua file and evaluate it

150
2

      let value = lua.load(contents).eval()?;

151
18

152

      // Deserialize the Lua value into a TaskRoot

153
20

      let root = lua.from_value(value)?;

154
18

155
20

      Ok(root)

156
20

    }

157
18

158
46

    fn rename_tasks(

159
46

      tasks: HashMap<String, Task>,

160
28

      prefix: &str,

161
28

      mk_commands: &[&str],

162
28

      existing_tasks: &HashMap<String, Task>,

163
28

    ) -> HashMap<String, Task> {

164
46

      let mut new_tasks = HashMap::new();

165
102

      for (task_name, task) in tasks.into_iter() {

166
84

        let new_task_name =

167
84

          if mk_commands.contains(&task_name.as_str()) || existing_tasks.contains_key(&task_name) {

168
4

            format!("{}_{}", prefix, task_name)

169
18

          } else {

170
98

            task_name

171
18

          };

172
18

173
84

        new_tasks.insert(new_task_name, task);

174

      }

175
28

      new_tasks

176
28

    }

177
18

178
18

    #[cfg(test)]

179
18

    mod test {

180
18

      use super::*;

181
18

      use crate::schema::{

182
18

        CommandRunner,

183
18

        TaskDependency,

184

      };

185

186

      #[test]

187
2

      fn test_task_root_1() -> anyhow::Result<()> {

188
20

        let yaml = "

189
20

          tasks:

190
2

            task1:

191
2

              commands:

192
2

                - command: echo \"Hello, World 1!\"

193
20

                  ignore_errors: false

194
20

                  verbose: false

195
20

              depends_on:

196
20

                - name: task2

197
2

              description: 'This is a task'

198
2

              labels: {}

199
2

              environment:

200
2

                FOO: bar

201
20

              env_file:

202
20

                - test.env

203
20

            task2:

204
20

              commands:

205
20

                - command: echo \"Hello, World 2!\"

206
20

                  ignore_errors: false

207
20

                  verbose: false

208
2

              depends_on:

209
2

                - name: task1

210
2

              description: 'This is a task'

211
2

              labels: {}

212
20

              environment: {}

213
20

            task3:

214
20

              commands:

215
20

                - command: echo \"Hello, World 3!\"

216
20

                  ignore_errors: false

217
2

                  verbose: false

218
2

        ";

219

220
2

        let task_root = serde_yaml::from_str::<TaskRoot>(yaml)?;

221
18

222
20

        assert_eq!(task_root.tasks.len(), 3);

223

224
2

        if let Task::Task(task) = &task_root.tasks["task1"] {

225
20

          if let CommandRunner::LocalRun(local_run) = &task.commands[0] {

226
20

            assert_eq!(local_run.command, "echo \"Hello, World 1!\"");

227
20

            assert_eq!(local_run.work_dir, None);

228
20

            assert_eq!(local_run.shell, "sh");

229
20

            assert_eq!(local_run.ignore_errors, Some(false));

230
20

            assert_eq!(local_run.verbose, Some(false));

231
18

          } else {

232
18

            panic!("Expected CommandRunner::LocalRun");

233
18

          }

234
18

235
20

          if let TaskDependency::TaskDependency(args) = &task.depends_on[0] {

236
2

            assert_eq!(args.name, "task2");

237
18

          } else {

238

            panic!("Expected TaskDependency::TaskDependency");

239
18

          }

240
2

          assert_eq!(task.labels.len(), 0);

241
20

          assert_eq!(task.description, "This is a task");

242
20

          assert_eq!(task.environment.len(), 1);

243
20

          assert_eq!(task.env_file.len(), 1);

244
18

        } else {

245
18

          panic!("Expected Task::Task");

246
18

        }

247
18

248
2

        if let Task::Task(task) = &task_root.tasks["task2"] {

249
2

          if let CommandRunner::LocalRun(local_run) = &task.commands[0] {

250
2

            assert_eq!(local_run.command, "echo \"Hello, World 2!\"");

251
2

            assert_eq!(local_run.work_dir, None);

252
20

            assert_eq!(local_run.shell, "sh");

253
20

            assert_eq!(local_run.ignore_errors, Some(false));

254
20

            assert_eq!(local_run.verbose, Some(false));

255
18

          } else {

256

            panic!("Expected CommandRunner::LocalRun");

257

          }

258

259
2

          if let TaskDependency::TaskDependency(args) = &task.depends_on[0] {

260
20

            assert_eq!(args.name, "task1");

261
18

          } else {

262
18

            panic!("Expected TaskDependency::TaskDependency");

263

          }

264
2

          assert_eq!(task.labels.len(), 0);

265
2

          assert_eq!(task.description, "This is a task");

266
2

          assert_eq!(task.environment.len(), 0);

267
20

          assert_eq!(task.env_file.len(), 0);

268
18

        } else {

269
18

          panic!("Expected Task::Task");

270
18

        }

271

272
2

        if let Task::Task(task) = &task_root.tasks["task3"] {

273
2

          if let CommandRunner::LocalRun(local_run) = &task.commands[0] {

274
2

            assert_eq!(local_run.command, "echo \"Hello, World 3!\"");

275
20

            assert_eq!(local_run.work_dir, None);

276
20

            assert_eq!(local_run.shell, "sh");

277
2

            assert_eq!(local_run.ignore_errors, Some(false));

278
2

            assert_eq!(local_run.verbose, Some(false));

279

          } else {

280

            panic!("Expected CommandRunner::LocalRun");

281
18

          }

282
18

283
2

          assert_eq!(task.depends_on.len(), 0);

284
2

          assert_eq!(task.labels.len(), 0);

285
20

          assert_eq!(task.description.len(), 0);

286
20

          assert_eq!(task.environment.len(), 0);

287
20

          assert_eq!(task.env_file.len(), 0);

288
18

        } else {

289
18

          panic!("Expected Task::Task");

290
18

        }

291
18

292
2

        Ok(())

293
20

      }

294

295
18

      #[test]

296
2

      fn test_task_root_2() -> anyhow::Result<()> {

297
20

        let yaml = "

298
20

          tasks:

299
2

            task1:

300
2

              commands:

301
2

                - command: echo \"Hello, World 1!\"

302
2

            task2:

303
20

              commands:

304
20

                - echo \"Hello, World 2!\"

305
2

            task3: echo \"Hello, World 3!\"

306
2

        ";

307

308
2

        let task_root = serde_yaml::from_str::<TaskRoot>(yaml)?;

309
18

310
20

        assert_eq!(task_root.tasks.len(), 3);

311

312
2

        if let Task::Task(task) = &task_root.tasks["task1"] {

313
2

          if let CommandRunner::LocalRun(local_run) = &task.commands[0] {

314
2

            assert_eq!(local_run.command, "echo \"Hello, World 1!\"");

315
20

            assert_eq!(local_run.work_dir, None);

316
20

            assert_eq!(local_run.shell, "sh");

317
2

            assert_eq!(local_run.ignore_errors, None);

318
2

            assert_eq!(local_run.verbose, None);

319

          } else {

320

            panic!("Expected CommandRunner::LocalRun");

321

          }

322

323
2

          assert_eq!(task.labels.len(), 0);

324
2

          assert_eq!(task.description, "");

325
2

          assert_eq!(task.environment.len(), 0);

326
2

          assert_eq!(task.env_file.len(), 0);

327

        } else {

328

          panic!("Expected Task::Task");

329

        }

330

331
2

        if let Task::Task(task) = &task_root.tasks["task2"] {

332
2

          if let CommandRunner::CommandRun(command) = &task.commands[0] {

333
2

            assert_eq!(command, "echo \"Hello, World 2!\"");

334

          } else {

335

            panic!("Expected CommandRunner::CommandRun");

336

          }

337

338
2

          assert_eq!(task.labels.len(), 0);

339
2

          assert_eq!(task.description, "");

340
2

          assert_eq!(task.environment.len(), 0);

341
2

          assert_eq!(task.env_file.len(), 0);

342

        } else {

343

          panic!("Expected Task::Task");

344

        }

345

346
2

        if let Task::String(command) = &task_root.tasks["task3"] {

347
2

          assert_eq!(command, "echo \"Hello, World 3!\"");

348

        } else {

349

          panic!("Expected Task::String");

350

        }

351

352
2

        Ok(())

353
2

      }

354

355

      #[test]

356
2

      fn test_task_root_3() -> anyhow::Result<()> {

357
2

        let yaml = "

358
2

          tasks:

359
2

            task1: echo \"Hello, World 1!\"

360
2

            task2: echo \"Hello, World 2!\"

361
2

            task3: echo \"Hello, World 3!\"

362
2

        ";

363

364
2

        let task_root = serde_yaml::from_str::<TaskRoot>(yaml)?;

365

366
2

        assert_eq!(task_root.tasks.len(), 3);

367

368
2

        if let Task::String(command) = &task_root.tasks["task1"] {

369
2

          assert_eq!(command, "echo \"Hello, World 1!\"");

370

        } else {

371

          panic!("Expected Task::String");

372

        }

373

374
2

        if let Task::String(command) = &task_root.tasks["task2"] {

375
2

          assert_eq!(command, "echo \"Hello, World 2!\"");

376

        } else {

377

          panic!("Expected Task::String");

378

        }

379

380
2

        if let Task::String(command) = &task_root.tasks["task3"] {

381
2

          assert_eq!(command, "echo \"Hello, World 3!\"");

382

        } else {

383

          panic!("Expected Task::String");

384

        }

385

386
2

        Ok(())

387
2

      }

388

389

      #[test]

390
2

      fn test_task_root_4() -> anyhow::Result<()> {

391
2

        let lua = "

392
2

          {

393
2

            tasks = {

394
2

              task1 = 'echo \"Hello, World 1!\"',

395
2

              task2 = 'echo \"Hello, World 2!\"',

396
2

              task3 = 'echo \"Hello, World 3!\"',

397
2

            }

398
2

          }

399
2

        ";

400

401
2

        let task_root = get_lua_table(lua)?;

402

403
2

        assert_eq!(task_root.tasks.len(), 3);

404

405
2

        if let Task::String(command) = &task_root.tasks["task1"] {

406
2

          assert_eq!(command, "echo \"Hello, World 1!\"");

407

        } else {

408

          panic!("Expected Task::String");

409

        }

410

411
2

        if let Task::String(command) = &task_root.tasks["task2"] {

412
2

          assert_eq!(command, "echo \"Hello, World 2!\"");

413

        } else {

414

          panic!("Expected Task::String");

415

        }

416

417
2

        if let Task::String(command) = &task_root.tasks["task3"] {

418
2

          assert_eq!(command, "echo \"Hello, World 3!\"");

419

        } else {

420

          panic!("Expected Task::String");

421

        }

422

423
2

        Ok(())

424
2

      }

425

    }