Code Monkey home page Code Monkey logo

cargo-intraconv's Introduction

CI crates.io crates.io

Cargo intraconv

cargo-intraconv is a simple helper which will transform Markdown links to intra-doc links in Rust projects when appropriate.

Intra-doc links have been stabilized in Rust 1.48.0, see the blog post annoucing it for more information.

What are intra-doc links ?

Previously the only way to write links to other elements of your crate (or other crates) was the following, the path depending on the current and target files:

/// [`make_ascii_uppercase`]: #method.make_ascii_uppercase

/// [`Path::ancestors`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.ancestors

/// See [the `Rotation` type](../struct.Rotation.html)

It is now possible to write them with Rust paths, depending on the path of the targeted item and what's in scope (which means items like String which are in the prelude are just a [String] away). Those links are clearer for both the person writing them in the first place and subsequent readers reviewing them. They are also easier to reason about since file hierachy does not affect them. Below is the intra-doc links version of the examples above:

/// [`make_ascii_uppercase`]: u8::make_ascii_uppercase()

/// [`Path::ancestors`]: std::path::Path::ancestors()

/// See [the `Rotation` type](super::Rotation)

When both side of the link are the same, it is possible to be even shorter:

/// See [`Rotation`]
///
- /// [`Rotation`]: struct.Rotation.html

- /// See [`Rotation`](struct.Rotation.html)
+ /// See [`Rotation`]

Why this crate ?

Changing all the existing links can be tedious and can be automated. This crate is here to help you update your documentation to intra-doc links as painlessly as possible.

Usage

By default the binary produced by the crate will not modify the given files, only show what would change:

# Call `cargo-intraconv` for a whole crate or workspace. Crate names will be
# discovered from `Cargo.toml` files.
cargo intraconv

# Call `cargo-intraconv` for specific file. No automatic crate name discovery.
cargo intraconv path/to/std/file.rs

# Specifying the root crate.
cargo intraconv path/to/core/file.rs -c core

# Applying the changes.
cargo intraconv path/to/std/file.rs -a

# Disambiguate links by prefixing them with their rustdoc group ('type@', ...).
cargo intraconv path/to/my/file.rs -d

# Do not transform favored links to intra-doc links (see below for more).
cargo intravonc path/to/my/file.rs -f

# Do not display changes, only errors.
cargo intraconv path/to/my/file.rs -q

# Give a file containing links to ignore.
#
# Note: `intraconv.toml` is read by default when present.
cargo intraconv path/to/my/file.rs -i intraconv.toml

It is possible to give multiple paths to files or directories. When searching exact paths, cargo-intraconv will use the crate name given with -c. For directories it will try to find the crate name in a Cargo.toml, falling back to the default of -c if the name cannot be transformed to a valid Rust identifier.

Note: intraconv will accept any file, no just .rs ones: you can use it on markdown files that are included as docs in Rust files for example.

Favored links

By default the crate will transform favored http(s):// links to intra-doc links (like those from docs.rs). To disable this behaviour use the -f (--no-favored) flag.

Ignoring links

cargo-intraconv is not perfect and will sometimes wrongly transform links, as in #31. To fix that you can either do it manually if you run it only once but if you start to run it several times because the changes are significative it will quickly become repetitive and error-prone. For a tool designed to reduce repetitive and error prone work, this is a sad thing !

When the --ignore-file (-i) is not passed, cargo-intraconv will read ìntraconv.toml by default (though it will not produce an error nor warning if it's missing since it's a default check).

To fix this, you can write a file in the TOML format with links to ignore:

# Global ignores, applied everywhere.
[ignore]
# Will be deleted by the second instance of `"name"`.
"name" = [ "link 1" ] # Must be an array, a single value will not work,
                      # this allows for multiples values easily.
"name" = [ "link 2" ]
# Will only keep one instance of 'link 3'.
"other name" = [ "link 3", "link 3", "link 1" ]

# Will match exactly the lib.rs file in tracing-core.
# NOTE: this path must either be absolute OR relative to the EXACT directory
# from which `cargo-intraconv` is called.
#
# Paths must be unique whether canonicalized (for paths with more than one
# component) or not (paths with a single component, as `lib.rs` below).
["tracing-core/src/lib.rs"]
# Will match both 'downcast_ref' and '`downcast_ref`' links.
"downcast_ref" = [ "#method.downcast_ref" ] # Must be an array.

