about summary refs log tree commit diff
path: root/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/utils.rs
blob: 9a5d12748918aaba6c8089217c5aa8e249c0542f (plain) (blame)
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
use anyhow::Context;
use std::fs;
use std::io;
use std::path::Path;

pub const BASE_SUBPATH: &str = "pkgs/by-name";
pub const PACKAGE_NIX_FILENAME: &str = "package.nix";

/// Deterministic file listing so that tests are reproducible
pub fn read_dir_sorted(base_dir: &Path) -> anyhow::Result<Vec<fs::DirEntry>> {
    let listing = base_dir
        .read_dir()
        .with_context(|| format!("Could not list directory {}", base_dir.display()))?;
    let mut shard_entries = listing
        .collect::<io::Result<Vec<_>>>()
        .with_context(|| format!("Could not list directory {}", base_dir.display()))?;
    shard_entries.sort_by_key(|entry| entry.file_name());
    Ok(shard_entries)
}

/// A simple utility for calculating the line for a string offset.
/// This doesn't do any Unicode handling, though that probably doesn't matter
/// because newlines can't split up Unicode characters. Also this is only used
/// for error reporting
pub struct LineIndex {
    /// Stores the indices of newlines
    newlines: Vec<usize>,
}

impl LineIndex {
    pub fn new(s: &str) -> LineIndex {
        let mut newlines = vec![];
        let mut index = 0;
        // Iterates over all newline-split parts of the string, adding the index of the newline to
        // the vec
        for split in s.split_inclusive('\n') {
            index += split.len();
            newlines.push(index - 1);
        }
        LineIndex { newlines }
    }

    /// Returns the line number for a string index.
    /// If the index points to a newline, returns the line number before the newline
    pub fn line(&self, index: usize) -> usize {
        match self.newlines.binary_search(&index) {
            // +1 because lines are 1-indexed
            Ok(x) => x + 1,
            Err(x) => x + 1,
        }
    }

    /// Returns the string index for a line and column.
    pub fn fromlinecolumn(&self, line: usize, column: usize) -> usize {
        // If it's the 1th line, the column is the index
        if line == 1 {
            // But columns are 1-indexed
            column - 1
        } else {
            // For the nth line, we add the index of the (n-1)st newline to the column,
            // and remove one more from the index since arrays are 0-indexed.
            // Then add the 1-indexed column to get not the newline index itself,
            // but rather the index of the position on the next line
            self.newlines[line - 2] + column
        }
    }
}

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

    #[test]
    fn line_index() {
        let line_index = LineIndex::new("a\nbc\n\ndef\n");

        let pairs = [
            (0, 1, 1),
            (1, 1, 2),
            (2, 2, 1),
            (3, 2, 2),
            (4, 2, 3),
            (5, 3, 1),
            (6, 4, 1),
            (7, 4, 2),
            (8, 4, 3),
            (9, 4, 4),
        ];

        for (index, line, column) in pairs {
            assert_eq!(line_index.line(index), line);
            assert_eq!(line_index.fromlinecolumn(line, column), index);
        }
    }
}