mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-04-12 03:38:30 +00:00
Fix base_dir for GitHub PageS
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -55,6 +55,7 @@ test/*.pem
|
|||||||
test/*.srl
|
test/*.srl
|
||||||
test/*.log
|
test/*.log
|
||||||
test/_build_*
|
test/_build_*
|
||||||
|
test/cpp-httplib/
|
||||||
work/
|
work/
|
||||||
benchmark/server*
|
benchmark/server*
|
||||||
docs-gen/target/
|
docs-gen/target/
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ docs-src/
|
|||||||
[site]
|
[site]
|
||||||
title = "My Project"
|
title = "My Project"
|
||||||
base_url = "https://example.github.io/my-project"
|
base_url = "https://example.github.io/my-project"
|
||||||
|
base_path = "/my-project"
|
||||||
|
|
||||||
[i18n]
|
[i18n]
|
||||||
default_lang = "en"
|
default_lang = "en"
|
||||||
@@ -64,6 +65,19 @@ theme = "base16-eighties.dark" # Dark mode syntax theme (syntect built-in
|
|||||||
theme_light = "base16-ocean.light" # Light mode syntax theme (optional)
|
theme_light = "base16-ocean.light" # Light mode syntax theme (optional)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `base_path`
|
||||||
|
|
||||||
|
`base_path` controls the URL prefix prepended to all generated links, CSS/JS paths, and redirects.
|
||||||
|
|
||||||
|
| Value | Use case |
|
||||||
|
|---|---|
|
||||||
|
| `"/my-project"` | GitHub Pages (`https://user.github.io/my-project/`) |
|
||||||
|
| `""` | Local development at `http://localhost:8000/` |
|
||||||
|
|
||||||
|
Leave empty for local-only use; set to `"/<repo-name>"` before deploying to GitHub Pages.
|
||||||
|
|
||||||
|
### `highlight`
|
||||||
|
|
||||||
When `theme_light` is set, code blocks are rendered twice (dark and light) and toggled via CSS classes `.code-dark` / `.code-light`.
|
When `theme_light` is set, code blocks are rendered twice (dark and light) and toggled via CSS classes `.code-dark` / `.code-light`.
|
||||||
|
|
||||||
Available themes: `base16-ocean.dark`, `base16-ocean.light`, `base16-eighties.dark`, `base16-mocha.dark`, `InspiredGitHub`, `Solarized (dark)`, `Solarized (light)`.
|
Available themes: `base16-ocean.dark`, `base16-ocean.light`, `base16-eighties.dark`, `base16-mocha.dark`, `InspiredGitHub`, `Solarized (dark)`, `Solarized (light)`.
|
||||||
@@ -90,12 +104,24 @@ order: 1
|
|||||||
Markdown files are mapped to URLs as follows:
|
Markdown files are mapped to URLs as follows:
|
||||||
|
|
||||||
| File path | URL | Output file |
|
| File path | URL | Output file |
|
||||||
|-----------------------|-----------------|--------------------------|
|
|-----------------------|-----------------------------|-------------------------------------|
|
||||||
| `en/index.md` | `/en/` | `en/index.html` |
|
| `en/index.md` | `<base_path>/en/` | `en/index.html` |
|
||||||
| `en/tour/index.md` | `/en/tour/` | `en/tour/index.html` |
|
| `en/tour/index.md` | `<base_path>/en/tour/` | `en/tour/index.html` |
|
||||||
| `en/tour/01-foo.md` | `/en/tour/01-foo/` | `en/tour/01-foo/index.html` |
|
| `en/tour/01-foo.md` | `<base_path>/en/tour/01-foo/` | `en/tour/01-foo/index.html` |
|
||||||
|
|
||||||
A root `index.html` is generated automatically, redirecting `/` to `/<default_lang>/` (respecting `localStorage` preference).
|
A root `index.html` is generated automatically, redirecting `<base_path>/` to `<base_path>/<default_lang>/` (respecting `localStorage` preference).
|
||||||
|
|
||||||
|
## Local Debugging vs GitHub Pages
|
||||||
|
|
||||||
|
To preview locally with the same URL structure as GitHub Pages, set `base_path = "/cpp-httplib"` in `config.toml`, then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./docs-gen/target/release/docs-gen docs-src --out /tmp/test/cpp-httplib
|
||||||
|
cd /tmp/test && python3 -m http.server
|
||||||
|
# Open http://localhost:8000/cpp-httplib/
|
||||||
|
```
|
||||||
|
|
||||||
|
For a plain local preview (no prefix), set `base_path = ""` and open `http://localhost:8000/`.
|
||||||
|
|
||||||
## Navigation
|
## Navigation
|
||||||
|
|
||||||
@@ -122,6 +148,7 @@ Templates use [Tera](https://keats.github.io/tera/) syntax. Available variables:
|
|||||||
| `lang` | string | Current language code |
|
| `lang` | string | Current language code |
|
||||||
| `site.title` | string | Site title from config |
|
| `site.title` | string | Site title from config |
|
||||||
| `site.base_url` | string | Base URL from config |
|
| `site.base_url` | string | Base URL from config |
|
||||||
|
| `site.base_path` | string | Base path prefix (e.g. `"/cpp-httplib"` or `""`) |
|
||||||
| `site.langs` | list | Available language codes |
|
| `site.langs` | list | Available language codes |
|
||||||
|
|
||||||
### page.html only
|
### page.html only
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ struct SiteContext {
|
|||||||
title: String,
|
title: String,
|
||||||
version: Option<String>,
|
version: Option<String>,
|
||||||
base_url: String,
|
base_url: String,
|
||||||
|
base_path: String,
|
||||||
langs: Vec<String>,
|
langs: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ pub fn build(src: &Path, out: &Path) -> Result<()> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pages = collect_pages(&pages_dir, lang, out, &renderer)?;
|
let pages = collect_pages(&pages_dir, lang, out, &renderer, &config.site.base_path)?;
|
||||||
let nav = build_nav(&pages);
|
let nav = build_nav(&pages);
|
||||||
|
|
||||||
for page in &pages {
|
for page in &pages {
|
||||||
@@ -81,7 +82,7 @@ pub fn build(src: &Path, out: &Path) -> Result<()> {
|
|||||||
let section_nav: Vec<&NavItem> = nav
|
let section_nav: Vec<&NavItem> = nav
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|item| {
|
.filter(|item| {
|
||||||
let item_section = extract_section(&item.url);
|
let item_section = extract_section(&item.url, &config.site.base_path);
|
||||||
item_section == page.section
|
item_section == page.section
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -98,6 +99,7 @@ pub fn build(src: &Path, out: &Path) -> Result<()> {
|
|||||||
title: config.site.title.clone(),
|
title: config.site.title.clone(),
|
||||||
version: config.site.version.clone(),
|
version: config.site.version.clone(),
|
||||||
base_url: config.site.base_url.clone(),
|
base_url: config.site.base_url.clone(),
|
||||||
|
base_path: config.site.base_path.clone(),
|
||||||
langs: config.i18n.langs.clone(),
|
langs: config.i18n.langs.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,6 +150,7 @@ fn collect_pages(
|
|||||||
lang: &str,
|
lang: &str,
|
||||||
out: &Path,
|
out: &Path,
|
||||||
renderer: &MarkdownRenderer,
|
renderer: &MarkdownRenderer,
|
||||||
|
base_path: &str,
|
||||||
) -> Result<Vec<Page>> {
|
) -> Result<Vec<Page>> {
|
||||||
let mut pages = Vec::new();
|
let mut pages = Vec::new();
|
||||||
|
|
||||||
@@ -172,30 +175,30 @@ fn collect_pages(
|
|||||||
|
|
||||||
// Compute URL and output path
|
// Compute URL and output path
|
||||||
let (url, out_path) = if rel.file_name().map_or(false, |f| f == "index.md") {
|
let (url, out_path) = if rel.file_name().map_or(false, |f| f == "index.md") {
|
||||||
// index.md -> /<lang>/dir/
|
// index.md -> <base_path>/<lang>/dir/
|
||||||
let parent = rel.parent().unwrap_or(Path::new(""));
|
let parent = rel.parent().unwrap_or(Path::new(""));
|
||||||
if parent.as_os_str().is_empty() {
|
if parent.as_os_str().is_empty() {
|
||||||
// Root index.md
|
// Root index.md
|
||||||
(
|
(
|
||||||
format!("/{}/", lang),
|
format!("{}/{}/", base_path, lang),
|
||||||
out.join(lang).join("index.html"),
|
out.join(lang).join("index.html"),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
format!("/{}/{}/", lang, parent.display()),
|
format!("{}/{}/{}/", base_path, lang, parent.display()),
|
||||||
out.join(lang).join(parent).join("index.html"),
|
out.join(lang).join(parent).join("index.html"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// foo.md -> /<lang>/foo/
|
// foo.md -> <base_path>/<lang>/foo/
|
||||||
let stem = rel.with_extension("");
|
let stem = rel.with_extension("");
|
||||||
(
|
(
|
||||||
format!("/{}/{}/", lang, stem.display()),
|
format!("{}/{}/{}/", base_path, lang, stem.display()),
|
||||||
out.join(lang).join(&stem).join("index.html"),
|
out.join(lang).join(&stem).join("index.html"),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let section = extract_section(&url);
|
let section = extract_section(&url, base_path);
|
||||||
|
|
||||||
pages.push(Page {
|
pages.push(Page {
|
||||||
frontmatter,
|
frontmatter,
|
||||||
@@ -210,9 +213,11 @@ fn collect_pages(
|
|||||||
Ok(pages)
|
Ok(pages)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_section(url: &str) -> String {
|
fn extract_section(url: &str, base_path: &str) -> String {
|
||||||
|
// Strip base_path prefix before parsing
|
||||||
|
let stripped = url.strip_prefix(base_path).unwrap_or(url);
|
||||||
// URL format: /<lang>/ or /<lang>/section/...
|
// URL format: /<lang>/ or /<lang>/section/...
|
||||||
let parts: Vec<&str> = url.trim_matches('/').split('/').collect();
|
let parts: Vec<&str> = stripped.trim_matches('/').split('/').collect();
|
||||||
if parts.len() >= 2 {
|
if parts.len() >= 2 {
|
||||||
parts[1].to_string()
|
parts[1].to_string()
|
||||||
} else {
|
} else {
|
||||||
@@ -249,7 +254,7 @@ fn build_nav(pages: &[Page]) -> Vec<NavItem> {
|
|||||||
// Find the section index page
|
// Find the section index page
|
||||||
let index_page = section_pages
|
let index_page = section_pages
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| p.rel_path.ends_with("index.md") && extract_section(&p.url) == section);
|
.find(|p| p.rel_path.ends_with("index.md") && p.section == section);
|
||||||
|
|
||||||
let section_title = index_page
|
let section_title = index_page
|
||||||
.map(|p| p.frontmatter.title.clone())
|
.map(|p| p.frontmatter.title.clone())
|
||||||
@@ -260,7 +265,7 @@ fn build_nav(pages: &[Page]) -> Vec<NavItem> {
|
|||||||
|
|
||||||
let children: Vec<NavItem> = section_pages
|
let children: Vec<NavItem> = section_pages
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|p| !p.rel_path.ends_with("index.md") || extract_section(&p.url) != section)
|
.filter(|p| !p.rel_path.ends_with("index.md") || p.section != section)
|
||||||
.map(|p| NavItem {
|
.map(|p| NavItem {
|
||||||
title: p.frontmatter.title.clone(),
|
title: p.frontmatter.title.clone(),
|
||||||
url: p.url.clone(),
|
url: p.url.clone(),
|
||||||
@@ -294,6 +299,7 @@ fn set_active(item: &mut NavItem, current_url: &str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn generate_root_redirect(out: &Path, config: &SiteConfig) -> Result<()> {
|
fn generate_root_redirect(out: &Path, config: &SiteConfig) -> Result<()> {
|
||||||
|
let base_path = &config.site.base_path;
|
||||||
let html = format!(
|
let html = format!(
|
||||||
r#"<!DOCTYPE html>
|
r#"<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -301,19 +307,19 @@ fn generate_root_redirect(out: &Path, config: &SiteConfig) -> Result<()> {
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script>
|
<script>
|
||||||
(function() {{
|
(function() {{
|
||||||
var lang = localStorage.getItem('preferred-lang') || '{}';
|
var lang = localStorage.getItem('preferred-lang') || '{default_lang}';
|
||||||
window.location.replace('/' + lang + '/');
|
window.location.replace('{base_path}/' + lang + '/');
|
||||||
}})();
|
}})();
|
||||||
</script>
|
</script>
|
||||||
<meta http-equiv="refresh" content="0;url=/{default_lang}/">
|
<meta http-equiv="refresh" content="0;url={base_path}/{default_lang}/">
|
||||||
<title>Redirecting...</title>
|
<title>Redirecting...</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>Redirecting to <a href="/{default_lang}/">/{default_lang}/</a>...</p>
|
<p>Redirecting to <a href="{base_path}/{default_lang}/">{base_path}/{default_lang}/</a>...</p>
|
||||||
</body>
|
</body>
|
||||||
</html>"#,
|
</html>"#,
|
||||||
config.i18n.default_lang,
|
|
||||||
default_lang = config.i18n.default_lang,
|
default_lang = config.i18n.default_lang,
|
||||||
|
base_path = base_path,
|
||||||
);
|
);
|
||||||
|
|
||||||
fs::write(out.join("index.html"), html)?;
|
fs::write(out.join("index.html"), html)?;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ pub struct Site {
|
|||||||
pub title: String,
|
pub title: String,
|
||||||
pub version: Option<String>,
|
pub version: Option<String>,
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub base_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -33,8 +35,11 @@ impl SiteConfig {
|
|||||||
let path = src_dir.join("config.toml");
|
let path = src_dir.join("config.toml");
|
||||||
let content =
|
let content =
|
||||||
std::fs::read_to_string(&path).with_context(|| format!("Failed to read {}", path.display()))?;
|
std::fs::read_to_string(&path).with_context(|| format!("Failed to read {}", path.display()))?;
|
||||||
let config: SiteConfig =
|
let mut config: SiteConfig =
|
||||||
toml::from_str(&content).with_context(|| format!("Failed to parse {}", path.display()))?;
|
toml::from_str(&content).with_context(|| format!("Failed to parse {}", path.display()))?;
|
||||||
|
// Normalize base_path: strip trailing slash (but keep empty for root)
|
||||||
|
let bp = config.site.base_path.trim_end_matches('/').to_string();
|
||||||
|
config.site.base_path = bp;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
title = "cpp-httplib"
|
title = "cpp-httplib"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
base_url = "https://yhirose.github.io/cpp-httplib"
|
base_url = "https://yhirose.github.io/cpp-httplib"
|
||||||
|
# Base path for URL generation. Use "/cpp-httplib" for GitHub Pages,
|
||||||
|
# or "" for local development (python3 -m http.server).
|
||||||
|
base_path = "/cpp-httplib"
|
||||||
|
|
||||||
[i18n]
|
[i18n]
|
||||||
default_lang = "en"
|
default_lang = "en"
|
||||||
|
|||||||
@@ -19,8 +19,11 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var lang = link.getAttribute('data-lang');
|
var lang = link.getAttribute('data-lang');
|
||||||
localStorage.setItem('preferred-lang', lang);
|
localStorage.setItem('preferred-lang', lang);
|
||||||
|
var basePath = document.documentElement.getAttribute('data-base-path') || '';
|
||||||
var path = window.location.pathname;
|
var path = window.location.pathname;
|
||||||
var newPath = path.replace(/^\/[a-z]{2}\//, '/' + lang + '/');
|
// Strip base path prefix, replace lang, then re-add base path
|
||||||
|
var pathWithoutBase = path.slice(basePath.length);
|
||||||
|
var newPath = basePath + pathWithoutBase.replace(/^\/[a-z]{2}\//, '/' + lang + '/');
|
||||||
window.location.href = newPath;
|
window.location.href = newPath;
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ lang }}">
|
<html lang="{{ lang }}" data-base-path="{{ site.base_path }}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{ page.title }} - {{ site.title }}</title>
|
<title>{{ page.title }} - {{ site.title }}</title>
|
||||||
<link rel="stylesheet" href="/css/main.css">
|
<link rel="stylesheet" href="{{ site.base_path }}/css/main.css">
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var t = localStorage.getItem('preferred-theme');
|
var t = localStorage.getItem('preferred-theme');
|
||||||
@@ -16,11 +16,11 @@
|
|||||||
<body>
|
<body>
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<div class="header-inner">
|
<div class="header-inner">
|
||||||
<a href="/{{ lang }}/" class="header-title">{{ site.title }}{% if site.version %} <span style="font-size:0.75em;font-weight:normal;margin-left:4px">v{{ site.version }}</span>{% endif %}</a>
|
<a href="{{ site.base_path }}/{{ lang }}/" class="header-title">{{ site.title }}{% if site.version %} <span style="font-size:0.75em;font-weight:normal;margin-left:4px">v{{ site.version }}</span>{% endif %}</a>
|
||||||
<div class="header-spacer"></div>
|
<div class="header-spacer"></div>
|
||||||
<nav class="header-nav">
|
<nav class="header-nav">
|
||||||
<a href="/{{ lang }}/">Home</a>
|
<a href="{{ site.base_path }}/{{ lang }}/">Home</a>
|
||||||
<a href="/{{ lang }}/tour/">Tour</a>
|
<a href="{{ site.base_path }}/{{ lang }}/tour/">Tour</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="header-tools">
|
<div class="header-tools">
|
||||||
<button class="theme-toggle" aria-label="Toggle theme"></button>
|
<button class="theme-toggle" aria-label="Toggle theme"></button>
|
||||||
@@ -49,6 +49,6 @@
|
|||||||
© 2026 yhirose. All rights reserved.
|
© 2026 yhirose. All rights reserved.
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="/js/main.js"></script>
|
<script src="{{ site.base_path }}/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
5
justfile
5
justfile
@@ -48,3 +48,8 @@ bench:
|
|||||||
docs:
|
docs:
|
||||||
cargo build --release --manifest-path docs-gen/Cargo.toml
|
cargo build --release --manifest-path docs-gen/Cargo.toml
|
||||||
./docs-gen/target/release/docs-gen docs-src --out docs
|
./docs-gen/target/release/docs-gen docs-src --out docs
|
||||||
|
|
||||||
|
docs-test:
|
||||||
|
cargo build --release --manifest-path docs-gen/Cargo.toml
|
||||||
|
./docs-gen/target/release/docs-gen docs-src --out test/cpp-httplib
|
||||||
|
cd test && python3 -m http.server
|
||||||
|
|||||||
@@ -254,5 +254,5 @@ cert.pem:
|
|||||||
./gen-certs.sh
|
./gen-certs.sh
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf test test_split test_mbedtls test_split_mbedtls test_wolfssl test_split_wolfssl test_no_tls, test_split_no_tls test_proxy test_proxy_mbedtls test_proxy_wolfssl server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM *_shard_*.log
|
rm -rf test test_split test_mbedtls test_split_mbedtls test_wolfssl test_split_wolfssl test_no_tls, test_split_no_tls test_proxy test_proxy_mbedtls test_proxy_wolfssl server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM *_shard_*.log cpp-httplib
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user