Implement source linker and add URL to source code in generated docs.
Long overdue.
This commit is contained in:
@@ -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, ×tamp);
|
||||
let (indexes, file) =
|
||||
generate_module(root, config, module, &modules_links, &source, ×tamp);
|
||||
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,
|
||||
|
||||
84
crates/aiken-project/src/docs/source_links.rs
Normal file
84
crates/aiken-project/src/docs/source_links.rs
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user