Skip to content

Commit

Permalink
sorted items
Browse files Browse the repository at this point in the history
  • Loading branch information
StuartHarris committed Jun 6, 2024
1 parent c35ccbb commit c14bce9
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 83 deletions.
4 changes: 2 additions & 2 deletions crux_cli/src/codegen/item_processor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::crate_wrapper::CrateWrapper;
use super::nameable_item::NameableItem;
use super::CrateWrapper;
use super::{intermediate_public_item::IntermediatePublicItem, path_component::PathComponent};
use rustdoc_types::{
Crate, GenericArg, GenericArgs, Id, Impl, Import, Item, ItemEnum, Module, Path, Struct,
Expand Down Expand Up @@ -37,7 +37,7 @@ struct UnprocessedItem<'c> {
/// supported.
pub struct ItemProcessor<'c> {
/// The original and unmodified rustdoc JSON, in deserialized form.
crate_: CrateWrapper<'c>,
pub crate_: CrateWrapper<'c>,

/// A queue of unprocessed items to process.
work_queue: VecDeque<UnprocessedItem<'c>>,
Expand Down
41 changes: 4 additions & 37 deletions crux_cli/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,19 @@ mod item_processor;
mod nameable_item;
mod parser;
mod path_component;
mod public_api;
mod public_item;
use rustdoc_types::{Crate, Id, Item};

/// The [`Crate`] type represents the deserialized form of the rustdoc JSON
/// input. This wrapper adds some helpers and state on top.
pub struct CrateWrapper<'c> {
crate_: &'c Crate,

/// Normally, an item referenced by [`Id`] is present in the rustdoc JSON.
/// If [`Self::crate_.index`] is missing an [`Id`], then we add it here, to
/// aid with debugging. It will typically be missing because of bugs (or
/// borderline bug such as re-exports of foreign items like discussed in
/// <https://github.com/rust-lang/rust/pull/99287#issuecomment-1186586518>)
/// We do not report it to users by default, because they can't do anything
/// about it. Missing IDs will be printed with `--verbose` however.
missing_ids: Vec<&'c Id>,
}

