Skip to content

Commit ee379a1

Browse files
committed
feat(website): added shorthand for docs.rs links
1 parent b083173 commit ee379a1

File tree

5 files changed

+158
-57
lines changed

5 files changed

+158
-57
lines changed

docs/manifest.json

+19-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
{
2-
"stable": "0.3.4",
3-
"outdated": ["0.3.0-0.3.3", "0.2.x", "0.1.x"],
4-
"beta": ["0.4.x"],
5-
"history_map": {
6-
"0.1.x": "v0.1.4",
7-
"0.2.x": "v0.2.3",
8-
"0.3.0-0.3.3": "v0.3.3",
9-
"0.3.4": "eccf137032fbe8e6507be9e9317edc16e7576a4f",
10-
"0.4.x": "HEAD"
2+
"0.1.x": {
3+
"state": "outdated",
4+
"git": "v0.1.4"
5+
},
6+
"0.2.x": {
7+
"state": "outdated",
8+
"git": "v0.2.3"
9+
},
10+
"0.3.0-0.3.3": {
11+
"state": "outdated",
12+
"git": "v0.3.3"
13+
},
14+
"0.3.4": {
15+
"state": "stable",
16+
"git": "eccf137032fbe8e6507be9e9317edc16e7576a4f"
17+
},
18+
"0.4.x": {
19+
"state": "beta",
20+
"git": "HEAD"
1121
}
1222
}

docs/next/en-US/getting-started/first-app.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ We also give that macro an argument, `perseus_integration::dflt_server`. You sho
4848

4949
As you might have inferred, the argument we provide to the `#[perseus::main()]` macro is the function it will use to create a server for our app! You can provide something like `dflt_server` here if you don't want to think about that much more, or you can define an expansive API and use that here instead! (Note that there's actually a much better way to do this, which is addressed much later on.)
5050

51-
This function also takes a *generic*, or *type parameter*, called `G`. We use this to return a `PerseusApp` (which is the construct that contains all the information about our app) that uses `G`. This is essentially saying that we want to return a `PerseusApp` for something that implements the `Html` trait, which we imported earlier. This is Sycamore's way of expressing that this function can either return something designed for the browser, or for the engine. Specifically, the engine uses `SsrNode` (server-side rendering), and the browser uses `DomNode`/`HydrateNode`. Don't worry though, you don't need to understand these distinctions just yet.
51+
This function also takes a *generic*, or *type parameter*, called `G`. We use this to return a [`PerseusApp`](=type.PerseusApp@perseus) (which is the construct that contains all the information about our app) that uses `G`. This is essentially saying that we want to return a `PerseusApp` for something that implements the `Html` trait, which we imported earlier. This is Sycamore's way of expressing that this function can either return something designed for the browser, or for the engine. Specifically, the engine uses `SsrNode` (server-side rendering), and the browser uses `DomNode`/`HydrateNode`. Don't worry though, you don't need to understand these distinctions just yet.
5252

5353
The body of the function is where the magic happens: we define a new `PerseusApp` with our one template and some error pages. The template is called `index`, which is a special name that means it will be hosted at the index of our site --- it will be the landing page. The code for that template is a `view! { .. }`, which comes from Sycamore, and it's how we write things that the user can see. If you've used HTML before, this is the Rust version of that. It might look a bit daunting at first, but most people tend to warm to it fairly well after using it a little.
5454

website/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ wasm-bindgen = "0.2"
2626
tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] }
2727
walkdir = "2"
2828
pulldown-cmark = "0.8"
29+
regex = "1"
2930

3031
[target.'cfg(target_arch = "wasm32")'.dependencies]
3132
wee_alloc = "0.4"

website/src/templates/docs/container.rs

+38-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::components::container::NavLinks;
22
use crate::components::container::COPYRIGHT_YEARS;
3-
use crate::templates::docs::generation::{DocsManifest, DocsVersionStatus};
3+
use crate::templates::docs::generation::{
4+
get_beta_versions, get_outdated_versions, get_stable_version, DocsManifest, DocsVersionStatus,
5+
};
46
use perseus::internal::i18n::Translator;
57
use perseus::{link, navigate};
68
use sycamore::prelude::*;
@@ -35,28 +37,42 @@ fn DocsVersionSwitcher<G: Html>(cx: Scope, props: DocsVersionSwitcherProps) -> V
3537
let locale = create_signal(cx, String::new());
3638

