Skip to content

Commit

Permalink
feat(website): added shorthand for docs.rs links
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Jul 2, 2022
1 parent b083173 commit ee379a1
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 57 deletions.
28 changes: 19 additions & 9 deletions docs/manifest.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
{
"stable": "0.3.4",
"outdated": ["0.3.0-0.3.3", "0.2.x", "0.1.x"],
"beta": ["0.4.x"],
"history_map": {
"0.1.x": "v0.1.4",
"0.2.x": "v0.2.3",
"0.3.0-0.3.3": "v0.3.3",
"0.3.4": "eccf137032fbe8e6507be9e9317edc16e7576a4f",
"0.4.x": "HEAD"
"0.1.x": {
"state": "outdated",
"git": "v0.1.4"
},
"0.2.x": {
"state": "outdated",
"git": "v0.2.3"
},
"0.3.0-0.3.3": {
"state": "outdated",
"git": "v0.3.3"
},
"0.3.4": {
"state": "stable",
"git": "eccf137032fbe8e6507be9e9317edc16e7576a4f"
},
"0.4.x": {
"state": "beta",
"git": "HEAD"
}
}
2 changes: 1 addition & 1 deletion docs/next/en-US/getting-started/first-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ We also give that macro an argument, `perseus_integration::dflt_server`. You sho

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.)

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.
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.

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.

Expand Down
1 change: 1 addition & 0 deletions website/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ wasm-bindgen = "0.2"
tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] }
walkdir = "2"
pulldown-cmark = "0.8"
regex = "1"

[target.'cfg(target_arch = "wasm32")'.dependencies]
wee_alloc = "0.4"
Expand Down
60 changes: 38 additions & 22 deletions website/src/templates/docs/container.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::components::container::NavLinks;
use crate::components::container::COPYRIGHT_YEARS;
use crate::templates::docs::generation::{DocsManifest, DocsVersionStatus};
use crate::templates::docs::generation::{
get_beta_versions, get_outdated_versions, get_stable_version, DocsManifest, DocsVersionStatus,
};
use perseus::internal::i18n::Translator;
use perseus::{link, navigate};
use sycamore::prelude::*;
Expand Down Expand Up @@ -35,28 +37,42 @@ fn DocsVersionSwitcher<G: Html>(cx: Scope, props: DocsVersionSwitcherProps) -> V
let locale = create_signal(cx, String::new());

let current_version = create_ref(cx, props.current_version.to_string());
let stable_version = create_ref(cx, props.manifest.stable.to_string());
let stable_version = create_ref(cx, get_stable_version(&props.manifest).0);

let beta_versions = View::new_fragment(
props.manifest.beta.into_iter().map(|version| {
let version = create_ref(cx, version);
view! { cx,
option(value = &version, selected = current_version == version) { (t!("docs-version-switcher.beta", {
"version" = version.to_string()
}, cx)) }
}
}).collect()
);
let old_versions = View::new_fragment(
props.manifest.outdated.into_iter().map(|version| {
let version = create_ref(cx, version);
view! { cx,
option(value = version, selected = current_version == version) { (t!("docs-version-switcher.outdated", {
"version" = version.to_string()
}, cx)) }
}
}).collect()
);
let beta_versions = View::new_fragment({
let mut versions = get_beta_versions(&props.manifest)
.into_keys()
.collect::<Vec<String>>();
versions.sort_by(|a, b| b.partial_cmp(a).unwrap());
versions
.into_iter()
.map(|version| {
let version = create_ref(cx, version);
view! { cx,
option(value = &version, selected = current_version == version) { (t!("docs-version-switcher.beta", {
"version" = version.to_string()
}, cx)) }
}
})
.collect()
});
let old_versions = View::new_fragment({
let mut versions = get_outdated_versions(&props.manifest)
.into_keys()
.collect::<Vec<String>>();
versions.sort_by(|a, b| b.partial_cmp(a).unwrap());
versions
.into_iter()
.map(|version| {
let version = create_ref(cx, version);
view! { cx,
option(value = version, selected = current_version == version) { (t!("docs-version-switcher.outdated", {
"version" = version.to_string()
}, cx)) }
}
})
.collect()
});