impl<'c> CrateWrapper<'c> {
pub fn new(crate_: &'c Crate) -> Self {
Self {
crate_,
missing_ids: vec![],
}
}

pub fn get_item(&mut self, id: &'c Id) -> Option<&'c Item> {
self.crate_.index.get(id).or_else(|| {
self.missing_ids.push(id);
None
})
}

pub fn missing_item_ids(&self) -> Vec<String> {
self.missing_ids.iter().map(|m| m.0.clone()).collect()
}
}
mod render;
mod rust_types;
mod tokens;

use anyhow::{bail, Result};
use std::{
fs::File,
io::{stdout, IsTerminal},
};

use anyhow::{bail, Result};
use rustdoc_types::Crate;
use tokio::{process::Command, task::spawn_blocking};

use crate::{args::CodegenArgs, command_runner};
Expand Down
113 changes: 69 additions & 44 deletions crux_cli/src/codegen/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use std::collections::HashMap;
use anyhow::Result;
use rustdoc_types::{Crate, Id, Impl, ItemEnum, Path, Type};

use super::{
public_api::PublicApi,
rust_types::{RustEnum, RustStruct, RustTypeAlias},
};
use crate::codegen::{
item_processor::{sorting_prefix, ItemProcessor},
nameable_item::NameableItem,
Expand All @@ -11,8 +15,6 @@ use crate::codegen::{
render::RenderingContext,
};

use super::rust_types::{RustEnum, RustStruct, RustTypeAlias};

/// The results of parsing Rust source input.
#[derive(Default, Debug)]
pub struct ParsedData {
Expand Down Expand Up @@ -40,7 +42,8 @@ pub fn parse(crate_: &Crate) -> Result<ParsedData> {
crate_,
id_to_items: item_processor.id_to_items(),
};
let items: Vec<PublicItem> = item_processor

let items: Vec<_> = item_processor
.output
.iter()
.filter_map(|item| {
Expand All @@ -58,8 +61,23 @@ pub fn parse(crate_: &Crate) -> Result<ParsedData> {
.then_some(PublicItem::from_intermediate_public_item(&context, item))
})
.collect();
println!("{items:#?}");
Ok(ParsedData::new())

let mut public_api = PublicApi {
items,
missing_item_ids: item_processor.crate_.missing_item_ids(),
};

public_api.items.sort_by(PublicItem::grouping_cmp);

let mut parsed_data = ParsedData::new();

println!();

for item in public_api.items {
println!("{:?}", item.sortable_path);
println!("{}\n", item);
}
Ok(parsed_data)
}

fn add_items<'c: 'p, 'p>(
Expand All @@ -68,17 +86,13 @@ fn add_items<'c: 'p, 'p>(
filter: &'c [&'c str],
item_processor: &'p mut ItemProcessor<'c>,
) {
for (id, associated_items) in find_roots(crate_, trait_name, filter) {
let path = &crate_.paths[id].path;
println!("{} implements {trait_name}", path.join("::"));

let item = &crate_.index[id];
let module = path[..path.len() - 1].join("::");
for id in associated_items {
for root in find_roots(crate_, trait_name, filter) {
let item = &crate_.index[root.parent];
for id in root.assoc_types {
let parent = PathComponent {
item: NameableItem {
item,
overridden_name: Some(module.clone()),
overridden_name: None,
sorting_prefix: sorting_prefix(item),
},
type_: None,
Expand All @@ -89,47 +103,58 @@ fn add_items<'c: 'p, 'p>(
}
}

struct Root<'a> {
parent: &'a Id,
assoc_types: Vec<&'a Id>,
}

fn find_roots<'a>(
crate_: &'a Crate,
trait_name: &'a str,
filter: &'a [&'a str],
) -> impl Iterator<Item = (&'a Id, Vec<&'a Id>)> {
crate_.index.iter().filter_map(move |(_k, v)| {
if let ItemEnum::Impl(Impl {
trait_: Some(Path { name, .. }),
for_: Type::ResolvedPath(Path { id, .. }),
items,
..
}) = &v.inner
{
if name.as_str() == trait_name {
let assoc_types = items
.iter()
.filter_map(|id| {
let item = &crate_.index[id];
item.name.as_deref().and_then(|name| {
if filter.contains(&name) {
if let ItemEnum::AssocType {
default: Some(Type::ResolvedPath(Path { id, .. })),
..
} = &item.inner
{
Some(id)
) -> impl Iterator<Item = Root<'a>> {
crate_
.index
.iter()
.filter_map(move |(parent, parent_item)| {
if let ItemEnum::Impl(Impl {
trait_: Some(Path { name, .. }),
// for_: Type::ResolvedPath(_),
items,
..
}) = &parent_item.inner
{
if name.as_str() == trait_name {
let assoc_types = items
.iter()
.filter_map(|id| {
let item = &crate_.index[id];
item.name.as_deref().and_then(|name| {
if filter.contains(&name) {
if let ItemEnum::AssocType {
default: Some(Type::ResolvedPath(Path { id, .. })),
..
} = &item.inner
{
Some(id)
} else {
None
}
} else {
None
}
} else {
None
}
})
})
.collect();
Some(Root {
parent,
assoc_types,
})
.collect();
Some((id, assoc_types))
} else {
None
}
} else {
None
}
} else {
None
}
})
})
}
79 changes: 79 additions & 0 deletions crux_cli/src/codegen/public_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use super::public_item::PublicItem;

/// The public API of a crate
///
/// Create an instance with [`Builder`].
///
/// ## Rendering the items
///
/// To render the items in the public API you can iterate over the [items](PublicItem).
///
/// You get the `rustdoc_json_str` in the example below as explained in the [crate] documentation, either via
/// [`rustdoc_json`](https://crates.io/crates/rustdoc_json) or by calling `cargo rustdoc` yourself.
///
/// ```no_run
/// use public_api::PublicApi;
/// use std::path::PathBuf;
///
/// # let rustdoc_json: PathBuf = todo!();
/// // Gather the rustdoc content as described in this crates top-level documentation.
/// let public_api = public_api::Builder::from_rustdoc_json(&rustdoc_json).build()?;
///
/// for public_item in public_api.items() {
/// // here we print the items to stdout, we could also write to a string or a file.
/// println!("{}", public_item);
/// }
///
/// // If you want all items of the public API in a single big multi-line String then
/// // you can do like this:
/// let public_api_string = public_api.to_string();
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Debug)]
#[non_exhaustive] // More fields might be added in the future
pub struct PublicApi {
/// The items that constitutes the public API. An "item" is for example a
/// function, a struct, a struct field, an enum, an enum variant, a module,
/// etc...
pub(crate) items: Vec<PublicItem>,

/// See [`Self::missing_item_ids()`]
pub(crate) missing_item_ids: Vec<String>,
}

impl PublicApi {
/// Returns an iterator over all public items in the public API
pub fn items(&self) -> impl Iterator<Item = &'_ PublicItem> {
self.items.iter()
}

/// Like [`Self::items()`], but ownership of all `PublicItem`s are
/// transferred to the caller.
pub fn into_items(self) -> impl Iterator<Item = PublicItem> {
self.items.into_iter()
}

/// The rustdoc JSON IDs of missing but referenced items. Intended for use
/// with `--verbose` flags or similar.
///
/// In some cases, a public item might be referenced from another public
/// item (e.g. a `mod`), but is missing from the rustdoc JSON file. This
/// occurs for example in the case of re-exports of external modules (see
/// <https://github.com/Enselic/cargo-public-api/issues/103>). The entries
/// in this Vec are what IDs that could not be found.
///
/// The exact format of IDs are to be considered an implementation detail
/// and must not be be relied on.
pub fn missing_item_ids(&self) -> impl Iterator<Item = &String> {
self.missing_item_ids.iter()
}
}

impl std::fmt::Display for PublicApi {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for item in self.items() {
writeln!(f, "{item}")?;
}
Ok(())
}
}

0 comments on commit c14bce9

Please sign in to comment.