3739
let current_version = create_ref(cx, props.current_version.to_string());
38-
let stable_version = create_ref(cx, props.manifest.stable.to_string());
40+
let stable_version = create_ref(cx, get_stable_version(&props.manifest).0);
3941

40-
let beta_versions = View::new_fragment(
41-
props.manifest.beta.into_iter().map(|version| {
42-
let version = create_ref(cx, version);
43-
view! { cx,
44-
option(value = &version, selected = current_version == version) { (t!("docs-version-switcher.beta", {
45-
"version" = version.to_string()
46-
}, cx)) }
47-
}
48-
}).collect()
49-
);
50-
let old_versions = View::new_fragment(
51-
props.manifest.outdated.into_iter().map(|version| {
52-
let version = create_ref(cx, version);
53-
view! { cx,
54-
option(value = version, selected = current_version == version) { (t!("docs-version-switcher.outdated", {
55-
"version" = version.to_string()
56-
}, cx)) }
57-
}
58-
}).collect()
59-
);
42+
let beta_versions = View::new_fragment({
43+
let mut versions = get_beta_versions(&props.manifest)
44+
.into_keys()
45+
.collect::<Vec<String>>();
46+
versions.sort_by(|a, b| b.partial_cmp(a).unwrap());
47+
versions
48+
.into_iter()
49+
.map(|version| {
50+
let version = create_ref(cx, version);
51+
view! { cx,
52+
option(value = &version, selected = current_version == version) { (t!("docs-version-switcher.beta", {
53+
"version" = version.to_string()
54+
}, cx)) }
55+
}
56+
})
57+
.collect()
58+
});
59+
let old_versions = View::new_fragment({
60+
let mut versions = get_outdated_versions(&props.manifest)
61+
.into_keys()
62+
.collect::<Vec<String>>();
63+
versions.sort_by(|a, b| b.partial_cmp(a).unwrap());
64+
versions
65+
.into_iter()
66+
.map(|version| {
67+
let version = create_ref(cx, version);
68+
view! { cx,
69+
option(value = version, selected = current_version == version) { (t!("docs-version-switcher.outdated", {
70+
"version" = version.to_string()
71+
}, cx)) }
72+
}
73+
})
74+
.collect()
75+
});
6076

