Skip to content

Automatically generate rustdocs links to references in markdown#74

Merged
m4tx merged 16 commits intocot-rs:masterfrom
ElijahAhianyo:elijah/link-gen
Mar 1, 2026
Merged

Automatically generate rustdocs links to references in markdown#74
m4tx merged 16 commits intocot-rs:masterfrom
ElijahAhianyo:elijah/link-gen

Conversation

@ElijahAhianyo
Copy link
Contributor

@ElijahAhianyo ElijahAhianyo commented Feb 24, 2026

Add logic to resolve references into valid rustdoc links

@ElijahAhianyo ElijahAhianyo marked this pull request as ready for review February 26, 2026 03:05
match parts.len() {
1 => format!("{}.0.0", parts[0]),
2 => format!("{}.{}.0", parts[0], parts[1]),
_ => s.to_string(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't SemVer err if there are more than 4 parts? If so, I'd throw an error in that case and add tests coverage

Copy link
Contributor Author

@ElijahAhianyo ElijahAhianyo Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimum number of parts that Semver crate supports is 3. This is only a best effort to canonicalize any format less than 3 to have the standard major, minor, and patch. For cases > than 3, the idea is to route them to the SemVer::Version::parse constructor, which should validate them and throw an error when necessary

}
}

fn canonicalize_version_string(s: &str) -> String {
Copy link
Member

@m4tx m4tx Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure if this is the way we want to handle this. Suppose a user sees 0.5 version of the framework, but the latest version is 0.5.31. The URL generated will point specifically to 0.5.0, possibly omitting the changes made in the patch versions.

docs.rs is capable of redirecting to the latest patch version by itself, maybe we should use that instead? For instance, see that https://docs.rs/cot/0.3/cot/ redirects to 0.3.1, not 0.3.0.

I'm not even sure we need to parse the version. The content changes rarely and it's easy to spot the version might be wrong. Therefore, I think keeping it as String (wrapped in the Version newtype) would be enough, but I'll leave it to you to decide.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably must have missed something initially while playing around the rustdocs link structure using the semver versioning which didnt work initially and influenced this function. But, you're right. I tried this again and it works. Which means we dont need this at all

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I just remembered another reason for this function. The semver crate requires at least a three-component version (major, minor, patch) to construct a valid smever::Version type. However, throughout the codebase, versions are typically specified with only the major and minor components.
To accommodate this, we canonicalize to the “floor” (i.e., append a patch component, typically 0) as a best-effort strategy to produce a valid semver::Version when the patch component is not explicitly provided.
I think it makes sense to retain this behavior, and then when generating the Rustdoc link, we can use only the major and minor components. Rustdoc should then resolve the appropriate route to the latest compatible patch version, as you pointed out.

Comment on lines +33 to +64
/// Returns the major version number.
///
/// # Example
/// ```
/// let v = Version::new(0, 5, 0);
/// assert_eq!(v.major(), 0);
/// ```
pub fn major(&self) -> u64 {
self.0.major
}

/// Returns the minor version number.
///
/// # Example
/// ```
/// let v = Version::new(0, 5, 0);
/// assert_eq!(v.minor(), 5);
/// ```
pub fn minor(&self) -> u64 {
self.0.minor
}

/// Returns the patch version number.
///
/// # Example
/// ```
/// let v = Version::new(0, 5, 0);
/// assert_eq!(v.patch(), 0);
/// ```
pub fn patch(&self) -> u64 {
self.0.patch
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use these methods anywhere? If not, there's no point in keeping dead code on the repo.

@m4tx
Copy link
Member

m4tx commented Feb 26, 2026

A long-awaited and very useful change, thanks! I think it's a little bit overengineered, though - please have a look at my comments.

@m4tx m4tx closed this Feb 26, 2026
@m4tx m4tx reopened this Feb 26, 2026
@m4tx
Copy link
Member

m4tx commented Feb 26, 2026

oops, didn't mean to close, sorry.

@ElijahAhianyo ElijahAhianyo requested review from m4tx and seqre March 1, 2026 02:01
Comment on lines +116 to +119
segs.iter()
.skip(1)
.take(segs.len() - 2)
.map(|s| s.to_string()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment explaining why are we taking 1..n-2 segments, as it's not immediately obvious when looking at the code.

Copy link
Member

@m4tx m4tx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just address the last final comment!

@m4tx m4tx merged commit c6d4c9a into cot-rs:master Mar 1, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants