Implement source linker and add URL to source code in generated docs.

Long overdue.
This commit is contained in:
KtorZ
2024-08-22 16:37:48 +02:00
parent 44e42d608d
commit b479a289cf
7 changed files with 140 additions and 27 deletions

View File

@@ -26,6 +26,7 @@ const MAX_COLUMNS: isize = 999;
const VERSION: &str = env!("CARGO_PKG_VERSION");
mod link_tree;
mod source_links;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DocFile {
@@ -131,7 +132,8 @@ pub fn generate_all(root: &Path, config: &Config, modules: Vec<&CheckedModule>)
continue;
}
let (indexes, file) = generate_module(config, module, &modules_links, &source, &timestamp);
let (indexes, file) =
generate_module(root, config, module, &modules_links, &source, &timestamp);
if !indexes.is_empty() {
search_indexes.extend(indexes);
output_files.push(file);
@@ -151,6 +153,7 @@ pub fn generate_all(root: &Path, config: &Config, modules: Vec<&CheckedModule>)
}
fn generate_module(
root: &Path,
config: &Config,
module: &CheckedModule,
modules: &[DocLink],
@@ -159,6 +162,8 @@ fn generate_module(
) -> (Vec<SearchIndex>, DocFile) {
let mut search_indexes = vec![];
let source_linker = source_links::SourceLinker::new(root, config, module);
// Section headers
let mut section_headers = module
.extra
@@ -189,7 +194,7 @@ fn generate_module(
.ast
.definitions
.iter()
.flat_map(DocFunction::from_definition)
.flat_map(|def| DocFunction::from_definition(def, &source_linker))
.collect();
functions.iter().for_each(|(_, function)| {
@@ -221,7 +226,7 @@ fn generate_module(
.ast
.definitions
.iter()
.flat_map(DocType::from_definition)
.flat_map(|def| DocType::from_definition(def, &source_linker))
.collect();
types
.iter()
@@ -232,7 +237,7 @@ fn generate_module(
.ast
.definitions
.iter()
.flat_map(DocConstant::from_definition)
.flat_map(|def| DocConstant::from_definition(def, &source_linker))
.collect();
constants
.iter()
@@ -465,7 +470,10 @@ struct DocFunction {
}
impl DocFunction {
fn from_definition(def: &TypedDefinition) -> Option<(Span, Self)> {
fn from_definition(
def: &TypedDefinition,
source_linker: &source_links::SourceLinker,
) -> Option<(Span, Self)> {
match def {
Definition::Fn(func_def) if func_def.public => Some((
func_def.location,
@@ -485,7 +493,8 @@ impl DocFunction {
func_def.return_type.clone(),
)
.to_pretty_string(MAX_COLUMNS),
source_url: "#todo".to_string(),
source_url: source_linker
.url(func_def.location.map_end(|_| func_def.end_position)),
},
)),
_ => None,
@@ -503,7 +512,10 @@ struct DocConstant {
}
impl DocConstant {
fn from_definition(def: &TypedDefinition) -> Option<Self> {
fn from_definition(
def: &TypedDefinition,
source_linker: &source_links::SourceLinker,
) -> Option<Self> {
match def {
Definition::ModuleConstant(const_def) if const_def.public => Some(DocConstant {
name: const_def.name.clone(),
@@ -516,7 +528,7 @@ impl DocConstant {
definition: format::Formatter::new()
.docs_const_expr(&const_def.name, &const_def.value)
.to_pretty_string(MAX_COLUMNS),
source_url: "#todo".to_string(),
source_url: source_linker.url(const_def.location),
}),
_ => None,
}
@@ -536,7 +548,10 @@ struct DocType {
}
impl DocType {
fn from_definition(def: &TypedDefinition) -> Option<Self> {
fn from_definition(
def: &TypedDefinition,
source_linker: &source_links::SourceLinker,
) -> Option<Self> {
match def {
Definition::TypeAlias(info) if info.public => Some(DocType {
name: info.alias.clone(),
@@ -548,7 +563,7 @@ impl DocType {
constructors: vec![],
parameters: info.parameters.clone(),
opaque: false,
source_url: "#todo".to_string(),
source_url: source_linker.url(info.location),
}),
Definition::DataType(info) if info.public && !info.opaque => Some(DocType {
@@ -570,7 +585,7 @@ impl DocType {
.collect(),
parameters: info.parameters.clone(),
opaque: info.opaque,
source_url: "#todo".to_string(),
source_url: source_linker.url(info.location),
}),
Definition::DataType(info) if info.public && info.opaque => Some(DocType {
@@ -583,7 +598,7 @@ impl DocType {
constructors: vec![],
parameters: info.parameters.clone(),
opaque: info.opaque,
source_url: "#todo".to_string(),
source_url: source_linker.url(info.location),
}),
_ => None,

View File

@@ -0,0 +1,84 @@
use crate::{
config::{Config, Platform},
CheckedModule,
};
use aiken_lang::{ast::Span, line_numbers::LineNumbers};
use camino::{Utf8Component, Utf8Path};
use std::path::Path;
pub struct SourceLinker {
line_numbers: LineNumbers,
url_pattern: Option<(String, String)>,
}
impl SourceLinker {
pub fn new(root: &Path, config: &Config, module: &CheckedModule) -> Self {
let utf8_path = <&Utf8Path>::try_from(
module
.input_path
.as_path()
.strip_prefix(root)
.expect("root path isn't a prefix of project modules' paths!"),
)
.expect("module path contains non UTF-8 characters!");
let path_in_repo = to_url_path(utf8_path).unwrap_or_default();
let url_pattern = config
.repository
.as_ref()
.map(|repository| match repository.platform {
Platform::Github => (
format!(
"https://github.com/{}/{}/blob/{}/{}#L",
repository.user, repository.project, config.version, path_in_repo
),
"-L".into(),
),
Platform::Gitlab => (
format!(
"https://gitlab.com/{}/{}/-/blob/{}/{}#L",
repository.user, repository.project, config.version, path_in_repo
),
"-".into(),
),
Platform::Bitbucket => (
format!(
"https://bitbucket.com/{}/{}/src/{}/{}#lines-",
repository.user, repository.project, config.version, path_in_repo
),
":".into(),
),
});
SourceLinker {
line_numbers: LineNumbers::new(&module.code),
url_pattern,
}
}
pub fn url(&self, span: Span) -> String {
match &self.url_pattern {
Some((base, line_sep)) => {
let start_line = self.line_numbers.line_number(span.start).unwrap();
let end_line = self.line_numbers.line_number(span.end).unwrap();
format!("{base}{start_line}{line_sep}{end_line}")
}
None => "".into(),
}
}
}
fn to_url_path(path: &Utf8Path) -> Option<String> {
let mut buf = String::new();
for c in path.components() {
if let Utf8Component::Normal(s) = c {
buf.push_str(s);
}
buf.push('/');
}
let _ = buf.pop();
Some(buf)
}