Merge pull request #644 from getzola/next

v0.7.0
This commit is contained in:
Vincent Prouillet 2019-04-28 10:41:12 +02:00 committed by GitHub
commit 986437546c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 591 additions and 551 deletions

View file

@ -1,5 +1,14 @@
# Changelog
## 0.7.0 (2019-04-28)
### Breaking
- Remove --base-path option, it broke `serve` on Windows and wasn't properly tested
### Other
- Strip wrapping whitespaces from shortcodes
- Sort sitemap elements by permalink`
## 0.6.0 (2019-03-25)
### Breaking

918
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "zola"
version = "0.6.0"
version = "0.7.0"
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
license = "MIT"
readme = "README.md"

View file

@ -1,4 +1,5 @@
# zola (né Gutenberg)
[![Build Status](https://travis-ci.com/getzola/zola.svg?branch=master)](https://travis-ci.com/getzola/zola)
[![Build status](https://ci.appveyor.com/api/projects/status/i0ufvx2sdm2cmawo/branch/master?svg=true)](https://ci.appveyor.com/project/Keats/zola/branch/master)
@ -10,31 +11,30 @@ in the `docs/content` folder of the repository and the community can use [its fo
## Comparisons with other static site generators
| | Zola | Cobalt | Hugo | Pelican |
|:-------------------------------:|:---------:|--------|------|---------|
| Single binary | ✔ | ✔ | ✔ | ✕ |
|:--------------------------------|:--------------------:|:--------------------:|:--------------------:|:--------------------:|
| Single binary | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![no](./is-no.svg) |
| Language | Rust | Rust | Go | Python |
| Syntax highlighting | ✔ | ✔ | ✔ | ✔ |
| Sass compilation | ✔ | ✔ | ✔ | ✔ |
| Assets co-location | ✔ | ✔ | ✔ | ✔ |
| Multilingual site | ✔ | ✕ | ✔ | ✔ |
| Image processing | ✔ | ✕ | ✔ | ✔ |
| Sane & powerful template engine | ✔ | ~ | ~ | ✔ |
| Themes | ✔ | ✕ | ✔ | ✔ |
| Shortcodes | ✔ | ✕ | ✔ | ✔ |
| Internal links | ✔ | ✕ | ✔ | ✔ |
| Link checker | ✔ | ✕ | ✕ | ✔ |
| Table of contents | ✔ | ✕ | ✔ | ✔ |
| Automatic header anchors | ✔ | ✕ | ✔ | ✔ |
| Aliases | ✔ | ✕ | ✔ | ✔ |
| Pagination | ✔ | ✕ | ✔ | ✔ |
| Custom taxonomies | ✔ | ✕ | ✔ | ✕ |
| Search | ✔ | ✕ | ✕ | ✔ |
| Data files | ✔ | ✔ | ✔ | ✕ |
| LiveReload | ✔ | ✕ | ✔ | ✔ |
| Netlify support | ~ | ✕ | ✔ | ✕ |
| Breadcrumbs | ✔ | ✕ | ✕ | ✔ |
| Custom output formats | ✕ | ✕ | ✔ | ? |
| Syntax highlighting | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Sass compilation | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Assets co-location | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Multilingual site | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Image processing | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Sane & powerful template engine | ![yes](./is-yes.svg) | ![ehh](./is-ehh.svg) | ![ehh](./is-ehh.svg) | ![yes](./is-yes.svg) |
| Themes | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Shortcodes | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Internal links | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Link checker | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) |
| Table of contents | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Automatic header anchors | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Aliases | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Pagination | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Custom taxonomies | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![no](./is-no.svg) |
| Search | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) |
| Data files | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) | ![no](./is-no.svg) |
| LiveReload | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![yes](./is-yes.svg) |
| Netlify support | ![ehh](./is-ehh.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![no](./is-no.svg) |
| Breadcrumbs | ![yes](./is-yes.svg) | ![no](./is-no.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) |
| Custom output formats | ![no](./is-no.svg) | ![no](./is-no.svg) | ![yes](./is-yes.svg) | ![no](./is-no.svg) |
### Supported content formats
@ -45,11 +45,10 @@ in the `docs/content` folder of the repository and the community can use [its fo
### Template engine explanation
Cobalt gets `~` as, while based on [Liquid](https://shopify.github.io/liquid/), the Rust library doesn't implement all its features but there is no documentation on what is and isn't implemented. The errors are also cryptic. Liquid itself is not powerful enough to do some of things you can do in Jinja2, Go templates or Tera.
Cobalt gets ![ehh](./is-ehh.svg) because, while based on [Liquid](https://shopify.github.io/liquid/), the Rust library doesn't implement all its features and there is no documentation on what is and isn't implemented; the errors are cryptic; and Liquid itself is not powerful enough to do some of things you can do in Jinja2, Go templates, or Tera.
Hugo gets `~`. It is probably the most powerful template engine in the list after Jinja2 (hard to beat python code in templates) but personally drives me insane, to the point of writing my own template engine and static site generator. Yes, this is a bit biased.
Hugo gets ![ehh](./is-ehh.svg) because while it is probably the most powerful template engine in the list, after Jinja2, it personally drives me insane, to the point of writing my own template engine and static site generator. Yes, this is a bit biased.
### Pelican notes
Many features of Pelican are coming from plugins, which might be tricky
to use because of version mismatch or lacking documentation. Netlify supports Python
and Pipenv but you still need to install your dependencies manually.
Many features of Pelican are coming from plugins, which might be tricky to use because of version mismatch or lacking documentation. Netlify supports Python and Pipenv but you still need to install your dependencies manually.

View file

@ -16,6 +16,7 @@ pub struct ContentParser;
lazy_static! {
static ref MULTIPLE_NEWLINE_RE: Regex = Regex::new(r"\n\s*\n").unwrap();
static ref OUTER_NEWLINE_RE: Regex = Regex::new(r"^\s*\n|\n\s*$").unwrap();
}
fn replace_string_markers(input: &str) -> String {
@ -122,6 +123,8 @@ fn render_shortcode(
// at indentation, making the output a code block.
let res = MULTIPLE_NEWLINE_RE.replace_all(&res, "\n");
let res = OUTER_NEWLINE_RE.replace_all(&res, "");
Ok(res.to_string())
}
@ -411,4 +414,20 @@ Some body {{ hello() }}{%/* end */%}"#,
let res = render_shortcodes("Body\n {% youtube() %}\nHello \n World{% end %}", &tera);
assert_eq!(res, "Body\n Hello \n World");
}
#[test]
fn outer_newlines_removed_from_shortcodes_with_body() {
let mut tera = Tera::default();
tera.add_raw_template("shortcodes/youtube.html", " \n {{body}} \n ").unwrap();
let res = render_shortcodes("\n{% youtube() %} \n content \n {% end %}\n", &tera);
assert_eq!(res, "\n content \n");
}
#[test]
fn outer_newlines_removed_from_inline_shortcodes() {
let mut tera = Tera::default();
tera.add_raw_template("shortcodes/youtube.html", " \n Hello, Zola. \n ").unwrap();
let res = render_shortcodes("\n{{ youtube() }}\n", &tera);
assert_eq!(res, "\n Hello, Zola. \n");
}
}

View file

@ -66,7 +66,7 @@ pub struct Site {
impl Site {
/// Parse a site at the given path. Defaults to the current dir
/// Passing in a path is possible using the `base-path` command line build option
/// Passing in a path is only used in tests
pub fn new<P: AsRef<Path>>(path: P, config_file: &str) -> Result<Site> {
let path = path.as_ref();
let mut config = get_config(path, config_file);
@ -777,11 +777,15 @@ impl Site {
ensure_directory_exists(&self.output_path)?;
let library = self.library.read().unwrap();
let all_sitemap_entries = sitemap::find_entries(
let all_sitemap_entries = {
let mut all_sitemap_entries = sitemap::find_entries(
&library,
&self.taxonomies[..],
&self.config,
);
all_sitemap_entries.sort();
all_sitemap_entries
};
let sitemap_limit = 30000;
if all_sitemap_entries.len() < sitemap_limit {

View file

@ -5,6 +5,7 @@ use std::collections::{HashSet};
use tera::{Map, Value};
use config::{Config};
use library::{Library, Taxonomy};
use std::cmp::Ordering;
/// The sitemap only needs links, potentially date and extra for pages in case of updates
/// for examples so we trim down all entries to only that
@ -39,6 +40,18 @@ impl<'a> SitemapEntry<'a> {
}
}
impl<'a> PartialOrd for SitemapEntry<'a> {
fn partial_cmp(&self, other: &SitemapEntry) -> Option<Ordering> {
Some(self.permalink.as_ref().cmp(other.permalink.as_ref()))
}
}
impl<'a> Ord for SitemapEntry<'a> {
fn cmp(&self, other: &SitemapEntry) -> Ordering {
self.permalink.as_ref().cmp(other.permalink.as_ref())
}
}
/// Finds out all the links to put in a sitemap from the pages/sections/taxonomies
/// There are no duplicate permalinks in the output vec
pub fn find_entries<'a>(library: &'a Library, taxonomies: &'a [Taxonomy], config: &'a Config) -> Vec<SitemapEntry<'a>> {

View file

@ -1,4 +1,4 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
{% for sitemap_entry in entries %}
<url>
<loc>{{ sitemap_entry.permalink | safe }}</loc>

View file

@ -1,4 +1,4 @@
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemapindex xmlns="https://www.sitemaps.org/schemas/sitemap/0.9/siteindex.xsd">
{% for sitemap in sitemaps %}
<sitemap>
<loc>{{ sitemap }}</loc>

View file

@ -431,6 +431,7 @@ mod tests {
json!({
"category": {
"date": "1979-05-27T07:32:00Z",
"lt1": "07:32:00",
"key": "value"
},
})

View file

@ -1,3 +1,4 @@
[category]
key = "value"
date = 1979-05-27T07:32:00Z
lt1 = 07:32:00

View file

@ -36,14 +36,6 @@ $ zola build --base-url $DEPLOY_URL
This is useful for example when you want to deploy previews of a site to a dynamic URL, such as Netlify
deploy previews.
You can override the default `base_path` by passing a new directory to the `base-path` flag. If no `base-path` flag
is provided, zola defaults to your current working directory. This is useful if your zola project is located in
a different directory from where you're executing zola from.
```bash
$ zola build --base-path /path/to/zola/site
```
You can override the default output directory 'public' by passing a other value to the `output-dir` flag.
```bash
@ -75,7 +67,6 @@ $ zola serve --interface 0.0.0.0
$ zola serve --interface 0.0.0.0 --port 2000
$ zola serve --interface 0.0.0.0 --base-url 127.0.0.1
$ zola serve --interface 0.0.0.0 --port 2000 --output-dir www/public
$ zola serve --interface 0.0.0.0 --port 2000 --base-path mysite/ --output-dir mysite/www/public
$ zola serve --watch-only
```

3
is-ehh.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" style="fill:#dbab09" width="16" height="16" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="8"/>
</svg>

After

Width:  |  Height:  |  Size: 146 B

3
is-no.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" style="fill:#cb2431" width="16" height="16" viewBox="0 0 24 24">
<path d="M23 20.168l-8.185-8.187 8.185-8.174-2.832-2.807-8.182 8.179-8.176-8.179-2.81 2.81 8.186 8.196-8.186 8.184 2.81 2.81 8.203-8.192 8.18 8.192z"/>
</svg>

After

Width:  |  Height:  |  Size: 266 B

3
is-yes.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" style="fill:#28a745" width="16" height="16" viewBox="0 0 24 24">
<path d="M9 21.035l-9-8.638 2.791-2.87 6.156 5.874 12.21-12.436 2.843 2.817z"/>
</svg>

After

Width:  |  Height:  |  Size: 194 B

View file

@ -30,11 +30,6 @@ pub fn build_cli() -> App<'static, 'static> {
.long("base-url")
.takes_value(true)
.help("Force the base URL to be that value (default to the one in config.toml)"),
Arg::with_name("base_path")
.short("b")
.long("base-path")
.takes_value(true)
.help("Force the base site path to a certain directory [default: the current working directory]"),
Arg::with_name("output_dir")
.short("o")
.long("output-dir")
@ -61,11 +56,6 @@ pub fn build_cli() -> App<'static, 'static> {
.default_value("public")
.takes_value(true)
.help("Outputs the generated site in the given path"),
Arg::with_name("base_path")
.short("b")
.long("base-path")
.takes_value(true)
.help("Force the base site path to a certain directory [default: the current working directory]"),
Arg::with_name("base_url")
.short("u")
.long("base-url")

View file

@ -1,19 +1,12 @@
use std::env;
use std::path::PathBuf;
use errors::Result;
use site::Site;
use console;
pub fn build(
config_file: &str,
base_path: Option<&str>,
base_url: Option<&str>,
output_dir: &str,
) -> Result<()> {
let bp = base_path.map(PathBuf::from).unwrap_or(env::current_dir().unwrap());
let mut site = Site::new(bp, config_file)?;
pub fn build(config_file: &str, base_url: Option<&str>, output_dir: &str) -> Result<()> {
let mut site = Site::new(env::current_dir().unwrap(), config_file)?;
site.set_output_path(output_dir);
if let Some(b) = base_url {
site.set_base_url(b.to_string());

View file

@ -86,8 +86,8 @@ impl<S> Middleware<S> for NotFoundHandler {
}
}
fn livereload_handler(_: &HttpRequest) -> &'static str {
LIVE_RELOAD
fn livereload_handler(_: &HttpRequest) -> HttpResponse {
HttpResponse::Ok().content_type("text/javascript").body(LIVE_RELOAD)
}
fn rebuild_done_handling(broadcaster: &Option<Sender>, res: Result<()>, reload_path: &str) {
@ -114,15 +114,14 @@ fn rebuild_done_handling(broadcaster: &Option<Sender>, res: Result<()>, reload_p
}
}
fn create_new_site<P: AsRef<Path>>(
fn create_new_site(
interface: &str,
port: u16,
output_dir: &str,
base_path: P,
base_url: &str,
config_file: &str,
) -> Result<(Site, String)> {
let mut site = Site::new(base_path, config_file)?;
let mut site = Site::new(env::current_dir().unwrap(), config_file)?;
let base_address = format!("{}:{}", base_url, port);
let address = format!("{}:{}", interface, port);
@ -167,15 +166,12 @@ pub fn serve(
interface: &str,
port: u16,
output_dir: &str,
base_path: Option<&str>,
base_url: &str,
config_file: &str,
watch_only: bool,
) -> Result<()> {
let start = Instant::now();
let bp = base_path.map(PathBuf::from).unwrap_or(env::current_dir().unwrap());
let (mut site, address) =
create_new_site(interface, port, output_dir, bp.clone(), base_url, config_file)?;
let (mut site, address) = create_new_site(interface, port, output_dir, base_url, config_file)?;
console::report_elapsed_time(start);
// Setup watchers
@ -184,28 +180,28 @@ pub fn serve(
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
watcher
.watch(bp.join("content/"), RecursiveMode::Recursive)
.watch("content/", RecursiveMode::Recursive)
.map_err(|e| ZolaError::chain("Can't watch the `content` folder. Does it exist?", e))?;
watcher
.watch(bp.join(config_file), RecursiveMode::Recursive)
.watch(config_file, RecursiveMode::Recursive)
.map_err(|e| ZolaError::chain("Can't watch the `config` file. Does it exist?", e))?;
if bp.join("static").exists() {
if Path::new("static").exists() {
watching_static = true;
watcher
.watch(bp.join("static/"), RecursiveMode::Recursive)
.watch("static/", RecursiveMode::Recursive)
.map_err(|e| ZolaError::chain("Can't watch the `static` folder.", e))?;
}
if bp.join("templates").exists() {
if Path::new("templates").exists() {
watching_templates = true;
watcher
.watch(bp.join("templates/"), RecursiveMode::Recursive)
.watch("templates/", RecursiveMode::Recursive)
.map_err(|e| ZolaError::chain("Can't watch the `templates` folder.", e))?;
}
// Sass support is optional so don't make it an error to no have a sass folder
let _ = watcher.watch(bp.join("sass/"), RecursiveMode::Recursive);
let _ = watcher.watch("sass/", RecursiveMode::Recursive);
let ws_address = format!("{}:{}", interface, site.live_reload.unwrap());
let output_path = Path::new(output_dir).to_path_buf();
@ -262,6 +258,8 @@ pub fn serve(
None
};
let pwd = env::current_dir().unwrap();
let mut watchers = vec!["content", "config.toml"];
if watching_static {
watchers.push("static");
@ -275,7 +273,7 @@ pub fn serve(
println!(
"Listening for changes in {}{}{{{}}}",
bp.display(),
pwd.display(),
MAIN_SEPARATOR,
watchers.join(", ")
);
@ -351,8 +349,7 @@ pub fn serve(
if path.is_file() && is_temp_file(&path) {
continue;
}
let (change_kind, partial_path) =
detect_change_kind(&bp.canonicalize().unwrap(), &path);
let (change_kind, partial_path) = detect_change_kind(&pwd, &path);
// We only care about changes in non-empty folders
if path.is_dir() && is_folder_empty(&path) {
@ -384,7 +381,6 @@ pub fn serve(
interface,
port,
output_dir,
bp.clone(),
base_url,
config_file,
)
@ -405,7 +401,7 @@ pub fn serve(
);
let start = Instant::now();
match detect_change_kind(&bp.canonicalize().unwrap(), &path) {
match detect_change_kind(&pwd, &path) {
(ChangeKind::Content, _) => {
console::info(&format!("-> Content changed {}", path.display()));
// Force refresh
@ -424,7 +420,6 @@ pub fn serve(
interface,
port,
output_dir,
bp.clone(),
base_url,
config_file,
)

View file

@ -46,12 +46,7 @@ fn main() {
console::info("Building site...");
let start = Instant::now();
let output_dir = matches.value_of("output_dir").unwrap();
match cmd::build(
config_file,
matches.value_of("base_path"),
matches.value_of("base_url"),
output_dir,
) {
match cmd::build(config_file, matches.value_of("base_url"), output_dir) {
Ok(()) => console::report_elapsed_time(start),
Err(e) => {
console::unravel_errors("Failed to build the site", &e);
@ -84,18 +79,9 @@ fn main() {
}
let watch_only = matches.is_present("watch_only");
let output_dir = matches.value_of("output_dir").unwrap();
let base_path = matches.value_of("base_path");
let base_url = matches.value_of("base_url").unwrap();
console::info("Building site...");
match cmd::serve(
interface,
port,
output_dir,
base_path,
base_url,
config_file,
watch_only,
) {
match cmd::serve(interface, port, output_dir, base_url, config_file, watch_only) {
Ok(()) => (),
Err(e) => {
console::unravel_errors("", &e);