diff --git a/flake.lock b/flake.lock index 2045bcf..5c4c704 100644 --- a/flake.lock +++ b/flake.lock @@ -155,11 +155,11 @@ "nixpkgs-regression": "nixpkgs-regression" }, "locked": { - "lastModified": 1700475714, - "narHash": "sha256-8OXrkNaIpdtErfLN4u1Ew1IThTbk6n8POYRQfNatkSA=", + "lastModified": 1702384142, + "narHash": "sha256-AzZ/FLCMp8rqfY5wyFW+VvyEUA8OoZ43aBkAWp7NdPg=", "owner": "hsjobeki", "repo": "nix", - "rev": "9bf2153e696d88c6beb8e34709bb743af5cdd940", + "rev": "1c33d81594c08fc62e3e94e5496a53524beedf7a", "type": "github" }, "original": { diff --git a/pasta/default.nix b/pasta/default.nix index e16bd1a..53de4d9 100644 --- a/pasta/default.nix +++ b/pasta/default.nix @@ -5,7 +5,7 @@ pkgs.stdenv.mkDerivation { nativeBuildInputs = [ nix ]; buildPhase = '' nix-instantiate --eval --strict --json --store $PWD \ - eval.nix --arg 'pkgs' 'import ${nixpkgs} {}' -A docs.lib \ + eval.nix --arg 'pkgs' 'import ${nixpkgs} {}' -A all \ > $out ''; } diff --git a/pasta/src/eval.nix b/pasta/src/eval.nix index f984124..83c3716 100644 --- a/pasta/src/eval.nix +++ b/pasta/src/eval.nix @@ -25,17 +25,20 @@ let docs = { ############# Recusive analysis sets lib = collectFns lib { initialPath = [ "lib" ]; }; - rustTools = collectFns pkgs.pkgs.rustPackages { + rustTools = collectFns pkgs.rustPackages { initialPath = [ "pkgs" "rustPackages" ]; }; stdenvTools = getDocsFromSet pkgs.stdenv [ "pkgs" "stdenv" ]; ############# Non-recursive analysis sets pkgs = getDocsFromSet pkgs [ "pkgs" ]; - dockerTools = getDocsFromSet pkgs.pkgs.dockerTools [ "pkgs" "dockerTools" ]; + dockerTools = getDocsFromSet pkgs.dockerTools [ "pkgs" "dockerTools" ]; pythonTools = - getDocsFromSet pkgs.pkgs.pythonPackages [ "pkgs" "pythonPackages" ]; + getDocsFromSet pkgs.pythonPackages [ "pkgs" "pythonPackages" ]; + builtins = + getDocsFromSet builtins [ "builtins" ]; }; + all = builtins.foldl' (acc: name: acc ++ docs.${name}) [ ] (builtins.attrNames docs); # generate test_data for pesto test_data = { @@ -43,4 +46,4 @@ let }; in -{ inherit tools pkgs docs toFile test_data; } +{ inherit tools pkgs docs toFile getDocsFromSet collectFns all test_data; } diff --git a/pasta/src/tools.nix b/pasta/src/tools.nix index 7e2808d..4a64b0d 100644 --- a/pasta/src/tools.nix +++ b/pasta/src/tools.nix @@ -37,8 +37,8 @@ let { initialPath ? [ ], limit ? null, }: let filterFns = builtins.filter (item: - item.docs != null - # item.type == "lambda" + item.docs != null && + item.type == "lambda" ); getFnDocs = map (fn: { path = initialPath ++ fn.path; diff --git a/pesto/.gitignore b/pesto/.gitignore index 1de5659..3ed9c74 100644 --- a/pesto/.gitignore +++ b/pesto/.gitignore @@ -1 +1,2 @@ -target \ No newline at end of file +target +out \ No newline at end of file diff --git a/pesto/Cargo.lock b/pesto/Cargo.lock index 15b21fa..f12041c 100644 --- a/pesto/Cargo.lock +++ b/pesto/Cargo.lock @@ -395,6 +395,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "serde_yaml", "textwrap", "walkdir", ] @@ -555,6 +556,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_yaml" +version = "0.9.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +dependencies = [ + "indexmap 2.1.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "smawk" version = "0.3.2" @@ -642,6 +656,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unsafe-libyaml" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/pesto/Cargo.toml b/pesto/Cargo.toml index e6882ec..0aad4fa 100644 --- a/pesto/Cargo.toml +++ b/pesto/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "pesto" +description = "Gien a list of positions extract doc-comment into markdown and frontmatter." version = "0.1.0" edition = "2021" @@ -20,6 +21,7 @@ serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" expect-test = "1.4.0" serde_with = "3.4.0" +serde_yaml = "0.9.27" # [dev-dependencies] diff --git a/pesto/src/alias.rs b/pesto/src/alias.rs new file mode 100644 index 0000000..3fc6bc2 --- /dev/null +++ b/pesto/src/alias.rs @@ -0,0 +1,167 @@ +use std::{collections::HashMap, rc::Rc}; + +use crate::pasta::{AliasList, Docs, ValuePath}; + +// pub trait Aliases { +// fn find_aliases(self) -> AliasList; +// } + +// impl<'a> Aliases for DocIndex<'a> { +// fn find_aliases(self) -> AliasList { + +// } +// } + +/// Match +/// partially applied functions -> special case, don't know how it is "correct". Would need access to the upvalues? +/// Simple lambdas (not partially applied) +/// Match primop: (Doesnt have source position) +/// Eq countApplied, +/// Eq content +/// Other isPrimop, +/// Content not empty +/// Match Non-Primop +/// Eq position +pub fn find_aliases(item: &Docs, list: &Vec<&Docs>) -> AliasList { + let res: AliasList = list + .iter() + .filter_map(|other| { + if let (Some(s_meta), Some(o_meta)) = (&item.docs.lambda, &other.docs.lambda) { + // Avoid creating an alias for the same item. + if item.path == other.path { + return None; + } + // We cannot safely compare using the current value introspection if countApplied not eq 0. + if (s_meta.countApplied != Some(0)) + // Use less accurate name (only) aliases. This can lead to potentially false positives. + // e.g. lib.lists.last <=> lib.last + // comparing only the "last" string. Don't use any introspection + // TODO: find out how to compare partially applied values. + // A correct solution would include comparing the upValues ? + && item.path.last() == other.path.last() + { + return Some(other.path.clone()); + } + return match s_meta.isPrimop { + true => { + let is_empty = match &s_meta.content { + Some(c) => c.is_empty(), + None => true, + }; + + if o_meta.isPrimop && o_meta.content == s_meta.content && !is_empty { + return Some(other.path.clone()); + } + None + } + false => { + if s_meta.position == o_meta.position + && s_meta.countApplied == Some(0) + && s_meta.countApplied == o_meta.countApplied + { + return Some(other.path.clone()); + } + None + } + }; + } + None + }) + .collect(); + res +} + +pub struct FnCategories<'a> { + pub primop: Vec<&'a Docs>, + pub casual: Vec<&'a Docs>, + pub partial: Vec<&'a Docs>, +} +/// Build categories for efficiently finding aliases. (This is very expensive O(n^2). ) +/// Aliases can only exist within one subgroup, iterating over other items is a waste of time. +/// With the current value introspection, any value that is an alias of a builtin, also inherits the builtins docs and the isPrimop flag set. +/// +/// Group docs into the following subgroups +/// 1. primop_lambdas +/// e.g, lib.add, builtins.add +/// +/// 2.non_primop_lambdas +/// e.g, lib.attrByPath +/// +/// 3.partially_applied lambdas +/// e.g., concatLines (is concatMapStrings applied with f := Lambda<(s: s + "\n");>) +/// This is a special case, it is very hard, to properly detect aliases at this level. Although the alias must also be found in this subgroup. +pub fn categorize(data: &Vec) -> FnCategories { + // For finding aliases. + // Group docs into these subgroups. + // Aliases can only exist within one subgroup, iterating over other items is a waste of time. + let mut primop_lambdas: Vec<&Docs> = vec![]; + let mut non_primop_lambdas: Vec<&Docs> = vec![]; + let mut partially_applieds: Vec<&Docs> = vec![]; + + for item in data.iter() { + if let Some(lambda) = &item.docs.lambda { + match lambda.countApplied { + Some(0) | None => { + if lambda.isPrimop { + primop_lambdas.push(&item); + } + if !lambda.isPrimop { + non_primop_lambdas.push(&item); + } + } + _ => { + // # + partially_applieds.push(&item); + } + } + } + } + FnCategories { + primop: primop_lambdas, + casual: non_primop_lambdas, + partial: partially_applieds, + } +} + +pub type AliasMap = HashMap, AliasList>; + +pub fn init_alias_map(data: &Vec, categories: FnCategories) -> AliasMap { + let primop_lambdas = categories.primop; + let non_primop_lambdas = categories.casual; + let partially_applieds = categories.partial; + + let mut primops: Vec<&Docs> = vec![]; + primops.extend(primop_lambdas.iter().chain(&partially_applieds)); + + let mut non_primops: Vec<&Docs> = vec![]; + non_primops.extend(non_primop_lambdas.iter().chain(&partially_applieds)); + + let mut alias_map: AliasMap = HashMap::new(); + for item in data.iter() { + if let Some(lambda) = &item.docs.lambda { + match lambda.countApplied { + Some(0) => { + if lambda.isPrimop { + alias_map.insert(item.path.clone(), find_aliases(&item, &primop_lambdas)); + } + if !lambda.isPrimop { + alias_map + .insert(item.path.clone(), find_aliases(&item, &non_primop_lambdas)); + } + } + None => { + if lambda.isPrimop { + alias_map.insert(item.path.clone(), find_aliases(&item, &primops)); + } + if !lambda.isPrimop { + alias_map.insert(item.path.clone(), find_aliases(&item, &non_primops)); + } + } + Some(_) => { + alias_map.insert(item.path.clone(), find_aliases(&item, &partially_applieds)); + } + }; + } + } + alias_map +} diff --git a/pesto/src/bulk.rs b/pesto/src/bulk.rs index 0fde931..b9e531f 100644 --- a/pesto/src/bulk.rs +++ b/pesto/src/bulk.rs @@ -1,7 +1,8 @@ use std::{collections::HashMap, path::PathBuf, println, rc::Rc, time::Instant, vec}; use crate::{ - pasta::{Docs, Files, LambdaMeta, Pasta}, + alias::{categorize, init_alias_map}, + pasta::{Docs, Files, Pasta}, position::{DocComment, DocIndex, FilePosition, NixDocComment}, }; @@ -103,93 +104,6 @@ fn fill_docs( filled_docs } -/// Build categories for efficiently finding aliases. (This is very expensive O(n^2). ) -/// Aliases can only exist within one subgroup, iterating over other items is a waste of time. -/// With the current value introspection, any value that is an alias of a builtin, also inherits the builtins docs and the isPrimop flag set. -/// -/// Group docs into the following subgroups -/// 1. primop_lambdas -/// e.g, lib.add, builtins.add -/// -/// 2.non_primop_lambdas -/// e.g, lib.attrByPath -/// -/// 3.partially_applied lambdas -/// e.g., concatLines (is concatMapStrings applied with f := Lambda<(s: s + "\n");>) -/// This is a special case, it is very hard, to properly detect aliases at this level. Although the alias must also be found in this subgroup. -/// -fn categorize(data: &Vec) -> (Vec<&Docs>, Vec<&Docs>, Vec<&Docs>) { - // For finding aliases. - // Group docs into these subgroups. - // Aliases can only exist within one subgroup, iterating over other items is a waste of time. - let mut primop_lambdas: Vec<&Docs> = vec![]; - let mut non_primop_lambdas: Vec<&Docs> = vec![]; - let mut partially_applieds: Vec<&Docs> = vec![]; - - for item in data.iter() { - if let Some(lambda) = &item.docs.lambda { - match lambda.countApplied { - Some(0) | None => { - if lambda.isPrimop { - primop_lambdas.push(&item); - } - if !lambda.isPrimop { - non_primop_lambdas.push(&item); - } - } - _ => { - // # - partially_applieds.push(&item); - } - } - } - } - (primop_lambdas, non_primop_lambdas, partially_applieds) -} - -fn init_alias_map( - data: &Vec, - categories: (Vec<&Docs>, Vec<&Docs>, Vec<&Docs>), -) -> HashMap>, Vec>>> { - let (primop_lambdas, non_primop_lambdas, partially_applieds) = categories; - - let mut primops: Vec<&Docs> = vec![]; - primops.extend(primop_lambdas.iter()); - primops.extend(partially_applieds.iter()); - - let mut non_primops: Vec<&Docs> = vec![]; - non_primops.extend(non_primop_lambdas.iter()); - non_primops.extend(partially_applieds.iter()); - - let mut alias_map: HashMap>, Vec>>> = HashMap::new(); - for item in data.iter() { - if let Some(lambda) = &item.docs.lambda { - match lambda.countApplied { - Some(0) => { - if lambda.isPrimop { - alias_map.insert(item.path.clone(), find_aliases(&item, &primop_lambdas)); - } - if !lambda.isPrimop { - alias_map - .insert(item.path.clone(), find_aliases(&item, &non_primop_lambdas)); - } - } - None => { - if lambda.isPrimop { - alias_map.insert(item.path.clone(), find_aliases(&item, &primops)); - } - if !lambda.isPrimop { - alias_map.insert(item.path.clone(), find_aliases(&item, &non_primops)); - } - } - Some(_) => { - alias_map.insert(item.path.clone(), find_aliases(&item, &partially_applieds)); - } - }; - } - } - alias_map -} impl<'a> BulkProcessing for Pasta { fn new(path: &PathBuf) -> Self { let start_time = Instant::now(); @@ -199,7 +113,13 @@ impl<'a> BulkProcessing for Pasta { let mut pos_doc_map: HashMap<&FilePosition, Option> = HashMap::new(); for (path, lookups) in file_map.iter() { + if !path.exists() { + println!("file does not exist: {:?} Skipping.", path); + continue; + } + let positions = collect_file_positions(lookups); + println!( "{:?}: Lookups {:?}", path.file_name().unwrap(), @@ -240,70 +160,3 @@ impl<'a> BulkProcessing for Pasta { } } } - -/// How to find aliases: -/// Match -/// partially applied functions -> special case, don't know how it is "correct". Would need access to the upvalues? -/// Simple lambdas (not partially applied) -/// Match primop: (Doesnt have source position) -/// Eq countApplied, -/// Eq content -/// Other isPrimop, -/// Content not empty -/// Match Non-Primop -/// Eq position -fn find_aliases(item: &Docs, list: &Vec<&Docs>) -> Vec>> { - // println!("find aliases for {:?} \n\n in {:?}", item, list); - let res: Vec>> = list - .iter() - .filter_map(|other| { - if let (Some(s_meta), Some(o_meta)) = (&item.docs.lambda, &other.docs.lambda) { - // Avoid creating an alias for the same item. - if item.path == other.path { - return None; - } - if count_applied(s_meta) != 0 - // Use less accurate name aliases. This can lead to false positives - // TODO: figure out the proper way - && count_applied(o_meta) == count_applied(s_meta) - && item.path.last().unwrap() == other.path.last().unwrap() - { - return Some(other.path.clone()); - } - return match s_meta.isPrimop { - true => { - let is_empty = match &s_meta.content { - Some(c) => c.is_empty(), - None => true, - }; - - if o_meta.isPrimop - && o_meta.content == s_meta.content - && !is_empty - && count_applied(s_meta) == 0 - && count_applied(o_meta) == 0 - { - return Some(other.path.clone()); - } - None - } - false => { - if s_meta.position == o_meta.position - && count_applied(s_meta) == 0 - && count_applied(o_meta) == 0 - { - return Some(other.path.clone()); - } - None - } - }; - } - None - }) - .collect(); - res -} - -fn count_applied(meta: &LambdaMeta) -> usize { - meta.countApplied.unwrap_or(0) -} diff --git a/pesto/src/main.rs b/pesto/src/main.rs index c9d7536..7889cda 100644 --- a/pesto/src/main.rs +++ b/pesto/src/main.rs @@ -1,11 +1,24 @@ +mod alias; mod bulk; mod comment; mod pasta; mod position; mod tests; -use clap::Parser; -use std::{collections::HashMap, path::PathBuf, println}; +use clap::{Parser, ValueEnum}; +use pasta::{AliasList, ContentSource, Docs, Lookups, PositionType, SourceOrigin, ValuePath}; +use position::FilePosition; +use serde::Serialize; +use std::{ + collections::HashMap, + fs::{create_dir_all, File}, + io::Write, + path::PathBuf, + println, + process::exit, + rc::Rc, +}; +use textwrap::dedent; use crate::{ bulk::BulkProcessing, @@ -13,6 +26,12 @@ use crate::{ position::{DocComment, DocIndex}, }; +#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)] +enum Format { + JSON, + DIR, +} + #[derive(Debug, Parser)] #[command(author, version, about)] struct Options { @@ -21,6 +40,11 @@ struct Options { #[arg(long, conflicts_with_all=["line", "column", "file"])] pos_file: Option, + #[arg(long)] + format: Format, + /// Path to a directory for the output file(s). + out: String, + /// Path to the *.nix file that should be inspected. /// If provided, --line and --column must also be set. #[arg(long, requires_all=["line", "column", "file"])] @@ -38,6 +62,10 @@ pub fn main() { let opts = Options::parse(); if let Some(nix_file) = opts.file { + if !nix_file.exists() { + println!("file does not exist: {:?}", nix_file); + exit(1); + } let mut positions = HashMap::new(); positions.insert(opts.line.unwrap(), vec![opts.column.unwrap()]); @@ -50,5 +78,196 @@ pub fn main() { if let Some(pos_file) = opts.pos_file { let data = Pasta::new(&pos_file); + // data.doc_map + + let mut json_list: Vec = vec![]; + for item in data.docs.iter() { + let document = Document::new(&item, &data.doc_map); + let matter = document.meta; + let content = document.content; + match opts.format { + Format::DIR => { + if let Some((_, dir)) = item.path.split_last() { + let dir_dest = format!("{}/{}", opts.out, dir.join("/")); + let file_dest = format!("{}/{}.md", opts.out, item.path.join("/")); + create_dir_all(dir_dest).unwrap(); + let mut file = File::create(file_dest).unwrap(); + + file.write_all("---\n".as_bytes()).unwrap(); + file.write_all(serde_yaml::to_string(&matter).unwrap().as_bytes()) + .unwrap(); + file.write_all("---\n".as_bytes()).unwrap(); + + if let Some(content) = content.as_ref().map(|ref i| i.content).flatten() { + file.write_all(dedent(content).as_bytes()).unwrap(); + } + } + } + Format::JSON => json_list.push(CompressedDocument { + m: CompressedDocumentFrontmatter { + al: matter.aliases, + ip: matter.is_primop, + pm: matter.primop_meta.map(|m| CompressedPrimopMatter { + ar: m.args, + ay: m.arity, + }), + pa: matter.path, + }, + c: content.map(|c| c.clone()), + }), + } + } + if opts.format == Format::JSON { + let mut file = File::create(opts.out).unwrap(); + file.write_all(serde_json::to_string(&json_list).unwrap().as_bytes()) + .unwrap(); + } } } + +/// Find the content which should be displayed. +/// The own attribute content is the correct one usually. +/// Sometimes there is no attribute content. +/// The we search all the aliases for their attribute content. +/// As a fallback we can display the own lambda content. +fn find_document_content<'a>( + item: &'a Docs, + all: &'a HashMap, Docs>, +) -> Option> { + match &item.docs.attr.content { + Some(ref c) if !c.is_empty() => Some(ContentSource { + content: Some(c), + source: Some(SourceOrigin { + position: item.docs.attr.position.as_ref(), + path: Some(&item.path), + pos_type: Some(PositionType::Attribute), + }), + }), + _ => match item.fst_alias_content(&all) { + Some(d) => Some(d), + None => item.lambda_content(), + }, + } +} + +#[derive(Serialize, Debug, Clone)] +struct Document<'a> { + meta: DocumentFrontmatter<'a>, + content: Option>, +} + +#[derive(Serialize, Debug, Clone)] +struct DocumentFrontmatter<'a> { + path: &'a Rc, + aliases: Option<&'a AliasList>, + /// If an item is primop then it should have the PrimopMeta field. + is_primop: Option, + primop_meta: Option>, + /// Where the attribute is defined at. + attr_position: Option<&'a FilePosition>, + /// Where the original lambda is defined at. + lambda_position: Option<&'a FilePosition>, + /// How many times the function is applied. + count_applied: Option, + content_meta: Option>, + // content_position: Option<&'a FilePosition>, +} + +pub trait FromDocs<'a> { + fn new(docs: &'a Docs, data: &'a HashMap, Docs>) -> Self; +} + +impl<'a> FromDocs<'a> for Document<'a> { + fn new(item: &'a Docs, data: &'a HashMap, Docs>) -> Self { + let content = find_document_content(item, &data); + Self { + meta: DocumentFrontmatter { + // content_position: content + // .as_ref() + // .map(|t| t.source.as_ref().map(|s| s.position).flatten()) + // .flatten(), + content_meta: content.as_ref().map(|inner| inner.source.clone()).flatten(), + path: &item.path, + aliases: item.aliases.as_ref(), + attr_position: item.docs.attr.position.as_ref(), + lambda_position: item + .docs + .lambda + .as_ref() + .map(|i| i.position.as_ref()) + .flatten(), + is_primop: item.docs.lambda.as_ref().map(|i| i.isPrimop), + count_applied: item.docs.lambda.as_ref().map(|i| i.countApplied).flatten(), + primop_meta: match &item.docs.lambda { + None => None, + Some(lambda) if lambda.isPrimop => Some(PrimopMatter { + name: lambda.name.as_ref(), + args: lambda.args.as_ref(), + experimental: lambda.experimental, + arity: lambda.arity, + }), + _ => None, + }, + }, + content, + } + } +} + +#[derive(Serialize, Debug, Clone)] +struct PrimopMatter<'a> { + pub name: Option<&'a String>, + pub args: Option<&'a Vec>, + pub experimental: Option, + pub arity: Option, +} + +#[derive(Serialize, Debug, Clone)] +struct CompressedDocument<'a> { + /// meta + m: CompressedDocumentFrontmatter<'a>, + /// content + c: Option>, +} + +#[derive(Serialize, Debug, Clone)] +struct CompressedDocumentFrontmatter<'a> { + /// path + pa: &'a Rc, + // aliases + al: Option<&'a AliasList>, + /// If an item is primop then it should have the PrimopMeta field. + ip: Option, + /// primop meta + pm: Option>, +} + +#[derive(Serialize, Debug, Clone)] +struct CompressedContentSource<'a> { + // content + c: Option<&'a String>, + // position + p: Option<&'a FilePosition>, +} + +#[derive(Serialize, Debug, Clone)] +struct CompressedPrimopMatter<'a> { + // arguments + pub ar: Option<&'a Vec>, + // arity + pub ay: Option, +} + +// Translation matrix +// m: meta +// c: content +// p: position +// +// al: aliases +// ar: arguments +// ay: arity +// +// ip: is primop +// +// pa: path +// pm: primop meta diff --git a/pesto/src/pasta.rs b/pesto/src/pasta.rs index 904b7a2..fc83b74 100644 --- a/pesto/src/pasta.rs +++ b/pesto/src/pasta.rs @@ -40,16 +40,112 @@ pub struct DocsMeta { pub attr: AttrMeta, } +pub type ValuePath = Vec; +pub type AliasList = Vec>; + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Docs { pub docs: DocsMeta, - pub aliases: Option>>>, - pub path: Rc>, + pub aliases: Option, + pub path: Rc, +} + +#[derive(Serialize, Debug, Clone)] +pub struct ContentSource<'a> { + pub content: Option<&'a String>, + pub source: Option>, +} + +#[derive(Serialize, Debug, Clone)] +pub struct SourceOrigin<'a> { + pub position: Option<&'a FilePosition>, + pub path: Option<&'a Rc>, + pub pos_type: Option, +} + +#[derive(Serialize, Debug, Clone)] +pub enum PositionType { + Attribute, + Lambda, +} + +pub trait Lookups<'a> { + /// Returns the Lambda ContentSource. + /// + /// If there is a correct [ContentSource] return it. + /// + /// Partially applied functions still cary the underlying documentation which is wrong. + /// This inherited (but wrong) documentation is discarded + fn lambda_content(self: &'a Self) -> Option>; + + /// Return the docs of the first alias with docs. + /// + /// Only look at the aliases with content in the following order. + /// Return content from an alias with (1) attribute content, or (2) lambda content. + fn fst_alias_content( + self: &'a Self, + data: &'a HashMap, Docs>, + ) -> Option>; +} + +impl<'a> Lookups<'a> for Docs { + fn lambda_content(self: &'a Self) -> Option> { + self.docs + .lambda + .as_ref() + .map(|i| { + if i.countApplied == Some(0) || (i.countApplied == None && i.isPrimop) { + Some(ContentSource { + content: i.content.as_ref(), + source: Some(SourceOrigin { + position: i.position.as_ref(), + path: Some(&self.path), + pos_type: Some(PositionType::Lambda), + }), + }) + } else { + None + } + }) + .flatten() + } + + fn fst_alias_content( + self: &'a Self, + data: &'a HashMap, Docs>, + ) -> Option> { + match &self.aliases { + Some(aliases) => { + let x = aliases + .iter() + .find_map(|alias_path| { + data.get(alias_path).map(|i| { + if i.docs.attr.content.is_some() { + Some(ContentSource { + content: i.docs.attr.content.as_ref(), + source: Some(SourceOrigin { + position: i.docs.attr.position.as_ref(), + path: Some(&i.path), + pos_type: Some(PositionType::Attribute), + }), + }) + } else { + // i.lambda_content() + None + } + }) + }) + .flatten(); + x + } + _ => None, + } + } } pub struct Pasta { pub docs: Vec, - pub doc_map: HashMap>, Docs>, + pub doc_map: HashMap, Docs>, } pub trait Files { diff --git a/pesto/src/position.rs b/pesto/src/position.rs index 9be8515..6f3afea 100644 --- a/pesto/src/position.rs +++ b/pesto/src/position.rs @@ -8,7 +8,6 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use std::process::exit; use std::rc::Rc; -use std::time::Instant; use std::{format, fs, path::PathBuf, println}; @@ -120,28 +119,12 @@ impl<'a> DocComment<'a> for DocIndex<'a> { fn new(file: &'a PathBuf, positions: HashMap>) -> Self { let src = get_src(file); let rc: Rc = Rc::new(src); - let mut start_time = Instant::now(); + let ast = rnix::Root::parse(Rc::clone(&rc).as_str()).syntax(); - let mut end_time = Instant::now(); - // println!("{:?} - Parsed ast", end_time - start_time); - start_time = Instant::now(); let (pos_idx, inverse_pos_idx) = init_pos_idx(&file, positions); - end_time = Instant::now(); - // println!( - // "{:?} - Translated col,line into abs positions", - // end_time - start_time - // ); - // Call your function here - start_time = Instant::now(); let node_idx = init_node_idx(&ast, &inverse_pos_idx); - end_time = Instant::now(); - - // println!( - // "{:?} - Find all ast nodes for positions", - // end_time - start_time - // ); return Self { file, @@ -162,7 +145,6 @@ impl<'a> DocComment<'a> for DocIndex<'a> { } if let Some(idx) = idx { let expr = self.node_idx.get(idx); - // println!("L{}:C{}, expr: {:?}", line, column, expr); if let Some(Some(expr)) = expr { let doc = match expr.kind() { rnix::SyntaxKind::NODE_LAMBDA => { diff --git a/pesto/src/tests.rs b/pesto/src/tests.rs index 2fb1d0c..87e2932 100644 --- a/pesto/src/tests.rs +++ b/pesto/src/tests.rs @@ -1,12 +1,13 @@ #[cfg(test)] mod tests { - + use serde::Serialize; use std::{collections::HashMap, ffi::OsStr, format, fs, path::PathBuf, println, rc::Rc}; use crate::{ bulk::BulkProcessing, - pasta::Pasta, + pasta::{AliasList, Pasta, ValuePath}, position::{DocComment, DocIndex, TextPosition}, + Document, FromDocs, }; use expect_test::expect_file; @@ -38,7 +39,7 @@ mod tests { } #[test] - fn test_main() { + fn test_atoms() { dir_tests("atom", "nix", |path| { let mut pos_path = path.clone(); pos_path.set_extension("pos"); @@ -54,11 +55,66 @@ mod tests { format!("{:?}", pos.get_docs(line, column)) }) } + #[derive(Serialize, Debug)] + struct TestAlias { + aliases: Option, + path: Rc, + } #[test] fn test_aliases() { dir_tests("aliases", "json", |path| { let data: Pasta = Pasta::new(&PathBuf::from(path)); - serde_json::to_string_pretty(&data.docs).unwrap() + let aliases: Vec = data + .docs + .into_iter() + .map(|i| TestAlias { + aliases: i.aliases.clone(), + path: i.path.clone(), + }) + .collect(); + + serde_json::to_string_pretty(&aliases).unwrap() + }) + } + + #[derive(Serialize, Debug)] + struct TestContent { + name: String, + content: Option, + source: Option, + } + #[test] + fn test_content_inheritance() { + dir_tests("inheritance", "json", |path| { + let data: Pasta = Pasta::new(&PathBuf::from(path)); + let contents: Vec = data + .docs + .into_iter() + .map(|ref i| { + let document = &Document::new(i, &data.doc_map); + return TestContent { + name: document.meta.path.join("."), + content: document + .content + .as_ref() + .map(|inner| inner.content.map(|i| i.clone())) + .flatten(), + source: document + .content + .as_ref() + .map(|inner| { + inner + .source + .as_ref() + .map(|i| i.path.map(|p| p.join("."))) + .flatten() + }) + .flatten(), + }; + }) + .collect(); + + serde_json::to_string_pretty(&contents).unwrap() }) } diff --git a/pesto/test_data/aliases/add.expect b/pesto/test_data/aliases/add.expect index 577e0d5..f718b93 100644 --- a/pesto/test_data/aliases/add.expect +++ b/pesto/test_data/aliases/add.expect @@ -1,26 +1,5 @@ [ { - "docs": { - "lambda": { - "isPrimop": true, - "name": "add", - "args": [ - "e1", - "e2" - ], - "experimental": false, - "arity": 2, - "content": "\n Return the sum of the numbers *e1* and *e2*.\n " - }, - "attr": { - "position": { - "file": "test_data/assets/default.nix", - "line": 68, - "column": 23 - }, - "content": null - } - }, "aliases": [ [ "lib", @@ -38,27 +17,6 @@ ] }, { - "docs": { - "lambda": { - "isPrimop": true, - "name": "add", - "args": [ - "e1", - "e2" - ], - "experimental": false, - "arity": 2, - "content": "\n Return the sum of the numbers *e1* and *e2*.\n " - }, - "attr": { - "position": { - "file": "test_data/assets/trivial.nix", - "line": 269, - "column": 21 - }, - "content": null - } - }, "aliases": [ [ "lib", @@ -76,17 +34,6 @@ ] }, { - "docs": { - "lambda": { - "isPrimop": true, - "content": "\n Return the sum of the numbers *e1* and *e2*.\n ", - "countApplied": 0 - }, - "attr": { - "position": null, - "content": "" - } - }, "aliases": [ [ "lib", diff --git a/pesto/test_data/aliases/foldl.expect b/pesto/test_data/aliases/foldl.expect index 3fefdf2..677ab9d 100644 --- a/pesto/test_data/aliases/foldl.expect +++ b/pesto/test_data/aliases/foldl.expect @@ -1,25 +1,5 @@ [ { - "docs": { - "lambda": { - "isPrimop": false, - "position": { - "file": "test_data/assets/lists.nix", - "line": 204, - "column": 5 - }, - "content": "\n The binary operation to run, where the two arguments are:\n 1. `acc`: The current accumulator value: Either the initial one for the first iteration, or the result of the previous iteration\n 2. `x`: The corresponding list element for this iteration\n ", - "countApplied": 0 - }, - "attr": { - "position": { - "file": "test_data/assets/default.nix", - "line": 92, - "column": 25 - }, - "content": null - } - }, "aliases": [ [ "lib", @@ -33,26 +13,6 @@ ] }, { - "docs": { - "lambda": { - "isPrimop": false, - "position": { - "file": "test_data/assets/lists.nix", - "line": 204, - "column": 5 - }, - "content": "\n The binary operation to run, where the two arguments are:\n 1. `acc`: The current accumulator value: Either the initial one for the first iteration, or the result of the previous iteration\n 2. `x`: The corresponding list element for this iteration\n ", - "countApplied": 0 - }, - "attr": { - "position": { - "file": "test_data/assets/lists.nix", - "line": 198, - "column": 3 - }, - "content": "\n Reduce a list by applying a binary operator from left to right,\n starting with an initial accumulator.\n Before each application of the operator, the accumulator value is evaluated.\n This behavior makes this function stricter than [`foldl`](#function-library-lib.lists.foldl).\n Unlike [`builtins.foldl'`](https://nixos.org/manual/nix/unstable/language/builtins.html#builtins-foldl'),\n the initial accumulator argument is evaluated before the first iteration.\n A call like\n ```nix\n foldl' op acc₀ [ x₀ x₁ x₂ ... xₙ₋₁ xₙ ]\n ```\n is (denotationally) equivalent to the following,\n but with the added benefit that `foldl'` itself will never overflow the stack.\n ```nix\n let\n acc₁ = builtins.seq acc₀ (op acc₀ x₀ );\n acc₂ = builtins.seq acc₁ (op acc₁ x₁ );\n acc₃ = builtins.seq acc₂ (op acc₂ x₂ );\n ...\n accₙ = builtins.seq accₙ₋₁ (op accₙ₋₁ xₙ₋₁);\n accₙ₊₁ = builtins.seq accₙ (op accₙ xₙ );\n in\n accₙ₊₁\n # Or ignoring builtins.seq\n op (op (... (op (op (op acc₀ x₀) x₁) x₂) ...) xₙ₋₁) xₙ\n ```\n\n # Example\n\n ```nix\n foldl' (acc: x: acc + x) 0 [1 2 3]\n => 6\n ```\n\n # Type\n\n ```\n foldl' :: (acc -> x -> acc) -> acc -> [x] -> acc\n ```\n\n # Arguments\n\n - [op] The binary operation to run, where the two arguments are:\n\n1. `acc`: The current accumulator value: Either the initial one for the first iteration, or the result of the previous iteration\n2. `x`: The corresponding list element for this iteration\n - [acc] The initial accumulator value\n - [list] The list to fold\n\n " - } - }, "aliases": [ [ "lib", @@ -66,24 +26,6 @@ ] }, { - "docs": { - "lambda": { - "isPrimop": true, - "name": "foldl'", - "args": [ - "op", - "nul", - "list" - ], - "experimental": false, - "arity": 3, - "content": "\n Reduce a list by applying a binary operator, from left to right,\n e.g. `foldl' op nul [x0 x1 x2 ...] : op (op (op nul x0) x1) x2)\n ...`. For example, `foldl' (x: y: x + y) 0 [1 2 3]` evaluates to 6.\n The return value of each application of `op` is evaluated immediately,\n even for intermediate values.\n " - }, - "attr": { - "position": null, - "content": null - } - }, "aliases": [], "path": [ "builtins", diff --git a/pesto/test_data/aliases/strings.expect b/pesto/test_data/aliases/strings.expect index d8e4be3..5a6ea1d 100644 --- a/pesto/test_data/aliases/strings.expect +++ b/pesto/test_data/aliases/strings.expect @@ -1,26 +1,11 @@ [ { - "docs": { - "lambda": { - "isPrimop": false, - "position": { - "file": "test_data/assets/strings.nix", - "line": 84, - "column": 25 - }, - "content": "\n Map a function over a list and concatenate the resulting strings.\n\n # Example\n\n ```nix\n concatMapStrings (x: \"a\" + x) [\"foo\" \"bar\"]\n => \"afooabar\"\n ```\n\n # Type\n\n ```\n concatMapStrings :: (a -> string) -> [a] -> string\n ```\n\n # Arguments\n\n - [f] \n - [list] \n\n ", - "countApplied": 1 - }, - "attr": { - "position": { - "file": "test_data/assets/strings.nix", - "line": 243, - "column": 3 - }, - "content": "\n Concatenate a list of strings, adding a newline at the end of each one.\n Defined as `concatMapStrings (s: s + \"\\n\")`.\n\n # Example\n\n ```nix\n concatLines [ \"foo\" \"bar\" ]\n => \"foo\\nbar\\n\"\n ```\n\n # Type\n\n ```\n concatLines :: [string] -> string\n ```\n " - } - }, - "aliases": [], + "aliases": [ + [ + "lib", + "concatLines" + ] + ], "path": [ "lib", "strings", @@ -28,27 +13,13 @@ ] }, { - "docs": { - "lambda": { - "isPrimop": false, - "position": { - "file": "test_data/assets/strings.nix", - "line": 84, - "column": 25 - }, - "content": "\n Map a function over a list and concatenate the resulting strings.\n\n # Example\n\n ```nix\n concatMapStrings (x: \"a\" + x) [\"foo\" \"bar\"]\n => \"afooabar\"\n ```\n\n # Type\n\n ```\n concatMapStrings :: (a -> string) -> [a] -> string\n ```\n\n # Arguments\n\n - [f] \n - [list] \n\n ", - "countApplied": 1 - }, - "attr": { - "position": { - "file": "test_data/assets/default.nix", - "line": 98, - "column": 27 - }, - "content": null - } - }, - "aliases": [], + "aliases": [ + [ + "lib", + "strings", + "concatLines" + ] + ], "path": [ "lib", "concatLines" diff --git a/pesto/test_data/inheritance/add.expect b/pesto/test_data/inheritance/add.expect new file mode 100644 index 0000000..a4ccc8f --- /dev/null +++ b/pesto/test_data/inheritance/add.expect @@ -0,0 +1,17 @@ +[ + { + "name": "lib.add", + "content": "\n Return the sum of the numbers *e1* and *e2*.\n ", + "source": "lib.add" + }, + { + "name": "lib.trivial.add", + "content": "\n Return the sum of the numbers *e1* and *e2*.\n ", + "source": "lib.trivial.add" + }, + { + "name": "builtins.add", + "content": "\n Return the sum of the numbers *e1* and *e2*.\n ", + "source": "builtins.add" + } +] \ No newline at end of file diff --git a/pesto/test_data/inheritance/add.json b/pesto/test_data/inheritance/add.json new file mode 100644 index 0000000..4b79f3d --- /dev/null +++ b/pesto/test_data/inheritance/add.json @@ -0,0 +1,56 @@ +[ + { + "docs": { + "attr": { + "position": { + "column": 23, + "file": "test_data/assets/default.nix", + "line": 68 + } + }, + "lambda": { + "args": ["e1", "e2"], + "arity": 2, + "content": "\n Return the sum of the numbers *e1* and *e2*.\n ", + "experimental": false, + "isPrimop": true, + "name": "add", + "position": null + } + }, + "path": ["lib", "add"] + }, + { + "docs": { + "attr": { + "position": { + "column": 21, + "file": "test_data/assets/trivial.nix", + "line": 269 + } + }, + "lambda": { + "args": ["e1", "e2"], + "arity": 2, + "content": "\n Return the sum of the numbers *e1* and *e2*.\n ", + "experimental": false, + "isPrimop": true, + "name": "add", + "position": null + } + }, + "path": ["lib", "trivial", "add"] + }, + { + "docs": { + "attr": { "content": "", "position": null }, + "lambda": { + "content": "\n Return the sum of the numbers *e1* and *e2*.\n ", + "countApplied": 0, + "isPrimop": true, + "position": null + } + }, + "path": ["builtins", "add"] + } +] diff --git a/pesto/test_data/inheritance/concatStrings.expect b/pesto/test_data/inheritance/concatStrings.expect new file mode 100644 index 0000000..2de2c06 --- /dev/null +++ b/pesto/test_data/inheritance/concatStrings.expect @@ -0,0 +1,7 @@ +[ + { + "name": "pkgs.lib.strings.concatStrings", + "content": "\n Concatenate a list of strings.\n\n # Example\n\n ```nix\n concatStrings [\"foo\" \"bar\"]\n => \"foobar\"\n ```\n\n # Type\n\n ```\n concatStrings :: [string] -> string\n ```\n ", + "source": "pkgs.lib.strings.concatStrings" + } +] \ No newline at end of file diff --git a/pesto/test_data/inheritance/concatStrings.json b/pesto/test_data/inheritance/concatStrings.json new file mode 100644 index 0000000..83a28f6 --- /dev/null +++ b/pesto/test_data/inheritance/concatStrings.json @@ -0,0 +1,20 @@ +[ + { + "docs": { + "attr": { + "position": { + "column": 3, + "file": "test_data/assets/strings.nix", + "line": 60 + } + }, + "lambda": { + "content": "\n Concatenate a list of strings with a separator between each\n element, e.g. `concatStringsSep \"/\" [\"usr\" \"local\" \"bin\"] ==\n \"usr/local/bin\"`.\n ", + "countApplied": 1, + "isPrimop": true, + "position": null + } + }, + "path": ["pkgs", "lib", "strings", "concatStrings"] + } +] diff --git a/pesto/test_data/inheritance/foldl.expect b/pesto/test_data/inheritance/foldl.expect new file mode 100644 index 0000000..77ae23b --- /dev/null +++ b/pesto/test_data/inheritance/foldl.expect @@ -0,0 +1,17 @@ +[ + { + "name": "lib.foldl'", + "content": "\n Reduce a list by applying a binary operator from left to right,\n starting with an initial accumulator.\n Before each application of the operator, the accumulator value is evaluated.\n This behavior makes this function stricter than [`foldl`](#function-library-lib.lists.foldl).\n Unlike [`builtins.foldl'`](https://nixos.org/manual/nix/unstable/language/builtins.html#builtins-foldl'),\n the initial accumulator argument is evaluated before the first iteration.\n A call like\n ```nix\n foldl' op acc₀ [ x₀ x₁ x₂ ... xₙ₋₁ xₙ ]\n ```\n is (denotationally) equivalent to the following,\n but with the added benefit that `foldl'` itself will never overflow the stack.\n ```nix\n let\n acc₁ = builtins.seq acc₀ (op acc₀ x₀ );\n acc₂ = builtins.seq acc₁ (op acc₁ x₁ );\n acc₃ = builtins.seq acc₂ (op acc₂ x₂ );\n ...\n accₙ = builtins.seq accₙ₋₁ (op accₙ₋₁ xₙ₋₁);\n accₙ₊₁ = builtins.seq accₙ (op accₙ xₙ );\n in\n accₙ₊₁\n # Or ignoring builtins.seq\n op (op (... (op (op (op acc₀ x₀) x₁) x₂) ...) xₙ₋₁) xₙ\n ```\n\n # Example\n\n ```nix\n foldl' (acc: x: acc + x) 0 [1 2 3]\n => 6\n ```\n\n # Type\n\n ```\n foldl' :: (acc -> x -> acc) -> acc -> [x] -> acc\n ```\n\n # Arguments\n\n - [op] The binary operation to run, where the two arguments are:\n\n1. `acc`: The current accumulator value: Either the initial one for the first iteration, or the result of the previous iteration\n2. `x`: The corresponding list element for this iteration\n - [acc] The initial accumulator value\n - [list] The list to fold\n\n ", + "source": "lib.lists.foldl'" + }, + { + "name": "lib.lists.foldl'", + "content": "\n Reduce a list by applying a binary operator from left to right,\n starting with an initial accumulator.\n Before each application of the operator, the accumulator value is evaluated.\n This behavior makes this function stricter than [`foldl`](#function-library-lib.lists.foldl).\n Unlike [`builtins.foldl'`](https://nixos.org/manual/nix/unstable/language/builtins.html#builtins-foldl'),\n the initial accumulator argument is evaluated before the first iteration.\n A call like\n ```nix\n foldl' op acc₀ [ x₀ x₁ x₂ ... xₙ₋₁ xₙ ]\n ```\n is (denotationally) equivalent to the following,\n but with the added benefit that `foldl'` itself will never overflow the stack.\n ```nix\n let\n acc₁ = builtins.seq acc₀ (op acc₀ x₀ );\n acc₂ = builtins.seq acc₁ (op acc₁ x₁ );\n acc₃ = builtins.seq acc₂ (op acc₂ x₂ );\n ...\n accₙ = builtins.seq accₙ₋₁ (op accₙ₋₁ xₙ₋₁);\n accₙ₊₁ = builtins.seq accₙ (op accₙ xₙ );\n in\n accₙ₊₁\n # Or ignoring builtins.seq\n op (op (... (op (op (op acc₀ x₀) x₁) x₂) ...) xₙ₋₁) xₙ\n ```\n\n # Example\n\n ```nix\n foldl' (acc: x: acc + x) 0 [1 2 3]\n => 6\n ```\n\n # Type\n\n ```\n foldl' :: (acc -> x -> acc) -> acc -> [x] -> acc\n ```\n\n # Arguments\n\n - [op] The binary operation to run, where the two arguments are:\n\n1. `acc`: The current accumulator value: Either the initial one for the first iteration, or the result of the previous iteration\n2. `x`: The corresponding list element for this iteration\n - [acc] The initial accumulator value\n - [list] The list to fold\n\n ", + "source": "lib.lists.foldl'" + }, + { + "name": "builtins.foldl'", + "content": "\n Reduce a list by applying a binary operator, from left to right,\n e.g. `foldl' op nul [x0 x1 x2 ...] : op (op (op nul x0) x1) x2)\n ...`. For example, `foldl' (x: y: x + y) 0 [1 2 3]` evaluates to 6.\n The return value of each application of `op` is evaluated immediately,\n even for intermediate values.\n ", + "source": "builtins.foldl'" + } +] \ No newline at end of file diff --git a/pesto/test_data/inheritance/foldl.json b/pesto/test_data/inheritance/foldl.json new file mode 100644 index 0000000..7a72a98 --- /dev/null +++ b/pesto/test_data/inheritance/foldl.json @@ -0,0 +1,57 @@ +[ + { + "docs": { + "attr": { + "position": { + "column": 25, + "file": "test_data/assets/default.nix", + "line": 92 + } + }, + "lambda": { + "isPrimop": false, + "position": { + "column": 5, + "file": "test_data/assets/lists.nix", + "line": 204 + } + } + }, + "path": ["lib", "foldl'"] + }, + { + "docs": { + "attr": { + "position": { + "column": 3, + "file": "test_data/assets/lists.nix", + "line": 198 + } + }, + "lambda": { + "isPrimop": false, + "position": { + "column": 5, + "file": "test_data/assets/lists.nix", + "line": 204 + } + } + }, + "path": ["lib", "lists", "foldl'"] + }, + { + "docs": { + "attr": { "position": null }, + "lambda": { + "args": ["op", "nul", "list"], + "arity": 3, + "content": "\n Reduce a list by applying a binary operator, from left to right,\n e.g. `foldl' op nul [x0 x1 x2 ...] : op (op (op nul x0) x1) x2)\n ...`. For example, `foldl' (x: y: x + y) 0 [1 2 3]` evaluates to 6.\n The return value of each application of `op` is evaluated immediately,\n even for intermediate values.\n ", + "experimental": false, + "isPrimop": true, + "name": "foldl'", + "position": null + } + }, + "path": ["builtins", "foldl'"] + } +] diff --git a/pesto/test_data/inheritance/strings.expect b/pesto/test_data/inheritance/strings.expect new file mode 100644 index 0000000..d605773 --- /dev/null +++ b/pesto/test_data/inheritance/strings.expect @@ -0,0 +1,12 @@ +[ + { + "name": "lib.strings.concatLines", + "content": "\n Concatenate a list of strings, adding a newline at the end of each one.\n Defined as `concatMapStrings (s: s + \"\\n\")`.\n\n # Example\n\n ```nix\n concatLines [ \"foo\" \"bar\" ]\n => \"foo\\nbar\\n\"\n ```\n\n # Type\n\n ```\n concatLines :: [string] -> string\n ```\n ", + "source": "lib.strings.concatLines" + }, + { + "name": "lib.concatLines", + "content": "\n Concatenate a list of strings, adding a newline at the end of each one.\n Defined as `concatMapStrings (s: s + \"\\n\")`.\n\n # Example\n\n ```nix\n concatLines [ \"foo\" \"bar\" ]\n => \"foo\\nbar\\n\"\n ```\n\n # Type\n\n ```\n concatLines :: [string] -> string\n ```\n ", + "source": "lib.strings.concatLines" + } +] \ No newline at end of file diff --git a/pesto/test_data/inheritance/strings.json b/pesto/test_data/inheritance/strings.json new file mode 100644 index 0000000..5d14ff3 --- /dev/null +++ b/pesto/test_data/inheritance/strings.json @@ -0,0 +1,42 @@ +[ + { + "docs": { + "attr": { + "position": { + "column": 3, + "file": "test_data/assets/strings.nix", + "line": 243 + } + }, + "lambda": { + "isPrimop": false, + "position": { + "column": 25, + "file": "test_data/assets/strings.nix", + "line": 84 + } + } + }, + "path": ["lib", "strings", "concatLines"] + }, + { + "docs": { + "attr": { + "position": { + "column": 27, + "file": "test_data/assets/default.nix", + "line": 98 + } + }, + "lambda": { + "isPrimop": false, + "position": { + "column": 25, + "file": "test_data/assets/strings.nix", + "line": 84 + } + } + }, + "path": ["lib", "concatLines"] + } +] diff --git a/website/src/app/ref/[...id]/page.tsx b/website/src/app/ref/[...id]/page.tsx index 065d18a..cd40387 100644 --- a/website/src/app/ref/[...id]/page.tsx +++ b/website/src/app/ref/[...id]/page.tsx @@ -1,7 +1,7 @@ -// import { DocsFrontmatter, getMdxMeta } from "@/components/ListGroup"; import { docsDir, extractHeadings, + getMdxMeta, getMdxSource, mdxRenderOptions, } from "@/utils"; @@ -39,20 +39,22 @@ interface TocProps { const Toc = async (props: TocProps) => { const { mdxSource } = props; const headings = await extractHeadings(mdxSource); + return ( { // using the `params` returned by `generateStaticParams` export default async function Page(props: { params: { id: string[] } }) { const { mdxSource } = await getMdxSource(props.params.id); + const meta = await getMdxMeta(props.params.id); + console.log("matter", meta.compiled.frontmatter); + const { frontmatter } = meta.compiled; return ( <> + - + + {frontmatter.path ? frontmatter.path.join(".") : frontmatter.title} + ( + // @ts-ignore + + ), + h2: (p) => ( + // @ts-ignore + + ), + h3: (p) => ( + // @ts-ignore + + ), + h4: (p) => ( + // @ts-ignore + + ), + h5: (p) => ( + // @ts-ignore + + ), + h6: (p) => ( + // @ts-ignore + + ), + }} /> - diff --git a/website/src/components/NavSidebar/ListGroup.tsx b/website/src/components/NavSidebar/ListGroup.tsx index 87a843f..7e365eb 100644 --- a/website/src/components/NavSidebar/ListGroup.tsx +++ b/website/src/components/NavSidebar/ListGroup.tsx @@ -17,6 +17,7 @@ export const ListGroup = async (props: ListGroupProps) => { return sorted.map(async ([name, entry], idx) => { if (Array.isArray(entry)) { const matter = await getMdxMeta(entry); + const { frontmatter } = matter.compiled; return ( { }} > - + diff --git a/website/src/docs/lib/add.md b/website/src/docs/lib/add.md new file mode 100644 index 0000000..0caad72 --- /dev/null +++ b/website/src/docs/lib/add.md @@ -0,0 +1,25 @@ +--- +path: +- lib +- trivial +- add +aliases: +- - lib + - add +is_primop: true +primop_meta: + name: add + args: + - e1 + - e2 + experimental: false + arity: 2 +attr_position: + file: /nix/store/knnp4h12pk09vfn18lrrrnh54zsvw3ba-source/lib/trivial.nix + line: 269 + column: 21 +lambda_position: null +count_applied: null +--- + Return the sum of the numbers *e1* and *e2*. + \ No newline at end of file diff --git a/website/src/docs/lib/attrsets/lib-attrsets-attrByPath.md b/website/src/docs/lib/attrsets/lib-attrsets-attrByPath.md index bf13340..379b746 100644 --- a/website/src/docs/lib/attrsets/lib-attrsets-attrByPath.md +++ b/website/src/docs/lib/attrsets/lib-attrsets-attrByPath.md @@ -11,6 +11,17 @@ Return an attribute from nested attribute sets. # Example +## H2 + +### H3 + +#### H4 + +##### H5 + +###### H6 + + ```nix x = { a = { b = 3; }; } # ["a" "b"] is equivalent to x.a.b diff --git a/website/src/utils.ts b/website/src/utils.ts index fec32b7..7a68f39 100644 --- a/website/src/utils.ts +++ b/website/src/utils.ts @@ -67,7 +67,7 @@ export async function generateStaticSidebarEntries() { return paths; } -export type DocsFrontmatter = { title: String }; +export type DocsFrontmatter = { title: String; path?: string[] }; export const getMdxMeta = async ( parts: string[]