mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-19 23:07:33 +03:00
Merge pull request #1511 from rtfeldman/doc-links
Doc links enhancements
This commit is contained in:
commit
daaea392f2
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3140,6 +3140,7 @@ dependencies = [
|
||||
"roc_collections",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_types",
|
||||
]
|
||||
|
@ -140,7 +140,7 @@ pub fn build_zig_host(
|
||||
.args(&[
|
||||
"build-obj",
|
||||
zig_host_src,
|
||||
&emit_bin,
|
||||
emit_bin,
|
||||
"--pkg-begin",
|
||||
"str",
|
||||
zig_str_path,
|
||||
|
@ -73,7 +73,7 @@ xor : Bool, Bool -> Bool
|
||||
## 2. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal.
|
||||
## 3. Records are equal if all their fields are equal.
|
||||
## 4. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
|
||||
## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See [Num.isNaN] for more about *NaN*.
|
||||
## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*.
|
||||
##
|
||||
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
|
||||
## accept arguments whose types contain functions.
|
||||
|
@ -12,7 +12,7 @@ pub struct Env<'a> {
|
||||
/// are assumed to be relative to this path.
|
||||
pub home: ModuleId,
|
||||
|
||||
pub dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
pub dep_idents: &'a MutMap<ModuleId, IdentIds>,
|
||||
|
||||
pub module_ids: &'a ModuleIds,
|
||||
|
||||
@ -40,7 +40,7 @@ pub struct Env<'a> {
|
||||
impl<'a> Env<'a> {
|
||||
pub fn new(
|
||||
home: ModuleId,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
dep_idents: &'a MutMap<ModuleId, IdentIds>,
|
||||
module_ids: &'a ModuleIds,
|
||||
exposed_ident_ids: IdentIds,
|
||||
) -> Env<'a> {
|
||||
|
@ -47,7 +47,7 @@ pub fn canonicalize_module_defs<'a, F>(
|
||||
home: ModuleId,
|
||||
module_ids: &ModuleIds,
|
||||
exposed_ident_ids: IdentIds,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
dep_idents: &'a MutMap<ModuleId, IdentIds>,
|
||||
aliases: MutMap<Symbol, Alias>,
|
||||
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||
exposed_symbols: &MutSet<Symbol>,
|
||||
@ -139,7 +139,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let (defs, _scope, output, symbols_introduced) = canonicalize_defs(
|
||||
let (defs, scope, output, symbols_introduced) = canonicalize_defs(
|
||||
&mut env,
|
||||
Output::default(),
|
||||
var_store,
|
||||
|
@ -56,7 +56,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
||||
|
||||
let mut scope = Scope::new(home, &mut var_store);
|
||||
let dep_idents = IdentIds::exposed_builtins(0);
|
||||
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
||||
let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default());
|
||||
let (loc_expr, output) = canonicalize_expr(
|
||||
&mut env,
|
||||
&mut var_store,
|
||||
|
@ -524,6 +524,7 @@ fn start_phase<'a>(
|
||||
var_store,
|
||||
imported_modules,
|
||||
declarations,
|
||||
dep_idents,
|
||||
..
|
||||
} = constrained;
|
||||
|
||||
@ -536,6 +537,7 @@ fn start_phase<'a>(
|
||||
imported_modules,
|
||||
&mut state.exposed_types,
|
||||
state.stdlib,
|
||||
dep_idents,
|
||||
declarations,
|
||||
)
|
||||
}
|
||||
@ -621,6 +623,7 @@ pub struct LoadedModule {
|
||||
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
|
||||
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
|
||||
pub exposed_to_host: MutMap<Symbol, Variable>,
|
||||
pub dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
pub exposed_aliases: MutMap<Symbol, Alias>,
|
||||
pub exposed_values: Vec<Symbol>,
|
||||
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
@ -676,6 +679,7 @@ struct ConstrainedModule {
|
||||
constraint: Constraint,
|
||||
ident_ids: IdentIds,
|
||||
var_store: VarStore,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
module_timing: ModuleTiming,
|
||||
}
|
||||
|
||||
@ -759,6 +763,7 @@ enum Msg<'a> {
|
||||
solved_module: SolvedModule,
|
||||
solved_subs: Solved<Subs>,
|
||||
decls: Vec<Declaration>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
module_timing: ModuleTiming,
|
||||
unused_imports: MutMap<ModuleId, Region>,
|
||||
},
|
||||
@ -767,6 +772,7 @@ enum Msg<'a> {
|
||||
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
|
||||
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
||||
exposed_values: Vec<Symbol>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
},
|
||||
FoundSpecializations {
|
||||
@ -985,6 +991,7 @@ enum BuildTask<'a> {
|
||||
constraint: Constraint,
|
||||
var_store: VarStore,
|
||||
declarations: Vec<Declaration>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
unused_imports: MutMap<ModuleId, Region>,
|
||||
},
|
||||
BuildPendingSpecializations {
|
||||
@ -1516,6 +1523,7 @@ where
|
||||
exposed_vars_by_symbol,
|
||||
exposed_aliases_by_symbol,
|
||||
exposed_values,
|
||||
dep_idents,
|
||||
documentation,
|
||||
} => {
|
||||
// We're done! There should be no more messages pending.
|
||||
@ -1534,6 +1542,7 @@ where
|
||||
exposed_values,
|
||||
exposed_aliases_by_symbol,
|
||||
exposed_vars_by_symbol,
|
||||
dep_idents,
|
||||
documentation,
|
||||
)));
|
||||
}
|
||||
@ -1892,6 +1901,7 @@ fn update<'a>(
|
||||
solved_module,
|
||||
solved_subs,
|
||||
decls,
|
||||
dep_idents,
|
||||
mut module_timing,
|
||||
mut unused_imports,
|
||||
} => {
|
||||
@ -1949,6 +1959,7 @@ fn update<'a>(
|
||||
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
|
||||
exposed_values: solved_module.exposed_symbols,
|
||||
exposed_aliases_by_symbol: solved_module.aliases,
|
||||
dep_idents,
|
||||
documentation,
|
||||
})
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
@ -2283,6 +2294,7 @@ fn finish(
|
||||
exposed_values: Vec<Symbol>,
|
||||
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
||||
exposed_vars_by_symbol: MutMap<Symbol, Variable>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
) -> LoadedModule {
|
||||
let module_ids = Arc::try_unwrap(state.arc_modules)
|
||||
@ -2316,6 +2328,7 @@ fn finish(
|
||||
can_problems: state.module_cache.can_problems,
|
||||
type_problems: state.module_cache.type_problems,
|
||||
declarations_by_id: state.declarations_by_id,
|
||||
dep_idents,
|
||||
exposed_aliases: exposed_aliases_by_symbol,
|
||||
exposed_values,
|
||||
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
|
||||
@ -3234,6 +3247,7 @@ impl<'a> BuildTask<'a> {
|
||||
imported_modules: MutMap<ModuleId, Region>,
|
||||
exposed_types: &mut SubsByModule,
|
||||
stdlib: &StdLib,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
declarations: Vec<Declaration>,
|
||||
) -> Self {
|
||||
let home = module.module_id;
|
||||
@ -3261,6 +3275,7 @@ impl<'a> BuildTask<'a> {
|
||||
constraint,
|
||||
var_store,
|
||||
declarations,
|
||||
dep_idents,
|
||||
module_timing,
|
||||
unused_imports,
|
||||
}
|
||||
@ -3276,6 +3291,7 @@ fn run_solve<'a>(
|
||||
constraint: Constraint,
|
||||
mut var_store: VarStore,
|
||||
decls: Vec<Declaration>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
unused_imports: MutMap<ModuleId, Region>,
|
||||
) -> Msg<'a> {
|
||||
// We have more constraining work to do now, so we'll add it to our timings.
|
||||
@ -3330,6 +3346,7 @@ fn run_solve<'a>(
|
||||
solved_subs,
|
||||
ident_ids,
|
||||
decls,
|
||||
dep_idents,
|
||||
solved_module,
|
||||
module_timing,
|
||||
unused_imports,
|
||||
@ -3478,7 +3495,8 @@ fn fabricate_effects_module<'a>(
|
||||
let module_ids = { (*module_ids).lock().clone() }.into_module_ids();
|
||||
|
||||
let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store);
|
||||
let mut can_env = roc_can::env::Env::new(module_id, dep_idents, &module_ids, exposed_ident_ids);
|
||||
let mut can_env =
|
||||
roc_can::env::Env::new(module_id, &dep_idents, &module_ids, exposed_ident_ids);
|
||||
|
||||
let effect_symbol = scope
|
||||
.introduce(
|
||||
@ -3611,6 +3629,7 @@ fn fabricate_effects_module<'a>(
|
||||
var_store,
|
||||
constraint,
|
||||
ident_ids: module_output.ident_ids,
|
||||
dep_idents,
|
||||
module_timing,
|
||||
};
|
||||
|
||||
@ -3690,7 +3709,7 @@ where
|
||||
module_id,
|
||||
module_ids,
|
||||
exposed_ident_ids,
|
||||
dep_idents,
|
||||
&dep_idents,
|
||||
aliases,
|
||||
exposed_imports,
|
||||
&exposed_symbols,
|
||||
@ -3738,6 +3757,7 @@ where
|
||||
var_store,
|
||||
constraint,
|
||||
ident_ids: module_output.ident_ids,
|
||||
dep_idents,
|
||||
module_timing,
|
||||
};
|
||||
|
||||
@ -4210,6 +4230,7 @@ where
|
||||
var_store,
|
||||
ident_ids,
|
||||
declarations,
|
||||
dep_idents,
|
||||
unused_imports,
|
||||
} => Ok(run_solve(
|
||||
module,
|
||||
@ -4219,6 +4240,7 @@ where
|
||||
constraint,
|
||||
var_store,
|
||||
declarations,
|
||||
dep_idents,
|
||||
unused_imports,
|
||||
)),
|
||||
BuildPendingSpecializations {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::ast::{AssignedField, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation};
|
||||
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
|
||||
use crate::ident::{lowercase_ident, parse_ident_help, Ident};
|
||||
use crate::ident::{lowercase_ident, parse_ident, Ident};
|
||||
use crate::keyword;
|
||||
use crate::parser::{
|
||||
self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then,
|
||||
@ -2097,7 +2097,7 @@ fn if_expr_help<'a>(
|
||||
/// 5. A reserved keyword (e.g. `if ` or `case `), meaning we should do something else.
|
||||
|
||||
fn assign_or_destructure_identifier<'a>() -> impl Parser<'a, Ident<'a>, EExpr<'a>> {
|
||||
crate::ident::parse_ident_help
|
||||
crate::ident::parse_ident
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -2238,7 +2238,7 @@ fn record_field_help<'a>(
|
||||
fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> {
|
||||
specialize(
|
||||
|_, r, c| ERecord::Updateable(r, c),
|
||||
map_with_arena!(parse_ident_help, ident_to_expr),
|
||||
map_with_arena!(parse_ident, ident_to_expr),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -138,13 +138,10 @@ macro_rules! advance_state {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parse_ident_help<'a>(
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
|
||||
pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
|
||||
let initial = state;
|
||||
|
||||
match parse_ident_help_help(arena, state) {
|
||||
match parse_ident_help(arena, state) {
|
||||
Ok((progress, ident, state)) => {
|
||||
if let Ident::Access { module_name, parts } = ident {
|
||||
if module_name.is_empty() {
|
||||
@ -526,7 +523,7 @@ fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Res
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ident_help_help<'a>(
|
||||
fn parse_ident_help<'a>(
|
||||
arena: &'a Bump,
|
||||
mut state: State<'a>,
|
||||
) -> ParseResult<'a, Ident<'a>, BadIdent> {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::ast::Pattern;
|
||||
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
|
||||
use crate::ident::{lowercase_ident, parse_ident_help, Ident};
|
||||
use crate::ident::{lowercase_ident, parse_ident, Ident};
|
||||
use crate::parser::Progress::{self, *};
|
||||
use crate::parser::{
|
||||
backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord,
|
||||
@ -172,8 +172,7 @@ fn loc_ident_pattern_help<'a>(
|
||||
let original_state = state;
|
||||
|
||||
let (_, loc_ident, state) =
|
||||
specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident_help))
|
||||
.parse(arena, state)?;
|
||||
specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident)).parse(arena, state)?;
|
||||
|
||||
match loc_ident.value {
|
||||
Ident::GlobalTag(tag) => {
|
||||
|
@ -145,7 +145,7 @@ pub fn can_expr_with<'a>(
|
||||
|
||||
let mut scope = Scope::new(home, &mut var_store);
|
||||
let dep_idents = IdentIds::exposed_builtins(0);
|
||||
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
||||
let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default());
|
||||
let (loc_expr, output) = canonicalize_expr(
|
||||
&mut env,
|
||||
&mut var_store,
|
||||
|
@ -15,6 +15,7 @@ roc_can = { path = "../compiler/can" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_region = { path = "../compiler/region" }
|
||||
roc_types = { path = "../compiler/types" }
|
||||
roc_parse = { path = "../compiler/parse" }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
bumpalo = { version = "3.2", features = ["collections"] }
|
||||
|
||||
|
417
docs/src/lib.rs
417
docs/src/lib.rs
@ -1,22 +1,24 @@
|
||||
extern crate pulldown_cmark;
|
||||
use roc_builtins::std::StdLib;
|
||||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_load::docs::{DocEntry, TypeAnnotation};
|
||||
use roc_load::docs::{ModuleDocumentation, RecordField};
|
||||
use roc_load::file::{LoadedModule, LoadingProblem};
|
||||
use roc_module::symbol::Interns;
|
||||
|
||||
use std::fs;
|
||||
extern crate roc_load;
|
||||
use bumpalo::Bump;
|
||||
use roc_builtins::std::StdLib;
|
||||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_load::docs::DocEntry::DocDef;
|
||||
use roc_load::docs::{DocEntry, TypeAnnotation};
|
||||
use roc_load::docs::{ModuleDocumentation, RecordField};
|
||||
use roc_load::file::{LoadedModule, LoadingProblem};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId};
|
||||
use roc_parse::ident::{parse_ident, Ident};
|
||||
use roc_parse::parser::State;
|
||||
use roc_region::all::Region;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
||||
let files_docs = files_to_documentations(filenames, std_lib);
|
||||
let mut arena = Bump::new();
|
||||
|
||||
//
|
||||
// TODO: get info from a file like "elm.json"
|
||||
@ -63,11 +65,17 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
||||
|
||||
// Write each package's module docs html file
|
||||
for loaded_module in package.modules.iter_mut() {
|
||||
let exports = loaded_module
|
||||
.exposed_values
|
||||
.iter()
|
||||
.map(|symbol| symbol.ident_string(&loaded_module.interns).to_string())
|
||||
.collect::<Vec<String>>();
|
||||
arena.reset();
|
||||
|
||||
let mut exports: bumpalo::collections::Vec<&str> =
|
||||
bumpalo::collections::Vec::with_capacity_in(loaded_module.exposed_values.len(), &arena);
|
||||
|
||||
// TODO should this also include exposed_aliases?
|
||||
for symbol in loaded_module.exposed_values.iter() {
|
||||
exports.push(symbol.ident_string(&loaded_module.interns));
|
||||
}
|
||||
|
||||
let exports = exports.into_bump_slice();
|
||||
|
||||
for module in loaded_module.documentation.values_mut() {
|
||||
let module_dir = build_dir.join(module.name.replace(".", "/").as_str());
|
||||
@ -83,7 +91,14 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
||||
)
|
||||
.replace(
|
||||
"<!-- Module Docs -->",
|
||||
render_main_content(&loaded_module.interns, &exports, module).as_str(),
|
||||
render_main_content(
|
||||
loaded_module.module_id,
|
||||
exports,
|
||||
&loaded_module.dep_idents,
|
||||
&loaded_module.interns,
|
||||
module,
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
fs::write(module_dir.join("index.html"), rendered_module)
|
||||
@ -95,8 +110,10 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
|
||||
}
|
||||
|
||||
fn render_main_content(
|
||||
home: ModuleId,
|
||||
exposed_values: &[&str],
|
||||
dep_idents: &MutMap<ModuleId, IdentIds>,
|
||||
interns: &Interns,
|
||||
exposed_values: &[String],
|
||||
module: &mut ModuleDocumentation,
|
||||
) -> String {
|
||||
let mut buf = String::new();
|
||||
@ -115,7 +132,7 @@ fn render_main_content(
|
||||
|
||||
if let DocDef(def) = entry {
|
||||
// We dont want to render entries that arent exposed
|
||||
should_render_entry = exposed_values.contains(&def.name);
|
||||
should_render_entry = exposed_values.contains(&def.name.as_str());
|
||||
}
|
||||
|
||||
if should_render_entry {
|
||||
@ -158,12 +175,27 @@ fn render_main_content(
|
||||
|
||||
if let Some(docs) = &doc_def.docs {
|
||||
buf.push_str(
|
||||
markdown_to_html(&mut module.scope, interns, docs.to_string()).as_str(),
|
||||
markdown_to_html(
|
||||
home,
|
||||
exposed_values,
|
||||
dep_idents,
|
||||
&mut module.scope,
|
||||
interns,
|
||||
docs.to_string(),
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
DocEntry::DetachedDoc(docs) => {
|
||||
let markdown = markdown_to_html(&mut module.scope, interns, docs.to_string());
|
||||
let markdown = markdown_to_html(
|
||||
home,
|
||||
exposed_values,
|
||||
dep_idents,
|
||||
&mut module.scope,
|
||||
interns,
|
||||
docs.to_string(),
|
||||
);
|
||||
buf.push_str(markdown.as_str());
|
||||
}
|
||||
};
|
||||
@ -601,161 +633,234 @@ fn should_be_multiline(type_ann: &TypeAnnotation) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_doc_links(scope: &mut Scope, interns: &Interns, markdown: String) -> String {
|
||||
let buf = &markdown;
|
||||
let mut result = String::new();
|
||||
|
||||
let mut chomping_from: Option<usize> = None;
|
||||
|
||||
let mut chars = buf.chars().enumerate().peekable();
|
||||
|
||||
while let Some((index, char)) = chars.next() {
|
||||
match chomping_from {
|
||||
None => {
|
||||
let next_is_alphabetic = match chars.peek() {
|
||||
None => false,
|
||||
Some((_, next_char)) => next_char.is_alphabetic(),
|
||||
};
|
||||
|
||||
if char == '#' && next_is_alphabetic {
|
||||
chomping_from = Some(index);
|
||||
}
|
||||
}
|
||||
Some(from) => {
|
||||
if !(char.is_alphabetic() || char == '.') {
|
||||
let after_link = buf.chars().skip(from + buf.len());
|
||||
|
||||
result = buf.chars().take(from).collect();
|
||||
|
||||
let doc_link = make_doc_link(
|
||||
scope,
|
||||
interns,
|
||||
&buf.chars()
|
||||
.skip(from + 1)
|
||||
.take(index - from - 1)
|
||||
.collect::<String>(),
|
||||
);
|
||||
|
||||
result.insert_str(from, doc_link.as_str());
|
||||
|
||||
let remainder = insert_doc_links(scope, interns, after_link.collect());
|
||||
|
||||
result.push_str(remainder.as_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chomping_from == None {
|
||||
markdown
|
||||
} else {
|
||||
result
|
||||
}
|
||||
struct DocUrl {
|
||||
url: String,
|
||||
title: String,
|
||||
}
|
||||
|
||||
fn make_doc_link(scope: &mut Scope, interns: &Interns, doc_item: &str) -> String {
|
||||
match scope.lookup(&doc_item.into(), Region::zero()) {
|
||||
Ok(symbol) => {
|
||||
let module_str = symbol.module_string(interns);
|
||||
|
||||
let ident_str = symbol.ident_string(interns);
|
||||
|
||||
let mut link = String::new();
|
||||
|
||||
link.push('/');
|
||||
link.push_str(module_str);
|
||||
link.push('#');
|
||||
link.push_str(ident_str);
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
buf.push('[');
|
||||
buf.push_str(doc_item);
|
||||
buf.push_str("](");
|
||||
|
||||
buf.push_str(link.as_str());
|
||||
buf.push(')');
|
||||
|
||||
buf
|
||||
}
|
||||
Err(_) => {
|
||||
panic!(
|
||||
fn doc_url<'a>(
|
||||
home: ModuleId,
|
||||
exposed_values: &[&str],
|
||||
dep_idents: &MutMap<ModuleId, IdentIds>,
|
||||
scope: &mut Scope,
|
||||
interns: &'a Interns,
|
||||
mut module_name: &'a str,
|
||||
ident: &str,
|
||||
) -> DocUrl {
|
||||
if module_name.is_empty() {
|
||||
// This is an unqualified lookup, so look for the ident
|
||||
// in scope!
|
||||
match scope.lookup(&ident.into(), Region::zero()) {
|
||||
Ok(symbol) => {
|
||||
// Get the exact module_name from scope. It could be the
|
||||
// current module's name, but it also could be a different
|
||||
// module - for example, if this is in scope from an
|
||||
// unqualified import.
|
||||
module_name = symbol.module_string(interns);
|
||||
}
|
||||
Err(_) => {
|
||||
// TODO return Err here
|
||||
panic!(
|
||||
"Tried to generate an automatic link in docs for symbol `{}`, but that symbol was not in scope in this module. Scope was: {:?}",
|
||||
doc_item, scope
|
||||
)
|
||||
ident, scope
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match interns.module_ids.get_id(&module_name.into()) {
|
||||
Some(&module_id) => {
|
||||
// You can do qualified lookups on your own module, e.g.
|
||||
// if I'm in the Foo module, I can do a `Foo.bar` lookup.
|
||||
if module_id == home {
|
||||
// Check to see if the value is exposed in this module.
|
||||
// If it's not exposed, then we can't link to it!
|
||||
if !exposed_values.contains(&ident) {
|
||||
// TODO return Err here
|
||||
panic!(
|
||||
"Tried to generate an automatic link in docs for `{}.{}`, but `{}` is not declared in `{}`.",
|
||||
module_name, ident, ident, module_name);
|
||||
}
|
||||
} else {
|
||||
// This is not the home module
|
||||
match dep_idents
|
||||
.get(&module_id)
|
||||
.and_then(|exposed_ids| exposed_ids.get_id(&ident.into()))
|
||||
{
|
||||
Some(_) => {
|
||||
// This is a valid symbol for this dependency,
|
||||
// so proceed using the current module's name.
|
||||
//
|
||||
// TODO: In the future, this is where we'll
|
||||
// incorporate the package name into the link.
|
||||
}
|
||||
_ => {
|
||||
// TODO return Err here
|
||||
panic!(
|
||||
"Tried to generate an automatic link in docs for `{}.{}`, but `{}` is not exposed in `{}`.",
|
||||
module_name, ident, ident, module_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// TODO return Err here
|
||||
panic!("Tried to generate a doc link for `{}.{}` but the `{}` module was not imported!", module_name, ident, module_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut url = String::new();
|
||||
|
||||
// Example:
|
||||
//
|
||||
// module_name: "Str", ident: "join" => "/Str#join"
|
||||
url.push('/');
|
||||
url.push_str(module_name);
|
||||
url.push('#');
|
||||
url.push_str(ident);
|
||||
|
||||
DocUrl {
|
||||
url,
|
||||
title: format!("Docs for {}.{}", module_name, ident),
|
||||
}
|
||||
}
|
||||
|
||||
fn markdown_to_html(scope: &mut Scope, interns: &Interns, markdown: String) -> String {
|
||||
use pulldown_cmark::CodeBlockKind;
|
||||
use pulldown_cmark::CowStr;
|
||||
use pulldown_cmark::Event;
|
||||
use pulldown_cmark::Tag::*;
|
||||
fn markdown_to_html(
|
||||
home: ModuleId,
|
||||
exposed_values: &[&str],
|
||||
dep_idents: &MutMap<ModuleId, IdentIds>,
|
||||
scope: &mut Scope,
|
||||
interns: &Interns,
|
||||
markdown: String,
|
||||
) -> String {
|
||||
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Tag::*};
|
||||
|
||||
let markdown_with_links = insert_doc_links(scope, interns, markdown);
|
||||
let mut arena = Bump::new();
|
||||
let mut broken_link_callback = |link: BrokenLink| {
|
||||
// A shortcut link - see https://spec.commonmark.org/0.30/#shortcut-reference-link -
|
||||
// is something like `[foo]` in markdown. If you have a shortcut link
|
||||
// without a corresponding `[foo]: https://foo.com` entry
|
||||
// at the end of the document, we resolve it as an identifier based on
|
||||
// what's currently in scope, so you write things like [Str.join] or
|
||||
// [myFunction] and have them resolve to the docs for what you wrote.
|
||||
match link.link_type {
|
||||
LinkType::Shortcut => {
|
||||
let state = State::new(link.reference.as_bytes());
|
||||
|
||||
// Reset the bump arena so we aren't constantly reallocating
|
||||
// more memory.
|
||||
arena.reset();
|
||||
|
||||
match parse_ident(&arena, state) {
|
||||
Ok((_, Ident::Access { module_name, parts }, _)) => {
|
||||
let mut iter = parts.iter();
|
||||
|
||||
match iter.next() {
|
||||
Some(symbol_name) if iter.next().is_none() => {
|
||||
let DocUrl { url, title } = doc_url(
|
||||
home,
|
||||
exposed_values,
|
||||
dep_idents,
|
||||
scope,
|
||||
interns,
|
||||
module_name,
|
||||
symbol_name,
|
||||
);
|
||||
|
||||
Some((url.into(), title.into()))
|
||||
}
|
||||
_ => {
|
||||
// This had record field access,
|
||||
// e.g. [foo.bar] - which we
|
||||
// can't create a doc link to!
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((_, Ident::GlobalTag(type_name), _)) => {
|
||||
// This looks like a global tag name, but it could
|
||||
// be a type alias that's in scope, e.g. [I64]
|
||||
let DocUrl { url, title } = doc_url(
|
||||
home,
|
||||
exposed_values,
|
||||
dep_idents,
|
||||
scope,
|
||||
interns,
|
||||
"",
|
||||
type_name,
|
||||
);
|
||||
|
||||
Some((url.into(), title.into()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
let markdown_options = pulldown_cmark::Options::empty();
|
||||
let mut docs_parser = vec![];
|
||||
let (_, _) = pulldown_cmark::Parser::new_ext(&markdown_with_links, markdown_options).fold(
|
||||
(0, 0),
|
||||
|(start_quote_count, end_quote_count), event| {
|
||||
match event {
|
||||
// Replace this sequence (`>>>` syntax):
|
||||
// Start(BlockQuote)
|
||||
// Start(BlockQuote)
|
||||
// Start(BlockQuote)
|
||||
// Start(Paragraph)
|
||||
// For `Start(CodeBlock(Fenced(Borrowed("roc"))))`
|
||||
Event::Start(BlockQuote) => {
|
||||
let (_, _) = pulldown_cmark::Parser::new_with_broken_link_callback(
|
||||
&markdown,
|
||||
markdown_options,
|
||||
Some(&mut broken_link_callback),
|
||||
)
|
||||
.fold((0, 0), |(start_quote_count, end_quote_count), event| {
|
||||
match event {
|
||||
// Replace this sequence (`>>>` syntax):
|
||||
// Start(BlockQuote)
|
||||
// Start(BlockQuote)
|
||||
// Start(BlockQuote)
|
||||
// Start(Paragraph)
|
||||
// For `Start(CodeBlock(Fenced(Borrowed("roc"))))`
|
||||
Event::Start(BlockQuote) => {
|
||||
docs_parser.push(event);
|
||||
(start_quote_count + 1, 0)
|
||||
}
|
||||
Event::Start(Paragraph) => {
|
||||
if start_quote_count == 3 {
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.push(Event::Start(CodeBlock(CodeBlockKind::Fenced(
|
||||
CowStr::Borrowed("roc"),
|
||||
))));
|
||||
} else {
|
||||
docs_parser.push(event);
|
||||
(start_quote_count + 1, 0)
|
||||
}
|
||||
Event::Start(Paragraph) => {
|
||||
if start_quote_count == 3 {
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.push(Event::Start(CodeBlock(CodeBlockKind::Fenced(
|
||||
CowStr::Borrowed("roc"),
|
||||
))));
|
||||
} else {
|
||||
docs_parser.push(event);
|
||||
}
|
||||
(0, 0)
|
||||
}
|
||||
// Replace this sequence (`>>>` syntax):
|
||||
// End(Paragraph)
|
||||
// End(BlockQuote)
|
||||
// End(BlockQuote)
|
||||
// End(BlockQuote)
|
||||
// For `End(CodeBlock(Fenced(Borrowed("roc"))))`
|
||||
Event::End(Paragraph) => {
|
||||
docs_parser.push(event);
|
||||
(0, 1)
|
||||
}
|
||||
Event::End(BlockQuote) => {
|
||||
if end_quote_count == 3 {
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.push(Event::End(CodeBlock(CodeBlockKind::Fenced(
|
||||
CowStr::Borrowed("roc"),
|
||||
))));
|
||||
(0, 0)
|
||||
}
|
||||
// Replace this sequence (`>>>` syntax):
|
||||
// End(Paragraph)
|
||||
// End(BlockQuote)
|
||||
// End(BlockQuote)
|
||||
// End(BlockQuote)
|
||||
// For `End(CodeBlock(Fenced(Borrowed("roc"))))`
|
||||
Event::End(Paragraph) => {
|
||||
} else {
|
||||
docs_parser.push(event);
|
||||
(0, 1)
|
||||
}
|
||||
Event::End(BlockQuote) => {
|
||||
if end_quote_count == 3 {
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.pop();
|
||||
docs_parser.push(Event::End(CodeBlock(CodeBlockKind::Fenced(
|
||||
CowStr::Borrowed("roc"),
|
||||
))));
|
||||
(0, 0)
|
||||
} else {
|
||||
docs_parser.push(event);
|
||||
(0, end_quote_count + 1)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
docs_parser.push(event);
|
||||
(0, 0)
|
||||
(0, end_quote_count + 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
_ => {
|
||||
docs_parser.push(event);
|
||||
(0, 0)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut docs_html = String::new();
|
||||
|
||||
|
26
docs/tests/fixtures/Interface.roc
vendored
26
docs/tests/fixtures/Interface.roc
vendored
@ -1,26 +0,0 @@
|
||||
interface Test
|
||||
exposes [ singleline, multiline, multiparagraph, codeblock ]
|
||||
imports []
|
||||
|
||||
## This is a block
|
||||
Block elem : [ Block elem ]
|
||||
|
||||
## Single line documentation.
|
||||
singleline : Bool -> Bool
|
||||
|
||||
## Multiline documentation.
|
||||
## Without any complex syntax yet!
|
||||
multiline : Bool -> Bool
|
||||
|
||||
## Multiparagraph documentation.
|
||||
##
|
||||
## Without any complex syntax yet!
|
||||
multiparagraph : Bool -> Bool
|
||||
|
||||
## No documentation for not exposed function.
|
||||
notExposed : Bool -> Bool
|
||||
|
||||
## Turns >>> into code block for now.
|
||||
##
|
||||
## >>> codeblock
|
||||
codeblock : Bool -> Bool
|
@ -1,43 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
#[cfg(test)]
|
||||
mod insert_doc_links {
|
||||
use roc_can::env::Env;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_docs::insert_doc_links;
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleIds};
|
||||
use roc_types::subs::VarStore;
|
||||
|
||||
#[test]
|
||||
fn no_doc_links() {
|
||||
let home = ModuleIds::default().get_or_insert(&"Test".into());
|
||||
|
||||
let module_ids = ModuleIds::default();
|
||||
|
||||
let dep_idents = IdentIds::exposed_builtins(0);
|
||||
|
||||
let env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
||||
|
||||
let all_ident_ids = MutMap::default();
|
||||
|
||||
let interns = Interns {
|
||||
module_ids: env.module_ids.clone(),
|
||||
all_ident_ids,
|
||||
};
|
||||
|
||||
let var_store = &mut VarStore::default();
|
||||
let scope = &mut Scope::new(home, var_store);
|
||||
|
||||
let markdown = r#"
|
||||
# Hello
|
||||
Hello thanks for using my package
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
markdown,
|
||||
insert_doc_links(scope, &interns, markdown.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user