view! { cx,
({
Expand Down
124 changes: 99 additions & 25 deletions website/src/templates/docs/generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ pub fn parse_md_to_html(markdown: &str) -> String {
html_contents
}

// By using a lazy static, we won't read from the filesystem in client-side code
// 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)
lazy_static! {
/// The latest version of the documentation. This will need to be updated as the docs are from the `docs/stable.txt` file.
/// The current documentation manifest, which contains details on all versions of Perseus.
static ref DOCS_MANIFEST: DocsManifest = {
let contents = fs::read_to_string("../docs/manifest.json").unwrap();
serde_json::from_str(&contents).unwrap()
};
static ref STABLE_VERSION_NAME: String = get_stable_version(&DOCS_MANIFEST).0;
static ref OUTDATED_VERSIONS: HashMap<String, VersionManifest> = get_outdated_versions(&DOCS_MANIFEST);
static ref BETA_VERSIONS: HashMap<String, VersionManifest> = get_beta_versions(&DOCS_MANIFEST);
}

/// The stability of a version of the docs, which governs what kind of warning will be displayed.
Expand Down Expand Up @@ -98,13 +101,70 @@ impl DocsVersionStatus {
}
}
/// Information about the current state of the documentation, including which versions are outdated and the like.
pub type DocsManifest = HashMap<String, VersionManifest>;
// pub struct DocsManifest {
// pub stable: String,
// pub outdated: Vec<String>,
// pub beta: Vec<String>,
// /// A map of versions to points in the Git version history.
// pub history_map: HashMap<String, String>,
// }

/// Information about a single version in the documentation manifest.
#[derive(Serialize, Deserialize, Clone)]
pub struct DocsManifest {
pub stable: String,
pub outdated: Vec<String>,
pub beta: Vec<String>,
/// A map of versions to points in the Git version history.
pub history_map: HashMap<String, String>,
pub struct VersionManifest {
pub state: VersionState,
pub git: String,
}
/// 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`
/// version is not accounted for here.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum VersionState {
/// The version is outdated, and should no longer be used if possible.
Outdated,
/// The version is currently stable.
Stable,
/// The version is currently released, but in a beta form.
Beta,
}

/// Gets the latest stable version of the docs from the given manifest. This returns the version's name and metadata.
pub fn get_stable_version(manifest: &DocsManifest) -> (String, VersionManifest) {
let mut stable: Option<(String, VersionManifest)> = None;
for (name, details) in manifest.iter() {
if details.state == VersionState::Stable {
stable = Some((name.clone(), details.clone()));
break;
}
}
if let Some(stable) = stable {
stable
} else {
panic!("no stable version set for docs");
}
}
/// Gets the outdated versions of the docs from the given manifest. This returns a `HashMap` of their names to their manifests.
pub fn get_outdated_versions(manifest: &DocsManifest) -> HashMap<String, VersionManifest> {
let mut versions = HashMap::new();
for (name, details) in manifest.iter() {
if details.state == VersionState::Outdated {
versions.insert(name.clone(), details.clone());
}
}

versions
}
/// Gets the beta versions of the docs from the given manifest. This returns a `HashMap` of their names to their manifests.
pub fn get_beta_versions(manifest: &DocsManifest) -> HashMap<String, VersionManifest> {
let mut versions = HashMap::new();
for (name, details) in manifest.iter() {
if details.state == VersionState::Beta {
versions.insert(name.clone(), details.clone());
}
}

versions
}

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

let path_vec: Vec<&str> = path.split('/').collect();
// Localize the path again to what it'll be on the filesystem
Expand All @@ -122,11 +183,11 @@ pub async fn get_build_state(
// If the path is just `/docs` though, we'll render the introduction page for the stable version
let (version, fs_path): (&str, String) = if path == "docs" {
(
&DOCS_MANIFEST.stable,
STABLE_VERSION_NAME.as_str(),
format!(
"{}/{}/{}/{}",
path_vec[0], // `docs`
&DOCS_MANIFEST.stable,
STABLE_VERSION_NAME.as_str(),
&locale,
"intro"
),
Expand All @@ -145,12 +206,12 @@ pub async fn get_build_state(
)
} else {
(
&DOCS_MANIFEST.stable,
STABLE_VERSION_NAME.as_str(),
// If it doesn't have a version, we'll inject the latest stable one
format!(
"{}/{}/{}/{}",
path_vec[0], // `docs`
&DOCS_MANIFEST.stable,
STABLE_VERSION_NAME.as_str(),
&locale,
path_vec[1..].join("/") // The rest of the path
),
Expand Down Expand Up @@ -192,10 +253,10 @@ pub async fn get_build_state(
}
} else {
// Get the corresponding history point for this version
let history_point = DOCS_MANIFEST.history_map.get(version);
let history_point = match history_point {
Some(history_point) => history_point,
None => panic!("docs version '{}' not present in history map", version),
let version_manifest = DOCS_MANIFEST.get(version);
let history_point = match version_manifest {
Some(version_manifest) => &version_manifest.git,
None => panic!("docs version '{}' not present in manifest", version),
};
// We want the path relative to the root of the project directory (where the Git repo is)
get_file_at_version(incl_path, history_point, PathBuf::from("../"))?
Expand Down Expand Up @@ -233,10 +294,10 @@ pub async fn get_build_state(
}
} else {
// Get the corresponding history point for this version
let history_point = DOCS_MANIFEST.history_map.get(version);
let history_point = match history_point {
Some(history_point) => history_point,
None => panic!("docs version '{}' not present in history map", version),
let version_manifest = DOCS_MANIFEST.get(version);
let history_point = match version_manifest {
Some(version_manifest) => &version_manifest.git,
None => panic!("docs version '{}' not present in manifest", version),
};
// We want the path relative to the root of the project directory (where the Git repo is)
get_file_at_version(incl_path, history_point, PathBuf::from("../"))?
Expand Down Expand Up @@ -281,6 +342,19 @@ pub async fn get_build_state(
),
);

// Parse any links to docs.rs (of the form `[`Error`](=enum.Error@perseus)`, where `perseus` is the package name)
// Versions are interpolated automatically
let docs_rs_version = "latest";
let contents = Regex::new(r#"\]\(=(?P<path>.*?)@(?P<pkg>.*?)\)"#)
.unwrap()
.replace_all(
&contents,
format!(
"](https://docs.rs/${{pkg}}/{}/${{pkg}}/${{path}}.html)",
docs_rs_version
),
);

// Parse the file to HTML
let html_contents = parse_md_to_html(&contents);
// Get the title from the first line of the contents, stripping the initial `#`
Expand All @@ -303,11 +377,11 @@ pub async fn get_build_state(
// Work out the status of this page
let status = if version == "next" {
DocsVersionStatus::Next
} else if DOCS_MANIFEST.outdated.iter().any(|v| v == version) {
} else if OUTDATED_VERSIONS.keys().any(|v| v == version) {
DocsVersionStatus::Outdated
} else if DOCS_MANIFEST.beta.iter().any(|v| v == version) {
} else if BETA_VERSIONS.keys().any(|v| v == version) {
DocsVersionStatus::Beta
} else if DOCS_MANIFEST.stable == version {
} else if STABLE_VERSION_NAME.as_str() == version {
DocsVersionStatus::Stable
} else {
panic!("version '{}' isn't listed in the docs manifest", version)
Expand Down Expand Up @@ -355,9 +429,9 @@ pub async fn get_build_paths() -> RenderFnResult<Vec<String>> {
paths.push(path_str.clone());
// If it's for the latest stable version though, we should also render it without that prefix
// 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)
if path_str.starts_with(&DOCS_MANIFEST.stable) {
if path_str.starts_with(STABLE_VERSION_NAME.as_str()) {
let unprefixed_path_str = path_str
.strip_prefix(&format!("{}/", &DOCS_MANIFEST.stable))
.strip_prefix(&format!("{}/", STABLE_VERSION_NAME.as_str()))
.unwrap();
paths.push(unprefixed_path_str.to_string());
}
Expand Down

0 comments on commit ee379a1

Please sign in to comment.