aiken_project/docs/
source_links.rs

1use crate::{
2    CheckedModule,
3    config::{Platform, ProjectConfig},
4};
5use aiken_lang::{ast::Span, line_numbers::LineNumbers};
6use camino::{Utf8Component, Utf8Path};
7use std::path::Path;
8
9pub struct SourceLinker {
10    line_numbers: LineNumbers,
11    url_pattern: Option<(String, String)>,
12}
13
14impl SourceLinker {
15    pub fn new(root: &Path, config: &ProjectConfig, module: &CheckedModule) -> Self {
16        let utf8_path = <&Utf8Path>::try_from(
17            module
18                .input_path
19                .as_path()
20                .strip_prefix(root)
21                .expect("root path isn't a prefix of project modules' paths!"),
22        )
23        .expect("module path contains non UTF-8 characters!");
24
25        let path_in_repo = to_url_path(utf8_path).unwrap_or_default();
26
27        let url_pattern = config
28            .repository
29            .as_ref()
30            .map(|repository| match repository.platform {
31                Platform::Github => (
32                    format!(
33                        "https://github.com/{}/{}/blob/{}/{}#L",
34                        repository.user, repository.project, config.version, path_in_repo
35                    ),
36                    "-L".into(),
37                ),
38                Platform::Gitlab => (
39                    format!(
40                        "https://gitlab.com/{}/{}/-/blob/{}/{}#L",
41                        repository.user, repository.project, config.version, path_in_repo
42                    ),
43                    "-".into(),
44                ),
45                Platform::Bitbucket => (
46                    format!(
47                        "https://bitbucket.com/{}/{}/src/{}/{}#lines-",
48                        repository.user, repository.project, config.version, path_in_repo
49                    ),
50                    ":".into(),
51                ),
52            });
53
54        SourceLinker {
55            line_numbers: LineNumbers::new(&module.code),
56            url_pattern,
57        }
58    }
59
60    pub fn url(&self, span: Span) -> String {
61        match &self.url_pattern {
62            Some((base, line_sep)) => {
63                match (
64                    self.line_numbers.line_number(span.start),
65                    self.line_numbers.line_number(span.end),
66                ) {
67                    (Some(start_line), Some(end_line)) => {
68                        format!("{base}{start_line}{line_sep}{end_line}")
69                    }
70                    (Some(start_line), None) => format!("{base}{start_line}"),
71                    _ => base.to_string(),
72                }
73            }
74            None => "".into(),
75        }
76    }
77}
78
79fn to_url_path(path: &Utf8Path) -> Option<String> {
80    let mut buf = String::new();
81    for c in path.components() {
82        if let Utf8Component::Normal(s) = c {
83            buf.push_str(s);
84        }
85        buf.push('/');
86    }
87
88    let _ = buf.pop();
89
90    Some(buf)
91}