1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
pub mod block;
pub mod config;
pub use block::Block;
pub use config::Config;
use crate::util::Hash;
use crate::verify_features;
use eyre::{eyre, Context, Result};
use itertools::Itertools;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
pub static ROOT_DIR: OnceCell<PathBuf> = OnceCell::new();
pub static BASE_CFG: OnceCell<Config> = OnceCell::new();
#[derive(Deserialize, Debug, Default, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Task {
name: String,
version: String,
blocks: Vec<Block>,
#[serde(default)]
config: Config,
#[serde(default)]
description: String,
}
impl Task {
pub fn new(root_dir: &Path) -> Result<Self> {
ROOT_DIR.set(root_dir.to_owned()).unwrap();
let path = root_dir.join("task.ron");
let content =
fs::read_to_string(&path).wrap_err("Failed to read task description file.")?;
verify_features(&content)?;
ron::from_str::<Task>(&content)
.wrap_err_with(|| eyre!("Failed to deserialize task file ({path:?})."))?
.init(root_dir)
}
pub fn init(mut self, root_dir: &Path) -> Result<Self> {
for block in self.blocks.iter_mut() {
block
.init()
.wrap_err_with(|| eyre!("Failed to verify block ({}).", block.label()))?;
}
for (name, count) in self.block_labels().into_iter().counts() {
if count > 1 {
Err(eyre!(
"Block names have to be unique within a task ('{name}' is repeated)."
))?;
}
}
if self.description.is_empty() {
let path = root_dir.join("description.txt");
let description = fs::read_to_string(&path)
.wrap_err_with(|| format!("Unable to open task description file ({path:?})."))?;
self.description = description;
}
self.config.init()?;
self.config.verify_checksum(self.hash())?;
BASE_CFG.set(self.config.clone()).unwrap();
Ok(self)
}
#[inline(always)]
pub fn name(&self) -> &String {
&self.name
}
#[inline(always)]
pub fn version(&self) -> &String {
&self.version
}
#[inline(always)]
pub fn title(&self) -> String {
format!("{} ({})", self.name, self.version)
}
#[inline(always)]
pub fn config(&self) -> &Config {
&self.config
}
#[inline(always)]
pub fn block(&self, i: usize) -> &Block {
&self.blocks[i]
}
pub fn block_labels(&self) -> Vec<String> {
self.blocks.iter().map(|b| b.label().to_string()).collect()
}
#[inline(always)]
pub fn description(&self) -> &str {
&self.description
}
}
impl Hash for Task {
fn hash(&self) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::default();
let blocks: Vec<_> = self.blocks.iter().map(|b| b.hash()).collect();
hasher.update(&serde_cbor::to_vec(&blocks).unwrap());
hex::encode(hasher.finalize())
}
}