summaryrefslogtreecommitdiff
path: root/src/cargo_toml.rs
blob: 106e6a7ab0c6eb5bfb5004c1093b36e9666af826 (plain)
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
119
120
121
122
use anyhow::{Context, Result};

use crate::info_file::ExerciseInfo;

// Return the start and end index of the content of the list `bin = […]`.
// bin = [xxxxxxxxxxxxxxxxx]
//        |start_ind       |
//                         |end_ind
pub fn bins_start_end_ind(cargo_toml: &str) -> Result<(usize, usize)> {
    let start_ind = cargo_toml
        .find("bin = [")
        .context("Failed to find the start of the `bin` list (`bin = [`)")?
        + 7;
    let end_ind = start_ind
        + cargo_toml
            .get(start_ind..)
            .and_then(|slice| slice.as_bytes().iter().position(|c| *c == b']'))
            .context("Failed to find the end of the `bin` list (`]`)")?;

    Ok((start_ind, end_ind))
}

// Generate and append the content of the `bin` list in `Cargo.toml`.
// The `exercise_path_prefix` is the prefix of the `path` field of every list entry.
pub fn append_bins(
    buf: &mut Vec<u8>,
    exercise_infos: &[ExerciseInfo],
    exercise_path_prefix: &[u8],
) {
    buf.push(b'\n');
    for exercise_info in exercise_infos {
        buf.extend_from_slice(b"  { name = \"");
        buf.extend_from_slice(exercise_info.name.as_bytes());
        buf.extend_from_slice(b"\", path = \"");
        buf.extend_from_slice(exercise_path_prefix);
        buf.extend_from_slice(b"exercises/");
        if let Some(dir) = &exercise_info.dir {
            buf.extend_from_slice(dir.as_bytes());
            buf.push(b'/');
        }
        buf.extend_from_slice(exercise_info.name.as_bytes());
        buf.extend_from_slice(b".rs\" },\n");
    }
}

// Update the `bin` list and leave everything else unchanged.
pub fn updated_cargo_toml(
    exercise_infos: &[ExerciseInfo],
    current_cargo_toml: &str,
    exercise_path_prefix: &[u8],
) -> Result<Vec<u8>> {
    let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?;

    let mut updated_cargo_toml = Vec::with_capacity(1 << 13);
    updated_cargo_toml.extend_from_slice(current_cargo_toml[..bins_start_ind].as_bytes());
    append_bins(
        &mut updated_cargo_toml,
        exercise_infos,
        exercise_path_prefix,
    );
    updated_cargo_toml.extend_from_slice(current_cargo_toml[bins_end_ind..].as_bytes());

    Ok(updated_cargo_toml)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bins_start_end_ind() {
        assert_eq!(bins_start_end_ind("").ok(), None);
        assert_eq!(bins_start_end_ind("[]").ok(), None);
        assert_eq!(bins_start_end_ind("bin = [").ok(), None);
        assert_eq!(bins_start_end_ind("bin = ]").ok(), None);
        assert_eq!(bins_start_end_ind("bin = []").ok(), Some((7, 7)));
        assert_eq!(bins_start_end_ind("bin= []").ok(), None);
        assert_eq!(bins_start_end_ind("bin =[]").ok(), None);
        assert_eq!(bins_start_end_ind("bin=[]").ok(), None);
        assert_eq!(bins_start_end_ind("bin = [\nxxx\n]").ok(), Some((7, 12)));
    }

    #[test]
    fn test_bins() {
        let exercise_infos = [
            ExerciseInfo {
                name: String::from("1"),
                dir: None,
                test: true,
                strict_clippy: true,
                hint: String::new(),
            },
            ExerciseInfo {
                name: String::from("2"),
                dir: Some(String::from("d")),
                test: false,
                strict_clippy: false,
                hint: String::new(),
            },
        ];

        let mut buf = Vec::with_capacity(128);
        append_bins(&mut buf, &exercise_infos, b"");
        assert_eq!(
            buf,
            br#"
  { name = "1", path = "exercises/1.rs" },
  { name = "2", path = "exercises/d/2.rs" },
"#,
        );

        assert_eq!(
            updated_cargo_toml(&exercise_infos, "abc\nbin = [xxx]\n123", b"../").unwrap(),
            br#"abc
bin = [
  { name = "1", path = "../exercises/1.rs" },
  { name = "2", path = "../exercises/d/2.rs" },
]
123"#,
        );
    }
}