6177
view! { cx,
6278
({

website/src/templates/docs/generation.rs

+99-25
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ pub fn parse_md_to_html(markdown: &str) -> String {
2525
html_contents
2626
}
2727

28-
// By using a lazy static, we won't read from the filesystem in client-side code
28+
// By using a lazy static, we won't read from the filesystem in client-side code (because these variables are never requested on the client-side)
2929
lazy_static! {
30-
/// The latest version of the documentation. This will need to be updated as the docs are from the `docs/stable.txt` file.
30+
/// The current documentation manifest, which contains details on all versions of Perseus.
3131
static ref DOCS_MANIFEST: DocsManifest = {
3232
let contents = fs::read_to_string("../docs/manifest.json").unwrap();
3333
serde_json::from_str(&contents).unwrap()
3434
};
35+
static ref STABLE_VERSION_NAME: String = get_stable_version(&DOCS_MANIFEST).0;
36+
static ref OUTDATED_VERSIONS: HashMap<String, VersionManifest> = get_outdated_versions(&DOCS_MANIFEST);
37+
static ref BETA_VERSIONS: HashMap<String, VersionManifest> = get_beta_versions(&DOCS_MANIFEST);
3538
}
3639

3740
/// The stability of a version of the docs, which governs what kind of warning will be displayed.
@@ -98,13 +101,70 @@ impl DocsVersionStatus {
98101
}
99102
}
100103
/// Information about the current state of the documentation, including which versions are outdated and the like.
104+
pub type DocsManifest = HashMap<String, VersionManifest>;
105+
// pub struct DocsManifest {
106+
// pub stable: String,
107+
// pub outdated: Vec<String>,
108+
// pub beta: Vec<String>,
109+
// /// A map of versions to points in the Git version history.
110+
// pub history_map: HashMap<String, String>,
111+
// }
112+
113+
/// Information about a single version in the documentation manifest.
101114
#[derive(Serialize, Deserialize, Clone)]
102-
pub struct DocsManifest {
103-
pub stable: String,
104-
pub outdated: Vec<String>,
105-
pub beta: Vec<String>,
106-
/// A map of versions to points in the Git version history.
107-
pub history_map: HashMap<String, String>,
115+
pub struct VersionManifest {
116+
pub state: VersionState,
117+
pub git: String,
118+
}
119+
/// The possible states a version can be in. Note that there can only be one stable version at a time, and that the special `next`
120+
/// version is not accounted for here.
121+
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
122+
#[serde(rename_all = "snake_case")]
123+
pub enum VersionState {
124+
/// The version is outdated, and should no longer be used if possible.
125+
Outdated,
126+
/// The version is currently stable.
127+
Stable,
128+
/// The version is currently released, but in a beta form.
129+
Beta,
130+
}
131+
132+
/// Gets the latest stable version of the docs from the given manifest. This returns the version's name and metadata.
133+
pub fn get_stable_version(manifest: &DocsManifest) -> (String, VersionManifest) {
134+
let mut stable: Option<(String, VersionManifest)> = None;
135+
for (name, details) in manifest.iter() {
136+
if details.state == VersionState::Stable {
137+
stable = Some((name.clone(), details.clone()));
138+
break;
139+
}
140+
}
141+
if let Some(stable) = stable {
142+
stable
143+
} else {
144+
panic!("no stable version set for docs");
145+
}
146+
}
147+
/// Gets the outdated versions of the docs from the given manifest. This returns a `HashMap` of their names to their manifests.
148+
pub fn get_outdated_versions(manifest: &DocsManifest) -> HashMap<String, VersionManifest> {
149+
let mut versions = HashMap::new();
150+
for (name, details) in manifest.iter() {
151+
if details.state == VersionState::Outdated {
152+
versions.insert(name.clone(), details.clone());
153+
}
154+
}
155+
156+
versions
157+
}
158+
/// Gets the beta versions of the docs from the given manifest. This returns a `HashMap` of their names to their manifests.
159+
pub fn get_beta_versions(manifest: &DocsManifest) -> HashMap<String, VersionManifest> {
160+
let mut versions = HashMap::new();
161+
for (name, details) in manifest.iter() {
162+
if details.state == VersionState::Beta {
163+
versions.insert(name.clone(), details.clone());
164+
}
165+
}
166+
167+
versions
108168
}
109169

110170
#[perseus::build_state]
@@ -113,6 +173,7 @@ pub async fn get_build_state(
113173
locale: String,
114174
) -> RenderFnResultWithCause<DocsPageProps> {
115175
use perseus::internal::get_path_prefix_server;
176+
use regex::Regex;
116177

117178
let path_vec: Vec<&str> = path.split('/').collect();
118179
// Localize the path again to what it'll be on the filesystem
@@ -122,11 +183,11 @@ pub async fn get_build_state(
122183
// If the path is just `/docs` though, we'll render the introduction page for the stable version
123184
let (version, fs_path): (&str, String) = if path == "docs" {
124185
(
125-
&DOCS_MANIFEST.stable,
186+
STABLE_VERSION_NAME.as_str(),
126187
format!(
127188
"{}/{}/{}/{}",
128189
path_vec[0], // `docs`
129-
&DOCS_MANIFEST.stable,
190+
STABLE_VERSION_NAME.as_str(),
130191
&locale,
131192
"intro"
132193
),
@@ -145,12 +206,12 @@ pub async fn get_build_state(
145206
)
146207
} else {
147208
(
148-
&DOCS_MANIFEST.stable,
209+
STABLE_VERSION_NAME.as_str(),
149210
// If it doesn't have a version, we'll inject the latest stable one
150211
format!(
151212
"{}/{}/{}/{}",
152213
path_vec[0], // `docs`
153-
&DOCS_MANIFEST.stable,
214+
STABLE_VERSION_NAME.as_str(),
154215
&locale,
155216
path_vec[1..].join("/") // The rest of the path
156217
),
@@ -192,10 +253,10 @@ pub async fn get_build_state(
192253
}
193254
} else {
194255
// Get the corresponding history point for this version
195-
let history_point = DOCS_MANIFEST.history_map.get(version);
196-
let history_point = match history_point {
197-
Some(history_point) => history_point,
198-
None => panic!("docs version '{}' not present in history map", version),
256+
let version_manifest = DOCS_MANIFEST.get(version);
257+
let history_point = match version_manifest {
258+
Some(version_manifest) => &version_manifest.git,
259+
None => panic!("docs version '{}' not present in manifest", version),
199260
};
200261
// We want the path relative to the root of the project directory (where the Git repo is)
201262
get_file_at_version(incl_path, history_point, PathBuf::from("../"))?
@@ -233,10 +294,10 @@ pub async fn get_build_state(
233294
}
234295
} else {
235296
// Get the corresponding history point for this version
236-
let history_point = DOCS_MANIFEST.history_map.get(version);
237-
let history_point = match history_point {
238-
Some(history_point) => history_point,
239-
None => panic!("docs version '{}' not present in history map", version),
297+
let version_manifest = DOCS_MANIFEST.get(version);
298+
let history_point = match version_manifest {
299+
Some(version_manifest) => &version_manifest.git,
300+
None => panic!("docs version '{}' not present in manifest", version),
240301
};
241302
// We want the path relative to the root of the project directory (where the Git repo is)
242303
get_file_at_version(incl_path, history_point, PathBuf::from("../"))?
@@ -281,6 +342,19 @@ pub async fn get_build_state(
281342
),
282343
);
283344

345+
// Parse any links to docs.rs (of the form `[`Error`](=enum.Error@perseus)`, where `perseus` is the package name)
346+
// Versions are interpolated automatically
347+
let docs_rs_version = "latest";
348+
let contents = Regex::new(r#"\]\(=(?P<path>.*?)@(?P<pkg>.*?)\)"#)
349+
.unwrap()
350+
.replace_all(
351+
&contents,
352+
format!(
353+
"](https://docs.rs/${{pkg}}/{}/${{pkg}}/${{path}}.html)",
354+
docs_rs_version
355+
),
356+
);
357+
284358
// Parse the file to HTML
285359
let html_contents = parse_md_to_html(&contents);
286360
// Get the title from the first line of the contents, stripping the initial `#`
@@ -303,11 +377,11 @@ pub async fn get_build_state(
303377
// Work out the status of this page
304378
let status = if version == "next" {
305379
DocsVersionStatus::Next
306-
} else if DOCS_MANIFEST.outdated.iter().any(|v| v == version) {
380+
} else if OUTDATED_VERSIONS.keys().any(|v| v == version) {
307381
DocsVersionStatus::Outdated
308-
} else if DOCS_MANIFEST.beta.iter().any(|v| v == version) {
382+
} else if BETA_VERSIONS.keys().any(|v| v == version) {
309383
DocsVersionStatus::Beta
310-
} else if DOCS_MANIFEST.stable == version {
384+
} else if STABLE_VERSION_NAME.as_str() == version {
311385
DocsVersionStatus::Stable
312386
} else {
313387
panic!("version '{}' isn't listed in the docs manifest", version)
@@ -355,9 +429,9 @@ pub async fn get_build_paths() -> RenderFnResult<Vec<String>> {
355429
paths.push(path_str.clone());
356430
// If it's for the latest stable version though, we should also render it without that prefix
357431
// That way the latest stable verison is always at the docs without a version prefix (which I think is more sensible than having the unreleased version there)
358-
if path_str.starts_with(&DOCS_MANIFEST.stable) {
432+
if path_str.starts_with(STABLE_VERSION_NAME.as_str()) {
359433
let unprefixed_path_str = path_str
360-
.strip_prefix(&format!("{}/", &DOCS_MANIFEST.stable))
434+
.strip_prefix(&format!("{}/", STABLE_VERSION_NAME.as_str()))
361435
.unwrap();
362436
paths.push(unprefixed_path_str.to_string());
363437
}

0 commit comments

Comments
 (0)