# Will match EVERY lib.rs file found.
["lib.rs"]
"downcast_ref" = [ "#method.downcast_ref" ]

Using backticks (`) around the link names is not supported: this will never match any links:

[ignore]
# NEVER matches.
"`downcast_ref`" = [ "#method.downcast_ref" ]

Known issues

Both intra-doc links and this crate have several known issues, most of which should be adressed in future versions of either the crate or Rust itself.

For issues about intra-doc links you should look-up the issues at rust-lang/rust.

For issues about this crate, here is one:

  • #method.method_name links will sometimes be transformed to point to the wrong item. This is because intraconv uses regexes to find links and the types related to them, which is not perfect.

Drawbacks

It is not an official tool and the way it works right now is based on regexes. This approach means it is simple to understand but it has several drawbacks. For example cargo-intraconv is not aware of uses and will happily ignore them, even when they could shorten or remove links.

See Also

cargo-deadlinks is another tool for documentation and links. It works very well in conjunction with cargo-intraconv !

Contributing

Any form of contribution is appreciated ! You can open issues or PR and I'll try to answer you quickly !

License

See LICENSE at the repository's root.

cargo-intraconv's People

Contributors

jyn514 avatar poliorcetics avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

jyn514 isgasho

cargo-intraconv's Issues

`index.html` should link to `self`, not the current page

This is documentation on a struct, which is on a separate page from the module index.

 1463:  "/// [intrinsics]: index.html#intrinsic-variables"
        "/// [intrinsics]: #intrinsic-variables"

Instead it should link to self#intrinsic-variables.

Read `intraconv.toml` by default

I was about to report a bug that ignoring links didn't work, then realized that I hadn't passed --ignore-file. It would be nice for this to default to intraconv.toml so I don't have to remember it (and gracefully ignore the file if it doesn't exist).

Don't replace links starting with http(s)

-/// [fmt]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/index.html
+/// [fmt]: https:::::docs.rs::tracing-subscriber::latest::tracing_subscriber::fmt

Deletes attributes

-//!
-#![doc(html_root_url = "https://docs.rs/tracing-appender/0.1.1")]
+

Ignore links per-crate?

Right now you can ignore links globally:

[ignore]
"name" = [ "link 1" ]

and per-file:

["tracing-core/src/lib.rs"]
"`downcast_ref`" = [ "#method.downcast_ref" ]

but not per crate. It would be nice to be able to say "ignore links to String, but only in the core crate".

I'm not sure how to fit that in to the existing syntax - I think we might have hemmed ourselves in by getting too clever with the keys. Maybe something like this?

[[crate]]
name = "core"
"String" = "../../std/str/struct.String.html"

That means you can't ignore a name link I think, but I'm not sure what other syntax intra-conv could use.

Regression: adds lots of new whitespace

This is a regression from yesterday.

tracing-appender/src/inner.rs
=============================

    1:  "use std::io::{BufWriter, Write};
"
        "use std::io::{BufWriter, Write};
"

    2:  "
"
        "
"

    3:  "
"
        "
"

    4:  "
"
        "
"
diff --git a/tracing-appender/src/inner.rs b/tracing-appender/src/inner.rs
index f1e0fe0..6ade116 100644
--- a/tracing-appender/src/inner.rs
+++ b/tracing-appender/src/inner.rs
@@ -1,96 +1,768 @@
 use std::io::{BufWriter, Write};
+
+
+
+
+
+
+

Intraconv shouldn't convert links to primitives

Not sure how hard this is to implement - maybe not so bad since the HTML file has primitive in the name?

/home/joshua/src/rust/rust-clippy/clippy_lints/src/empty_enum.rs
================================================================

   16:  "    /// For further information visit [never type documentation](https://doc.rust-lang.org/std/primitive.never.html)"
        "    /// For further information visit [never type documentation](std::never)"

Properly detect blocks

tracing/tracing/src/lib.rs
==========================

   24:  "//! [msrv]: #supported-rust-versions"
        "//! [msrv]: MacroCallsite#supported-rust-versions"

This is wrong and it should stay empty.

Run on all crates in the workspace

Currently, the way I run intraconv is like this:

for crate in tracing*; do cargo intraconv $crate/**/*.rs -c $(echo $crate | tr - _) -a; done

It would be nice to have this built-in. In particular, this

  • Runs on all crates in the workspace
  • Sets the crate name appropriately
  • Runs on all files within a crate

Redundant links are not removed if they were originally HTTP links

At least, I think that's the issue. Not sure why tracing::Event was removed but not the others.

-//! [`tracing::Collector`]: https://docs.rs/tracing/latest/tracing/trait.Collect.html
-//! [`Collect`]: https://docs.rs/tracing/latest/tracing/trait.Collect.html
-//! [`tracing::Event`]: https://docs.rs/tracing/latest/tracing/struct.Event.html
+//! [`tracing::Collector`]: tracing::Collect
+//! [`Collect`]: tracing::Collect

Add a way to ignore links

Currently, every time I run intraconv, it changes this link:

-    /// [`downcast_ref`]: #method.downcast_ref
+    /// [`downcast_ref`]: Collect::downcast_ref()

That breaks because downcast_ref is a function on dyn Collect, not Collect itself. There's no way for intra-conv to know this, so I don't think it can be fixed on that end, but it would be nice to say "I know this link will break, don't convert it" with a configuration file or something.

Similarly

//! [span]: https://docs.rs/tracing/latest/tracing/span/index.html
//! [span]: tracing::span

gets converted every time, which breaks because tracing isn't in scope in tracing-attributes (rust-lang/rust#74481).

Convert doc.rust-lang.org to intra-doc links

Similar to #17. I'd like these links:

tracing/src/lib.rs://! [fmt]: https://doc.rust-lang.org/std/fmt/#usage
tracing/src/lib.rs://! [`libstd`]: https://doc.rust-lang.org/std/index.html
tracing/src/lib.rs://! [`liballoc`]: https://doc.rust-lang.org/alloc/index.html

to be turned into these:

tracing/src/lib.rs://! [fmt]: std::fmt#usage
tracing/src/lib.rs://! [`libstd`]: std
tracing/src/lib.rs://! [`liballoc`]: alloc

In an ideal world:

[stretch] convert <a href=""> to intra-doc links

Feel free to close this as wontfix.

Before:

    /// <strong>Note</strong>: The fields associated with a span are part of its
    /// <a href="../struct.Metadata.html"><code>Metadata</code></a>.
    /// The <a href="../struct.Metadata.html"><code>Metadata</code></a>. describing a particular
    /// span is constructed statically when the span is created and cannot be extended later to

After:

    /// <strong>Note</strong>: The fields associated with a span are part of its
    ///
    /// [`Metadata`](super::Metadata). The [Metadata](super::Metadata) describing a particular
    /// span is constructed statically when the span is created and cannot be extended later to

The extra blank line may not be necessary depending on the markdown parser, I haven't tested.

docs.rs/crate/x is treated as if crate/ is a crate

/home/joshua/src/rust/tracing/tracing-subscriber/src/registry/sharded.rs
========================================================================

   39:  "/// [slab]: https://docs.rs/crate/sharded-slab/"
        "/// [slab]: crate"

This can't be converted automatically, /crate/sharded-slab is not the target of any intra-doc link.

Ignore file compares the whole line, not the link

I have an idea for how to fix this and I'm interested in working on this myself.

Currently, this does nothing:

[ignore]
"`handle_alloc_error`" = ["../../alloc/alloc/fn.handle_alloc_error.html"]

because intra-conv is looking at the whole line:

[src/config_file.rs:80] name = "    /// [`handle_alloc_error`]: "
[src/config_file.rs:80] value = "../../alloc/alloc/fn.handle_alloc_error.html"
core: alloc/global.rs
=====

   89:  "    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html"
        "    /// [`handle_alloc_error`]: super::super::alloc::alloc::handle_alloc_error()"

Some links are not caught

$ rg '\[.*\]: \.\./.*' 
tracing-core/src/event.rs
20:/// [span]: ../span
21:/// [fields]: ../field

tracing/src/span.rs
403:    /// [metadata]: ../metadata
428:    /// [metadata]: ../metadata
452:    /// [metadata]: ../metadata

$ rg '\[.*\]: (trait|struct|enum|fn)\..*\.html*'
tracing-log/src/lib.rs
376:/// [`normalized_metadata`]: trait.NormalizeEvent.html#normalized_metadata

-a removes all whitespace

$ cargo intraconv -i
...
$ git d -w tracing-subscriber/src/registry/sharded.rs
... entire file is deleted ...
+#[cfg(feature = "registry")]#[cfg_attr(docsrs, doc(cfg(feature = "registry")))]#[derive(Debug)]pub struct Data<'a> {    /// Immutable reference to the pooled `DataInner` entry.    inner: Ref<'a, DataInner>,}/// Stored data associated with a span.////// This type is pooled using `sharded_slab::Pool`; when a span is dropped, the/// `DataInner` entry at that span's slab index is cleared in place and reused/// by a future span. Thus, the `Default` and `sharded_slab::Clear`/// implementations for this type are load-bearing.#[derive(Debug)]struct DataInner {    metadata: &'static Metadata<'static>,    parent: Option<Id>,    ref_count: AtomicUsize,    // The span's `Extensions` typemap. Allocations for the `HashMap` backing    // this are pooled and reused in place.    pub(crate) extensions: RwLock<ExtensionsInner>,}// === impl Registry ===impl Default for Registry {    fn default() -> Self {        Self {            spans: Pool::new(),            current_spans: ThreadLocal::new(),        }    }}#[inline]fn idx_to_id(idx: usize) -> Id {    Id::from_u64(idx as u64 + 1)}#[inline]fn id_to_idx(id: &Id) -> usize {    id.into_u64() as usize - 1}/// A guard that tracks how many [`Registry`]-backed `Subscriber`s have/// processed an `on_close` event.////// This is needed to enable a [`Registry`]-backed Subscriber to access span/// data after the `Subscriber` has recieved the `on_close` callback.////// Once all `Subscriber`s have processed this event, the [`Registry`] knows/// that is able to safely remove the span tracked by `id`. `CloseGuard`/// accomplishes this through a two-step process:/// 1. Whenever a [`Registry`]-backed `Subscriber::on_close` method is///    called, `Registry::start_close` is closed.///    `Registry::start_close` increments a thread-local `CLOSE_COUNT`///    by 1 and returns a `CloseGuard`./// 2. The `CloseGuard` is dropped at the end of `Subscriber::on_close`. On///    drop, `CloseGuard` checks thread-local `CLOSE_COUNT`. If///    `CLOSE_COUNT` is 0, the `CloseGuard` removes the span with the///    `id` from the registry, as all `Layers` that might have seen the///    `on_close` notification have processed it. If `CLOSE_COUNT` is///    greater than 0, `CloseGuard` decrements the counter by one and///    _does not_ remove the span from the [`Registry`].///pub(crate) struct CloseGuard<'a> {    id: Id,    registry: &'a Registry,    is_closing: bool,}impl Registry {    fn get(&self, id: &Id) -> Option<Ref<'_, DataInner>> {        self.spans.get(id_to_idx(id))    }    /// Returns a guard which tracks how many `Subscriber`s have    /// processed an `on_close` notification via the `CLOSE_COUNT` thread-local.    /// For additional details, see [`CloseGuard`].    ///    pub(crate) fn start_close(&self, id: Id) -> CloseGuard<'_> {        CLOSE_COUNT.with(|count| {            let c = count.get();            count.set(c + 1);        });        CloseGuard {            id,            registry: &self,            is_closing: false,        }    }}thread_local! {    /// `CLOSE_COUNT` is the thread-local counter used by `CloseGuard` to    /// track how many layers have processed the close.    /// For additional details, see [`CloseGuard`].    ///    static CLOSE_COUNT: Cell<usize> = Cell::new(0);}impl Collect for Registry {    fn register_callsite(&self, _: &'static Metadata<'static>) -> Interest {        Interest::always()    }    fn enabled(&self, _: &Metadata<'_>) -> bool {        true    }    #[inline]    fn new_span(&self, attrs: &span::Attributes<'_>) -> span::Id {        let parent = if attrs.is_root() {            None        } else if attrs.is_contextual() {            self.current_span().id().map(|id| self.clone_span(id))        } else {            attrs.parent().map(|id| self.clone_span(id))        };        let id = self            .spans            // Check out a `DataInner` entry from the pool for the new span. If            // there are free entries already allocated in the pool, this will            // preferentially reuse one; otherwise, a new `DataInner` is            // allocated and added to the pool.            .create_with(|data| {                data.metadata = attrs.metadata();                data.parent = parent;                let refs = data.ref_count.get_mut();                debug_assert_eq!(*refs, 0);                *refs = 1;            })            .expect("Unable to allocate another span");        idx_to_id(id)    }    /// This is intentionally not implemented, as recording fields    /// on a span is the responsibility of layers atop of this registry.    #[inline]    fn record(&self, _: &span::Id, _: &span::Record<'_>) {}    fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {}    /// This is intentionally not implemented, as recording events    /// is the responsibility of layers atop of this registry.    fn event(&self, _: &Event<'_>) {}    fn enter(&self, id: &span::Id) {        if self            .current_spans            .get_or_default()            .borrow_mut()            .push(id.clone())        {            self.clone_span(id);        }    }    fn exit(&self, id: &span::Id) {        if let Some(spans) = self.current_spans.get() {            if spans.borrow_mut().pop(id) {                dispatch::get_default(|dispatch| dispatch.try_close(id.clone()));            }        }    }    fn clone_span(&self, id: &span::Id) -> span::Id {        let span = self            .get(&id)            .unwrap_or_else(|| panic!("tried to clone {:?}, but no span exists with that ID", id));        // Like `std::sync::Arc`, adds to the ref count (on clone) don't require        // a strong ordering; if we call` clone_span`, the reference count must        // always at least 1. The only synchronization necessary is between        // calls to `try_close`: we have to ensure that all threads have        // dropped their refs to the span before the span is closed.        let refs = span.ref_count.fetch_add(1, Ordering::Relaxed);        assert!(            refs != 0,            "tried to clone a span ({:?}) that already closed",            id        );        id.clone()    }    fn current_span(&self) -> Current {        self.current_spans            .get()            .and_then(|spans| {                let spans = spans.borrow();                let id = spans.current()?;                let span = self.get(id)?;                Some(Current::new(id.clone(), span.metadata))            })            .unwrap_or_else(Current::none)    }    /// Decrements the reference count of the span with the given `id`, and    /// removes the span if it is zero.    ///    /// The allocated span slot will be reused when a new span is created.    fn try_close(&self, id: span::Id) -> bool {        let span = match self.get(&id) {            Some(span) => span,            None if std::thread::panicking() => return false,            None => panic!("tried to drop a ref to {:?}, but no such span exists!", id),        };        let refs = span.ref_count.fetch_sub(1, Ordering::Release);        if !std::thread::panicking() {            assert!(refs < std::usize::MAX, "reference count overflow!");        }        if refs > 1 {            return false;        }        // Synchronize if we are actually removing the span (stolen        // from std::Arc); this ensures that all other `try_close` calls on        // other threads happen-before we actually remove the span.        fence(Ordering::Acquire);        true    }}impl<'a> LookupSpan<'a> for Registry {    type Data = Data<'a>;    fn span_data(&'a self, id: &Id) -> Option<Self::Data> {        let inner = self.get(id)?;        Some(Data { inner })    }}// === impl CloseGuard ===impl<'a> CloseGuard<'a> {    pub(crate) fn is_closing(&mut self) {        self.is_closing = true;    }}impl<'a> Drop for CloseGuard<'a> {    fn drop(&mut self) {        // If this returns with an error, we are already panicking. At        // this point, there's nothing we can really do to recover        // except by avoiding a double-panic.        let _ = CLOSE_COUNT.try_with(|count| {            let c = count.get();            // Decrement the count to indicate that _this_ guard's            // `on_close` callback has completed.            //            // Note that we *must* do this before we actually remove the span            // from the registry, since dropping the `DataInner` may trigger a            // new close, if this span is the last reference to a parent span.            count.set(c - 1);            // If the current close count is 1, this stack frame is the last            // `on_close` call. If the span is closing, it's okay to remove the            // span.            if c == 1 && self.is_closing {                self.registry.spans.clear(id_to_idx(&self.id));            }        });    }}// === impl Data ===impl<'a> SpanData<'a> for Data<'a> {    fn id(&self) -> Id {        idx_to_id(self.inner.key())    }    fn metadata(&self) -> &'static Metadata<'static> {        (*self).inner.metadata    }    fn parent(&self) -> Option<&Id> {        self.inner.parent.as_ref()    }    fn extensions(&self) -> Extensions<'_> {        Extensions::new(self.inner.extensions.read().expect("Mutex poisoned"))    }    fn extensions_mut(&self) -> ExtensionsMut<'_> {        ExtensionsMut::new(self.inner.extensions.write().expect("Mutex poisoned"))    }}// === impl DataInner ===impl Default for DataInner {    fn default() -> Self {        // Since `DataInner` owns a `&'static Callsite` pointer, we need        // something to use as the initial default value for that callsite.        // Since we can't access a `DataInner` until it has had actual span data        // inserted into it, the null metadata will never actually be accessed.        struct NullCallsite;        impl tracing_core::callsite::Callsite for NullCallsite {            fn set_interest(&self, _: Interest) {                unreachable!(                    "/!\\ Tried to register the null callsite /!\\\n \                    This should never have happened and is definitely a bug. \                    A `tracing` bug report would be appreciated."                )            }            fn metadata(&self) -> &Metadata<'_> {                unreachable!(                    "/!\\ Tried to access the null callsite's metadata /!\\\n \                    This should never have happened and is definitely a bug. \                    A `tracing` bug report would be appreciated."                )            }        }        static NULL_CALLSITE: NullCallsite = NullCallsite;        static NULL_METADATA: Metadata<'static> = tracing_core::metadata! {            name: "",            target: "",            level: tracing_core::Level::TRACE,            fields: &[],            callsite: &NULL_CALLSITE,            kind: tracing_core::metadata::Kind::SPAN,        };        Self {            metadata: &NULL_METADATA,            parent: None,            ref_count: AtomicUsize::new(0),            extensions: RwLock::new(ExtensionsInner::new()),        }    }}impl Clear for DataInner {    /// Clears the span's data in place, dropping the parent's reference count.    fn clear(&mut self) {        // A span is not considered closed until all of its children have closed.        // Therefore, each span's `DataInner` holds a "reference" to the parent        // span, keeping the parent span open until all its children have closed.        // When we close a span, we must then decrement the parent's ref count        // (potentially, allowing it to close, if this child is the last reference        // to that span).        // We have to actually unpack the option inside the `get_default`        // closure, since it is a `FnMut`, but testing that there _is_ a value        // here lets us avoid the thread-local access if we don't need the        // dispatcher at all.        if self.parent.is_some() {            // Note that --- because `Layered::try_close` works by calling            // `try_close` on the inner subscriber and using the return value to            // determine whether to call the `Layer`'s `on_close` callback ---            // we must call `try_close` on the entire subscriber stack, rather            // than just on the registry. If the registry called `try_close` on            // itself directly, the layers wouldn't see the close notification.            let subscriber = dispatch::get_default(Dispatch::clone);            if let Some(parent) = self.parent.take() {                let _ = subscriber.try_close(parent);            }        }        // Clear (but do not deallocate!) the pooled `HashMap` for the span's extensions.        self.extensions            .get_mut()            .unwrap_or_else(|l| {                // This function can be called in a `Drop` impl, such as while                // panicking, so ignore lock poisoning.                l.into_inner()            })            .clear();    }}#[cfg(test)]mod tests {    use super::Registry;    use crate::{registry::LookupSpan, subscribe::Context, Subscribe};    use std::{        collections::HashMap,        sync::{Arc, Mutex, Weak},    };    use tracing::{self, collect::with_default};    use tracing_core::{        dispatch,        span::{Attributes, Id},        Collect,    };    struct AssertionSubscriber;    impl<C> Subscribe<C> for AssertionSubscriber    where        C: Collect + for<'a> LookupSpan<'a>,    {        fn on_close(&self, id: Id, ctx: Context<'_, C>) {            dbg!(format_args!("closing {:?}", id));            assert!(&ctx.span(&id).is_some());        }    }    #[test]    fn single_layer_can_access_closed_span() {        let subscriber = AssertionSubscriber.with_collector(Registry::default());        with_default(subscriber, || {            let span = tracing::debug_span!("span");            drop(span);        });    }    #[test]    fn multiple_layers_can_access_closed_span() {        let subscriber = AssertionSubscriber            .and_then(AssertionSubscriber)            .with_collector(Registry::default());        with_default(subscriber, || {            let span = tracing::debug_span!("span");            drop(span);        });    }    struct CloseLayer {        inner: Arc<Mutex<CloseState>>,    }    struct CloseHandle {        state: Arc<Mutex<CloseState>>,    }    #[derive(Default)]    struct CloseState {        open: HashMap<&'static str, Weak<()>>,        closed: Vec<(&'static str, Weak<()>)>,    }    struct SetRemoved(Arc<()>);    impl<S> Subscribe<S> for CloseLayer    where        S: Collect + for<'a> LookupSpan<'a>,    {        fn new_span(&self, _: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {            let span = ctx.span(id).expect("Missing span; this is a bug");            let mut lock = self.inner.lock().unwrap();            let is_removed = Arc::new(());            assert!(                lock.open                    .insert(span.name(), Arc::downgrade(&is_removed))                    .is_none(),                "test layer saw multiple spans with the same name, the test is probably messed up"            );            let mut extensions = span.extensions_mut();            extensions.insert(SetRemoved(is_removed));        }        fn on_close(&self, id: Id, ctx: Context<'_, S>) {            let span = if let Some(span) = ctx.span(&id) {                span            } else {                println!(                    "span {:?} did not exist in `on_close`, are we panicking?",                    id                );                return;            };            let name = span.name();            println!("close {} ({:?})", name, id);            if let Ok(mut lock) = self.inner.lock() {                if let Some(is_removed) = lock.open.remove(name) {                    assert!(is_removed.upgrade().is_some());                    lock.closed.push((name, is_removed));                }            }        }    }    impl CloseLayer {        fn new() -> (Self, CloseHandle) {            let state = Arc::new(Mutex::new(CloseState::default()));            (                Self {                    inner: state.clone(),                },                CloseHandle { state },            )        }    }    impl CloseState {        fn is_open(&self, span: &str) -> bool {            self.open.contains_key(span)        }        fn is_closed(&self, span: &str) -> bool {            self.closed.iter().any(|(name, _)| name == &span)        }    }    impl CloseHandle {        fn assert_closed(&self, span: &str) {            let lock = self.state.lock().unwrap();            assert!(                lock.is_closed(span),                "expected {} to be closed{}",                span,                if lock.is_open(span) {                    " (it was still open)"                } else {                    ", but it never existed (is there a problem with the test?)"                }            )        }        fn assert_open(&self, span: &str) {            let lock = self.state.lock().unwrap();            assert!(                lock.is_open(span),                "expected {} to be open{}",                span,                if lock.is_closed(span) {                    " (it was still open)"                } else {                    ", but it never existed (is there a problem with the test?)"                }            )        }        fn assert_removed(&self, span: &str) {            let lock = self.state.lock().unwrap();            let is_removed = match lock.closed.iter().find(|(name, _)| name == &span) {                Some((_, is_removed)) => is_removed,                None => panic!(                    "expected {} to be removed from the registry, but it was not closed {}",                    span,                    if lock.is_closed(span) {                        " (it was still open)"                    } else {                        ", but it never existed (is there a problem with the test?)"                    }                ),            };            assert!(                is_removed.upgrade().is_none(),                "expected {} to have been removed from the registry",                span            )        }        fn assert_not_removed(&self, span: &str) {            let lock = self.state.lock().unwrap();            let is_removed = match lock.closed.iter().find(|(name, _)| name == &span) {                Some((_, is_removed)) => is_removed,                None if lock.is_open(span) => return,                None => unreachable!(),            };            assert!(                is_removed.upgrade().is_some(),                "expected {} to have been removed from the registry",                span            )        }        #[allow(unused)] // may want this for future tests        fn assert_last_closed(&self, span: Option<&str>) {            let lock = self.state.lock().unwrap();            let last = lock.closed.last().map(|(span, _)| span);            assert_eq!(                last,                span.as_ref(),                "expected {:?} to have closed last",                span            );        }        fn assert_closed_in_order(&self, order: impl AsRef<[&'static str]>) {            let lock = self.state.lock().unwrap();            let order = order.as_ref();            for (i, name) in order.iter().enumerate() {                assert_eq!(                    lock.closed.get(i).map(|(span, _)| span),                    Some(name),                    "expected close order: {:?}, actual: {:?}",                    order,                    lock.closed.iter().map(|(name, _)| name).collect::<Vec<_>>()                );            }        }    }    #[test]    fn spans_are_removed_from_registry() {        let (close_layer, state) = CloseLayer::new();        let subscriber = AssertionSubscriber            .and_then(close_layer)            .with_collector(Registry::default());        // Create a `Dispatch` (which is internally reference counted) so that        // the subscriber lives to the end of the test. Otherwise, if we just        // passed the subscriber itself to `with_default`, we could see the span        // be dropped when the subscriber itself is dropped, destroying the        // registry.        let dispatch = dispatch::Dispatch::new(subscriber);        dispatch::with_default(&dispatch, || {            let span = tracing::debug_span!("span1");            drop(span);            let span = tracing::info_span!("span2");            drop(span);        });        state.assert_removed("span1");        state.assert_removed("span2");        // Ensure the registry itself outlives the span.        drop(dispatch);    }    #[test]    fn spans_are_only_closed_when_the_last_ref_drops() {        let (close_layer, state) = CloseLayer::new();        let subscriber = AssertionSubscriber            .and_then(close_layer)            .with_collector(Registry::default());        // Create a `Dispatch` (which is internally reference counted) so that        // the subscriber lives to the end of the test. Otherwise, if we just        // passed the subscriber itself to `with_default`, we could see the span        // be dropped when the subscriber itself is dropped, destroying the        // registry.        let dispatch = dispatch::Dispatch::new(subscriber);        let span2 = dispatch::with_default(&dispatch, || {            let span = tracing::debug_span!("span1");            drop(span);            let span2 = tracing::info_span!("span2");            let span2_clone = span2.clone();            drop(span2);            span2_clone        });        state.assert_removed("span1");        state.assert_not_removed("span2");        drop(span2);        state.assert_removed("span1");        // Ensure the registry itself outlives the span.        drop(dispatch);    }    #[test]    fn span_enter_guards_are_dropped_out_of_order() {        let (close_layer, state) = CloseLayer::new();        let subscriber = AssertionSubscriber            .and_then(close_layer)            .with_collector(Registry::default());        // Create a `Dispatch` (which is internally reference counted) so that        // the subscriber lives to the end of the test. Otherwise, if we just        // passed the subscriber itself to `with_default`, we could see the span        // be dropped when the subscriber itself is dropped, destroying the        // registry.        let dispatch = dispatch::Dispatch::new(subscriber);        dispatch::with_default(&dispatch, || {            let span1 = tracing::debug_span!("span1");            let span2 = tracing::info_span!("span2");            let enter1 = span1.enter();            let enter2 = span2.enter();            drop(enter1);            drop(span1);            state.assert_removed("span1");            state.assert_not_removed("span2");            drop(enter2);            state.assert_not_removed("span2");            drop(span2);            state.assert_removed("span1");            state.assert_removed("span2");        });    }    #[test]    fn child_closes_parent() {        // This test asserts that if a parent span's handle is dropped before        // a child span's handle, the parent will remain open until child        // closes, and will then be closed.        let (close_layer, state) = CloseLayer::new();        let subscriber = close_layer.with_collector(Registry::default());        let dispatch = dispatch::Dispatch::new(subscriber);        dispatch::with_default(&dispatch, || {            let span1 = tracing::info_span!("parent");            let span2 = tracing::info_span!(parent: &span1, "child");            state.assert_open("parent");            state.assert_open("child");            drop(span1);            state.assert_open("parent");            state.assert_open("child");            drop(span2);            state.assert_closed("parent");            state.assert_closed("child");        });    }    #[test]    fn child_closes_grandparent() {        // This test asserts that, when a span is kept open by a child which        // is *itself* kept open by a child, closing the grandchild will close        // both the parent *and* the grandparent.        let (close_layer, state) = CloseLayer::new();        let subscriber = close_layer.with_collector(Registry::default());        let dispatch = dispatch::Dispatch::new(subscriber);        dispatch::with_default(&dispatch, || {            let span1 = tracing::info_span!("grandparent");            let span2 = tracing::info_span!(parent: &span1, "parent");            let span3 = tracing::info_span!(parent: &span2, "child");            state.assert_open("grandparent");            state.assert_open("parent");            state.assert_open("child");            drop(span1);            drop(span2);            state.assert_open("grandparent");            state.assert_open("parent");            state.assert_open("child");            drop(span3);            state.assert_closed_in_order(&["child", "parent", "grandparent"]);        });    }}

Missing links again :(

This just happened after the changes for #13. - is before the change, + is after.

-//! [`Layer` type]: Layer
-//! [`Layer` trait]: super::layer::Layer
+//! [`Layer` type]: struct.Layer.html
+//! [`Layer` trait]: ../layer/trait.Layer.html

Support for docs.rs links

I'd like to turn this link:

/// [`Span`]: https://docs.rs/tracing-core/latest/tracing_core/span/index.html

into this one:

/// [`Span`]: tracing_code::span

I think this should be fairly easy to add: if the URL starts with https://docs.rs, strip the host and first 2 path segments, then treat it like a normal link.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.