@@ -7,9 +7,6 @@ | |||||
[submodule "sublime_syntaxes/LESS-sublime"] | [submodule "sublime_syntaxes/LESS-sublime"] | ||||
path = sublime_syntaxes/LESS-sublime | path = sublime_syntaxes/LESS-sublime | ||||
url = https://github.com/danro/LESS-sublime.git | url = https://github.com/danro/LESS-sublime.git | ||||
[submodule "sublime_syntaxes/TypeScript-Sublime-Plugin"] | |||||
path = sublime_syntaxes/TypeScript-Sublime-Plugin | |||||
url = https://github.com/Microsoft/TypeScript-Sublime-Plugin.git | |||||
[submodule "sublime_syntaxes/Handlebars"] | [submodule "sublime_syntaxes/Handlebars"] | ||||
path = sublime_syntaxes/Handlebars | path = sublime_syntaxes/Handlebars | ||||
url = https://github.com/daaain/Handlebars.git | url = https://github.com/daaain/Handlebars.git | ||||
@@ -31,3 +28,6 @@ | |||||
[submodule "sublime_syntaxes/Sublime-VimL"] | [submodule "sublime_syntaxes/Sublime-VimL"] | ||||
path = sublime_syntaxes/Sublime-VimL | path = sublime_syntaxes/Sublime-VimL | ||||
url = https://github.com/SalGnt/Sublime-VimL.git | url = https://github.com/SalGnt/Sublime-VimL.git | ||||
[submodule "sublime_syntaxes/TypeScript-TmLanguage"] | |||||
path = sublime_syntaxes/TypeScript-TmLanguage | |||||
url = https://github.com/Microsoft/TypeScript-TmLanguage |
@@ -1,7 +1,6 @@ | |||||
dist: trusty | dist: trusty | ||||
language: rust | language: rust | ||||
services: docker | services: docker | ||||
sudo: required | |||||
env: | env: | ||||
global: | global: | ||||
@@ -20,6 +19,9 @@ matrix: | |||||
rust: beta | rust: beta | ||||
- env: TARGET=x86_64-unknown-linux-gnu | - env: TARGET=x86_64-unknown-linux-gnu | ||||
rust: nightly | rust: nightly | ||||
# The earliest stable Rust version that works | |||||
- env: TARGET=x86_64-unknown-linux-gnu | |||||
rust: 1.23.0 | |||||
before_install: set -e | before_install: set -e | ||||
@@ -1,5 +1,26 @@ | |||||
# Changelog | # Changelog | ||||
## 0.3.0 (2017-01-25) | |||||
### Breaking | |||||
- Change names of individual taxonomies to be plural (ie `tags/my-tag` instead of `tag/my-tag`) | |||||
- Front matter now uses TOML dates rather strings: remove quotes from your date value to fix it. | |||||
For example: `date = "2001-10-10"` becomes `date = 2001-10-10` | |||||
- `language_code` has been renamed `default_language` in preparations of i18n support | |||||
### Others | |||||
- Add `get_taxonomy_url` to retrieve the permalink of a tag/category | |||||
- Fix bug when generating permalinks for taxonomies | |||||
- Update to Tera 0.11 | |||||
- Better UX on first `serve` thanks to some default templates. | |||||
- Add `output-dir` to `build` and `serve` to generate the site in a folder other than `public` | |||||
- Add Prolog syntax highlighting and update all current syntaxes | |||||
- Live reloading now works on shortcode template changes | |||||
- `gutenberg serve` now reloads site on `config.toml` changes: you will need to F5 to see any changes though | |||||
- Add a `trans` global function that will get return the translation of the given key for the given lang, defaulting | |||||
to `config.default_language` if not given | |||||
- `gutenberg serve` cleans after itself and deletes the output directory on CTRL+C | |||||
## 0.2.2 (2017-11-01) | ## 0.2.2 (2017-11-01) | ||||
- Fix shortcodes without arguments being ignored | - Fix shortcodes without arguments being ignored | ||||
@@ -1,10 +1,10 @@ | |||||
[package] | [package] | ||||
name = "gutenberg" | name = "gutenberg" | ||||
version = "0.2.2" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | |||||
version = "0.3.0" | |||||
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"] | |||||
license = "MIT" | license = "MIT" | ||||
readme = "README.md" | readme = "README.md" | ||||
description = "Static site generator" | |||||
description = "A static site generator with everything built-in" | |||||
homepage = "https://github.com/Keats/gutenberg" | homepage = "https://github.com/Keats/gutenberg" | ||||
repository = "https://github.com/Keats/gutenberg" | repository = "https://github.com/Keats/gutenberg" | ||||
keywords = ["static", "site", "generator", "blog"] | keywords = ["static", "site", "generator", "blog"] | ||||
@@ -24,11 +24,12 @@ term-painter = "0.2" | |||||
# Used in init to ensure the url given as base_url is a valid one | # Used in init to ensure the url given as base_url is a valid one | ||||
url = "1.5" | url = "1.5" | ||||
# Below is for the serve cmd | # Below is for the serve cmd | ||||
staticfile = "0.4" | |||||
iron = "0.5" | |||||
mount = "0.3" | |||||
staticfile = "0.5" | |||||
iron = "0.6" | |||||
mount = "0.4" | |||||
notify = "4" | notify = "4" | ||||
ws = "0.7" | ws = "0.7" | ||||
ctrlc = "3" | |||||
site = { path = "components/site" } | site = { path = "components/site" } | ||||
errors = { path = "components/errors" } | errors = { path = "components/errors" } | ||||
@@ -1,7 +1,7 @@ | |||||
#[macro_use] | #[macro_use] | ||||
extern crate clap; | extern crate clap; | ||||
use clap::Shell; | |||||
// use clap::Shell; | |||||
include!("src/cli.rs"); | include!("src/cli.rs"); | ||||
@@ -32,6 +32,8 @@ _arguments -s -S -C \ | |||||
_arguments -s -S -C \ | _arguments -s -S -C \ | ||||
'-u+[Force the base URL to be that value (default to the one in config.toml)]' \ | '-u+[Force the base URL to be that value (default to the one in config.toml)]' \ | ||||
'--base-url+[Force the base URL to be that value (default to the one in config.toml)]' \ | '--base-url+[Force the base URL to be that value (default to the one in config.toml)]' \ | ||||
'-o+[Outputs the generated site in the given path]' \ | |||||
'--output-dir+[Outputs the generated site in the given path]' \ | |||||
'-h[Prints help information]' \ | '-h[Prints help information]' \ | ||||
'--help[Prints help information]' \ | '--help[Prints help information]' \ | ||||
'-V[Prints version information]' \ | '-V[Prints version information]' \ | ||||
@@ -44,6 +46,8 @@ _arguments -s -S -C \ | |||||
'--interface+[Interface to bind on]' \ | '--interface+[Interface to bind on]' \ | ||||
'-p+[Which port to use]' \ | '-p+[Which port to use]' \ | ||||
'--port+[Which port to use]' \ | '--port+[Which port to use]' \ | ||||
'-o+[Outputs the generated site in the given path]' \ | |||||
'--output-dir+[Outputs the generated site in the given path]' \ | |||||
'-h[Prints help information]' \ | '-h[Prints help information]' \ | ||||
'--help[Prints help information]' \ | '--help[Prints help information]' \ | ||||
'-V[Prints version information]' \ | '-V[Prints version information]' \ | ||||
@@ -53,11 +53,11 @@ | |||||
} | } | ||||
'_gutenberg_build' { | '_gutenberg_build' { | ||||
$completions = @('-h', '-V', '-u', '--help', '--version', '--base-url') | |||||
$completions = @('-h', '-V', '-u', '-o', '--help', '--version', '--base-url', '--output-dir') | |||||
} | } | ||||
'_gutenberg_serve' { | '_gutenberg_serve' { | ||||
$completions = @('-h', '-V', '-i', '-p', '--help', '--version', '--interface', '--port') | |||||
$completions = @('-h', '-V', '-i', '-p', '-o', '--help', '--version', '--interface', '--port', '--output-dir') | |||||
} | } | ||||
'_gutenberg_help' { | '_gutenberg_help' { | ||||
@@ -0,0 +1,153 @@ | |||||
_gutenberg() { | |||||
local i cur prev opts cmds | |||||
COMPREPLY=() | |||||
cur="${COMP_WORDS[COMP_CWORD]}" | |||||
prev="${COMP_WORDS[COMP_CWORD-1]}" | |||||
cmd="" | |||||
opts="" | |||||
for i in ${COMP_WORDS[@]} | |||||
do | |||||
case "${i}" in | |||||
gutenberg) | |||||
cmd="gutenberg" | |||||
;; | |||||
build) | |||||
cmd+="__build" | |||||
;; | |||||
help) | |||||
cmd+="__help" | |||||
;; | |||||
init) | |||||
cmd+="__init" | |||||
;; | |||||
serve) | |||||
cmd+="__serve" | |||||
;; | |||||
*) | |||||
;; | |||||
esac | |||||
done | |||||
case "${cmd}" in | |||||
gutenberg) | |||||
opts=" -c -h -V --config --help --version init build serve help" | |||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
fi | |||||
case "${prev}" in | |||||
*) | |||||
COMPREPLY=() | |||||
;; | |||||
esac | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
;; | |||||
gutenberg__build) | |||||
opts=" -h -V -u -o --help --version --base-url --output-dir " | |||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
fi | |||||
case "${prev}" in | |||||
--base-url) | |||||
COMPREPLY=("<base_url>") | |||||
return 0 | |||||
;; | |||||
-u) | |||||
COMPREPLY=("<base_url>") | |||||
return 0 | |||||
;; | |||||
--output-dir) | |||||
COMPREPLY=("<output_dir>") | |||||
return 0 | |||||
;; | |||||
-o) | |||||
COMPREPLY=("<output_dir>") | |||||
return 0 | |||||
;; | |||||
*) | |||||
COMPREPLY=() | |||||
;; | |||||
esac | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
;; | |||||
gutenberg__help) | |||||
opts=" -h -V --help --version " | |||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
fi | |||||
case "${prev}" in | |||||
*) | |||||
COMPREPLY=() | |||||
;; | |||||
esac | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
;; | |||||
gutenberg__init) | |||||
opts=" -h -V --help --version <name> " | |||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
fi | |||||
case "${prev}" in | |||||
*) | |||||
COMPREPLY=() | |||||
;; | |||||
esac | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
;; | |||||
gutenberg__serve) | |||||
opts=" -h -V -i -p -o --help --version --interface --port --output-dir " | |||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
fi | |||||
case "${prev}" in | |||||
--interface) | |||||
COMPREPLY=("<interface>") | |||||
return 0 | |||||
;; | |||||
-i) | |||||
COMPREPLY=("<interface>") | |||||
return 0 | |||||
;; | |||||
--port) | |||||
COMPREPLY=("<port>") | |||||
return 0 | |||||
;; | |||||
-p) | |||||
COMPREPLY=("<port>") | |||||
return 0 | |||||
;; | |||||
--output-dir) | |||||
COMPREPLY=("<output_dir>") | |||||
return 0 | |||||
;; | |||||
-o) | |||||
COMPREPLY=("<output_dir>") | |||||
return 0 | |||||
;; | |||||
*) | |||||
COMPREPLY=() | |||||
;; | |||||
esac | |||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||||
return 0 | |||||
;; | |||||
esac | |||||
} | |||||
complete -F _gutenberg -o bashdefault -o default gutenberg |
@@ -21,10 +21,12 @@ complete -c gutenberg -n "__fish_using_command gutenberg" -f -a "help" -d 'Print | |||||
complete -c gutenberg -n "__fish_using_command gutenberg init" -s h -l help -d 'Prints help information' | complete -c gutenberg -n "__fish_using_command gutenberg init" -s h -l help -d 'Prints help information' | ||||
complete -c gutenberg -n "__fish_using_command gutenberg init" -s V -l version -d 'Prints version information' | complete -c gutenberg -n "__fish_using_command gutenberg init" -s V -l version -d 'Prints version information' | ||||
complete -c gutenberg -n "__fish_using_command gutenberg build" -s u -l base-url -d 'Force the base URL to be that value (default to the one in config.toml)' | complete -c gutenberg -n "__fish_using_command gutenberg build" -s u -l base-url -d 'Force the base URL to be that value (default to the one in config.toml)' | ||||
complete -c gutenberg -n "__fish_using_command gutenberg build" -s o -l output-dir -d 'Outputs the generated site in the given path' | |||||
complete -c gutenberg -n "__fish_using_command gutenberg build" -s h -l help -d 'Prints help information' | complete -c gutenberg -n "__fish_using_command gutenberg build" -s h -l help -d 'Prints help information' | ||||
complete -c gutenberg -n "__fish_using_command gutenberg build" -s V -l version -d 'Prints version information' | complete -c gutenberg -n "__fish_using_command gutenberg build" -s V -l version -d 'Prints version information' | ||||
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s i -l interface -d 'Interface to bind on' | complete -c gutenberg -n "__fish_using_command gutenberg serve" -s i -l interface -d 'Interface to bind on' | ||||
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s p -l port -d 'Which port to use' | complete -c gutenberg -n "__fish_using_command gutenberg serve" -s p -l port -d 'Which port to use' | ||||
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s o -l output-dir -d 'Outputs the generated site in the given path' | |||||
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s h -l help -d 'Prints help information' | complete -c gutenberg -n "__fish_using_command gutenberg serve" -s h -l help -d 'Prints help information' | ||||
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s V -l version -d 'Prints version information' | complete -c gutenberg -n "__fish_using_command gutenberg serve" -s V -l version -d 'Prints version information' | ||||
complete -c gutenberg -n "__fish_using_command gutenberg help" -s h -l help -d 'Prints help information' | complete -c gutenberg -n "__fish_using_command gutenberg help" -s h -l help -d 'Prints help information' | ||||
@@ -38,7 +38,7 @@ pub struct Config { | |||||
/// Description of the site | /// Description of the site | ||||
pub description: Option<String>, | pub description: Option<String>, | ||||
/// The language used in the site. Defaults to "en" | /// The language used in the site. Defaults to "en" | ||||
pub language_code: Option<String>, | |||||
pub default_language: Option<String>, | |||||
/// Whether to generate RSS. Defaults to false | /// Whether to generate RSS. Defaults to false | ||||
pub generate_rss: Option<bool>, | pub generate_rss: Option<bool>, | ||||
/// The number of articles to include in the RSS feed. Defaults to unlimited | /// The number of articles to include in the RSS feed. Defaults to unlimited | ||||
@@ -50,6 +50,9 @@ pub struct Config { | |||||
/// Whether to compile the `sass` directory and output the css files into the static folder | /// Whether to compile the `sass` directory and output the css files into the static folder | ||||
pub compile_sass: Option<bool>, | pub compile_sass: Option<bool>, | ||||
/// Languages list and translated strings | |||||
pub translations: Option<HashMap<String, Toml>>, | |||||
/// All user params set in [extra] in the config | /// All user params set in [extra] in the config | ||||
pub extra: Option<HashMap<String, Toml>>, | pub extra: Option<HashMap<String, Toml>>, | ||||
@@ -74,13 +77,14 @@ impl Config { | |||||
Err(e) => bail!(e) | Err(e) => bail!(e) | ||||
}; | }; | ||||
set_default!(config.language_code, "en".to_string()); | |||||
set_default!(config.default_language, "en".to_string()); | |||||
set_default!(config.highlight_code, false); | set_default!(config.highlight_code, false); | ||||
set_default!(config.generate_rss, false); | set_default!(config.generate_rss, false); | ||||
set_default!(config.rss_limit, 20); | set_default!(config.rss_limit, 20); | ||||
set_default!(config.generate_tags_pages, false); | set_default!(config.generate_tags_pages, false); | ||||
set_default!(config.generate_categories_pages, false); | set_default!(config.generate_categories_pages, false); | ||||
set_default!(config.compile_sass, false); | set_default!(config.compile_sass, false); | ||||
set_default!(config.translations, HashMap::new()); | |||||
set_default!(config.extra, HashMap::new()); | set_default!(config.extra, HashMap::new()); | ||||
match config.highlight_theme { | match config.highlight_theme { | ||||
@@ -120,6 +124,8 @@ impl Config { | |||||
format!("{}{}{}", self.base_url, &path[1..], trailing_bit) | format!("{}{}{}", self.base_url, &path[1..], trailing_bit) | ||||
} else if self.base_url.ends_with('/') { | } else if self.base_url.ends_with('/') { | ||||
format!("{}{}{}", self.base_url, path, trailing_bit) | format!("{}{}{}", self.base_url, path, trailing_bit) | ||||
} else if path.starts_with('/') { | |||||
format!("{}{}{}", self.base_url, path, trailing_bit) | |||||
} else { | } else { | ||||
format!("{}/{}{}", self.base_url, path, trailing_bit) | format!("{}/{}{}", self.base_url, path, trailing_bit) | ||||
} | } | ||||
@@ -164,12 +170,13 @@ impl Default for Config { | |||||
highlight_code: Some(true), | highlight_code: Some(true), | ||||
highlight_theme: Some("base16-ocean-dark".to_string()), | highlight_theme: Some("base16-ocean-dark".to_string()), | ||||
description: None, | description: None, | ||||
language_code: Some("en".to_string()), | |||||
default_language: Some("en".to_string()), | |||||
generate_rss: Some(false), | generate_rss: Some(false), | ||||
rss_limit: Some(10_000), | rss_limit: Some(10_000), | ||||
generate_tags_pages: Some(true), | generate_tags_pages: Some(true), | ||||
generate_categories_pages: Some(true), | generate_categories_pages: Some(true), | ||||
compile_sass: Some(false), | compile_sass: Some(false), | ||||
translations: None, | |||||
extra: None, | extra: None, | ||||
build_timestamp: Some(1), | build_timestamp: Some(1), | ||||
} | } | ||||
@@ -272,6 +279,13 @@ hello = "world" | |||||
assert_eq!(config.make_permalink("/hello"), "http://vincent.is/hello/"); | assert_eq!(config.make_permalink("/hello"), "http://vincent.is/hello/"); | ||||
} | } | ||||
#[test] | |||||
fn can_make_url_with_localhost() { | |||||
let mut config = Config::default(); | |||||
config.base_url = "http://127.0.0.1:1111".to_string(); | |||||
assert_eq!(config.make_permalink("/tags/rust"), "http://127.0.0.1:1111/tags/rust/"); | |||||
} | |||||
#[test] | #[test] | ||||
fn can_merge_with_theme_data_and_preserve_config_value() { | fn can_merge_with_theme_data_and_preserve_config_value() { | ||||
let config_str = r#" | let config_str = r#" | ||||
@@ -293,4 +307,27 @@ a_value = 10 | |||||
assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string()); | assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string()); | ||||
assert_eq!(extra["a_value"].as_integer().unwrap(), 10); | assert_eq!(extra["a_value"].as_integer().unwrap(), 10); | ||||
} | } | ||||
#[test] | |||||
fn can_use_language_configuration() { | |||||
let config = r#" | |||||
base_url = "https://remplace-par-ton-url.fr" | |||||
default_language = "fr" | |||||
[translations] | |||||
[translations.fr] | |||||
title = "Un titre" | |||||
[translations.en] | |||||
title = "A title" | |||||
"#; | |||||
let config = Config::parse(config); | |||||
assert!(config.is_ok()); | |||||
let translations = config.unwrap().translations.unwrap(); | |||||
assert_eq!(translations["fr"]["title"].as_str().unwrap(), "Un titre"); | |||||
assert_eq!(translations["en"]["title"].as_str().unwrap(), "A title"); | |||||
} | |||||
} | } |
@@ -4,10 +4,10 @@ version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | ||||
[dependencies] | [dependencies] | ||||
tera = "0.10" | |||||
tera = "0.11.0" | |||||
serde = "1.0" | serde = "1.0" | ||||
slug = "0.1" | slug = "0.1" | ||||
rayon = "0.8" | |||||
rayon = "0.9" | |||||
errors = { path = "../errors" } | errors = { path = "../errors" } | ||||
config = { path = "../config" } | config = { path = "../config" } | ||||
@@ -17,3 +17,4 @@ front_matter = { path = "../front_matter" } | |||||
[dev-dependencies] | [dev-dependencies] | ||||
tempdir = "0.3" | tempdir = "0.3" | ||||
toml = "0.4" |
@@ -8,7 +8,7 @@ pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> { | |||||
let mut components = vec![]; | let mut components = vec![]; | ||||
for section in path.parent().unwrap().components() { | for section in path.parent().unwrap().components() { | ||||
let component = section.as_ref().to_string_lossy(); | |||||
let component = section.as_os_str().to_string_lossy(); | |||||
if is_in_content { | if is_in_content { | ||||
components.push(component.to_string()); | components.push(component.to_string()); | ||||
@@ -11,6 +11,8 @@ extern crate utils; | |||||
#[cfg(test)] | #[cfg(test)] | ||||
extern crate tempdir; | extern crate tempdir; | ||||
#[cfg(test)] | |||||
extern crate toml; | |||||
mod file_info; | mod file_info; | ||||
mod page; | mod page; | ||||
@@ -103,7 +103,6 @@ impl Page { | |||||
if let Some(ref p) = page.meta.path { | if let Some(ref p) = page.meta.path { | ||||
page.path = p.trim().trim_left_matches('/').to_string(); | page.path = p.trim().trim_left_matches('/').to_string(); | ||||
} else { | } else { | ||||
page.path = if page.file.components.is_empty() { | page.path = if page.file.components.is_empty() { | ||||
page.slug.clone() | page.slug.clone() | ||||
@@ -207,7 +206,12 @@ impl ser::Serialize for Page { | |||||
state.serialize_field("content", &self.content)?; | state.serialize_field("content", &self.content)?; | ||||
state.serialize_field("title", &self.meta.title)?; | state.serialize_field("title", &self.meta.title)?; | ||||
state.serialize_field("description", &self.meta.description)?; | state.serialize_field("description", &self.meta.description)?; | ||||
state.serialize_field("date", &self.meta.date)?; | |||||
// From a TOML datetime to a String first | |||||
let date = match self.meta.date { | |||||
Some(ref d) => Some(d.to_string()), | |||||
None => None, | |||||
}; | |||||
state.serialize_field("date", &date)?; | |||||
state.serialize_field("slug", &self.slug)?; | state.serialize_field("slug", &self.slug)?; | ||||
state.serialize_field("path", &self.path)?; | state.serialize_field("path", &self.path)?; | ||||
state.serialize_field("components", &self.components)?; | state.serialize_field("components", &self.components)?; | ||||
@@ -98,13 +98,16 @@ pub fn populate_previous_and_next_pages(input: &[Page]) -> Vec<Page> { | |||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use std::str::FromStr; | |||||
use toml::value::Datetime; | |||||
use front_matter::{PageFrontMatter, SortBy}; | use front_matter::{PageFrontMatter, SortBy}; | ||||
use page::Page; | use page::Page; | ||||
use super::{sort_pages, populate_previous_and_next_pages}; | use super::{sort_pages, populate_previous_and_next_pages}; | ||||
fn create_page_with_date(date: &str) -> Page { | fn create_page_with_date(date: &str) -> Page { | ||||
let mut front_matter = PageFrontMatter::default(); | let mut front_matter = PageFrontMatter::default(); | ||||
front_matter.date = Some(date.to_string()); | |||||
front_matter.date = Some(Datetime::from_str(date).unwrap()); | |||||
Page::new("content/hello.md", front_matter) | Page::new("content/hello.md", front_matter) | ||||
} | } | ||||
@@ -136,9 +139,9 @@ mod tests { | |||||
]; | ]; | ||||
let (pages, _) = sort_pages(input, SortBy::Date); | let (pages, _) = sort_pages(input, SortBy::Date); | ||||
// Should be sorted by date | // Should be sorted by date | ||||
assert_eq!(pages[0].clone().meta.date.unwrap(), "2019-01-01"); | |||||
assert_eq!(pages[1].clone().meta.date.unwrap(), "2018-01-01"); | |||||
assert_eq!(pages[2].clone().meta.date.unwrap(), "2017-01-01"); | |||||
assert_eq!(pages[0].clone().meta.date.unwrap().to_string(), "2019-01-01"); | |||||
assert_eq!(pages[1].clone().meta.date.unwrap().to_string(), "2018-01-01"); | |||||
assert_eq!(pages[2].clone().meta.date.unwrap().to_string(), "2017-01-01"); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -4,6 +4,6 @@ version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | ||||
[dependencies] | [dependencies] | ||||
error-chain = "0.10" | |||||
tera = "0.10" | |||||
error-chain = "0.11" | |||||
tera = "0.11.0" | |||||
toml = "0.4" | toml = "0.4" |
@@ -4,13 +4,13 @@ version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | ||||
[dependencies] | [dependencies] | ||||
tera = "0.10" | |||||
tera = "0.11.0" | |||||
chrono = "0.4" | chrono = "0.4" | ||||
serde = "1.0" | serde = "1.0" | ||||
serde_derive = "1.0" | serde_derive = "1.0" | ||||
toml = "0.4" | toml = "0.4" | ||||
regex = "0.2" | regex = "0.2" | ||||
lazy_static = "0.2" | |||||
lazy_static = "1" | |||||
errors = { path = "../errors" } | errors = { path = "../errors" } |
@@ -92,7 +92,7 @@ mod tests { | |||||
+++ | +++ | ||||
title = "Title" | title = "Title" | ||||
description = "hey there" | description = "hey there" | ||||
date = "2002/10/12" | |||||
date = 2002-10-12 | |||||
+++ | +++ | ||||
Hello | Hello | ||||
"#; | "#; | ||||
@@ -120,7 +120,7 @@ Hello | |||||
+++ | +++ | ||||
title = "Title" | title = "Title" | ||||
description = "hey there" | description = "hey there" | ||||
date = "2002/10/12" | |||||
date = 2002-10-12 | |||||
+++"#; | +++"#; | ||||
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap(); | let (front_matter, content) = split_page_content(Path::new(""), content).unwrap(); | ||||
assert_eq!(content, ""); | assert_eq!(content, ""); | ||||
@@ -133,7 +133,7 @@ date = "2002/10/12" | |||||
+++ | +++ | ||||
title = "Title" | title = "Title" | ||||
description = "hey there" | description = "hey there" | ||||
date = "2002-10-02T15:00:00Z" | |||||
date = 2002-10-02T15:00:00Z | |||||
+++ | +++ | ||||
+++"#; | +++"#; | ||||
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap(); | let (front_matter, content) = split_page_content(Path::new(""), content).unwrap(); | ||||
@@ -147,7 +147,7 @@ date = "2002-10-02T15:00:00Z" | |||||
+++ | +++ | ||||
title = "Title" | title = "Title" | ||||
description = "hey there" | description = "hey there" | ||||
date = "2002/10/12""#; | |||||
date = 2002-10-12"#; | |||||
let res = split_page_content(Path::new(""), content); | let res = split_page_content(Path::new(""), content); | ||||
assert!(res.is_err()); | assert!(res.is_err()); | ||||
} | } | ||||
@@ -6,6 +6,7 @@ use toml; | |||||
use errors::Result; | use errors::Result; | ||||
/// The front matter of every page | /// The front matter of every page | ||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||||
pub struct PageFrontMatter { | pub struct PageFrontMatter { | ||||
@@ -14,7 +15,7 @@ pub struct PageFrontMatter { | |||||
/// Description in <meta> that appears when linked, e.g. on twitter | /// Description in <meta> that appears when linked, e.g. on twitter | ||||
pub description: Option<String>, | pub description: Option<String>, | ||||
/// Date if we want to order pages (ie blog post) | /// Date if we want to order pages (ie blog post) | ||||
pub date: Option<String>, | |||||
pub date: Option<toml::value::Datetime>, | |||||
/// Whether this page is a draft and should be ignored for pagination etc | /// Whether this page is a draft and should be ignored for pagination etc | ||||
pub draft: Option<bool>, | pub draft: Option<bool>, | ||||
/// The page slug. Will be used instead of the filename if present | /// The page slug. Will be used instead of the filename if present | ||||
@@ -71,17 +72,17 @@ impl PageFrontMatter { | |||||
Ok(f) | Ok(f) | ||||
} | } | ||||
/// Converts the date in the front matter, which can be in 2 formats, into a NaiveDateTime | |||||
/// Converts the TOML datetime to a Chrono naive datetime | |||||
pub fn date(&self) -> Option<NaiveDateTime> { | pub fn date(&self) -> Option<NaiveDateTime> { | ||||
match self.date { | |||||
Some(ref d) => { | |||||
if d.contains('T') { | |||||
DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local())) | |||||
} else { | |||||
NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0))) | |||||
} | |||||
}, | |||||
None => None, | |||||
if let Some(ref d) = self.date { | |||||
let d2 = d.to_string(); | |||||
if d2.contains('T') { | |||||
DateTime::parse_from_rfc3339(&d2).ok().and_then(|s| Some(s.naive_local())) | |||||
} else { | |||||
NaiveDate::parse_from_str(&d2, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0, 0, 0))) | |||||
} | |||||
} else { | |||||
None | |||||
} | } | ||||
} | } | ||||
@@ -121,6 +122,7 @@ impl Default for PageFrontMatter { | |||||
} | } | ||||
} | } | ||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use super::PageFrontMatter; | use super::PageFrontMatter; | ||||
@@ -203,9 +205,10 @@ mod tests { | |||||
let content = r#" | let content = r#" | ||||
title = "Hello" | title = "Hello" | ||||
description = "hey there" | description = "hey there" | ||||
date = "2016-10-10""#; | |||||
date = 2016-10-10 | |||||
"#; | |||||
let res = PageFrontMatter::parse(content).unwrap(); | let res = PageFrontMatter::parse(content).unwrap(); | ||||
assert!(res.date().is_some()); | |||||
assert!(res.date.is_some()); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -213,9 +216,10 @@ mod tests { | |||||
let content = r#" | let content = r#" | ||||
title = "Hello" | title = "Hello" | ||||
description = "hey there" | description = "hey there" | ||||
date = "2002-10-02T15:00:00Z""#; | |||||
date = 2002-10-02T15:00:00Z | |||||
"#; | |||||
let res = PageFrontMatter::parse(content).unwrap(); | let res = PageFrontMatter::parse(content).unwrap(); | ||||
assert!(res.date().is_some()); | |||||
assert!(res.date.is_some()); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -223,9 +227,28 @@ mod tests { | |||||
let content = r#" | let content = r#" | ||||
title = "Hello" | title = "Hello" | ||||
description = "hey there" | description = "hey there" | ||||
date = "2002/10/12""#; | |||||
let res = PageFrontMatter::parse(content).unwrap(); | |||||
assert!(res.date().is_none()); | |||||
date = 2002/10/12"#; | |||||
let res = PageFrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | } | ||||
#[test] | |||||
fn cannot_parse_invalid_date_format() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
date = 2002-14-01"#; | |||||
let res = PageFrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
#[test] | |||||
fn cannot_parse_date_as_string() { | |||||
let content = r#" | |||||
title = "Hello" | |||||
description = "hey there" | |||||
date = "2002-14-01""#; | |||||
let res = PageFrontMatter::parse(content); | |||||
assert!(res.is_err()); | |||||
} | |||||
} | } |
@@ -4,5 +4,5 @@ version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | ||||
[dependencies] | [dependencies] | ||||
lazy_static = "0.2" | |||||
syntect = { version = "1", features = ["static-onig"] } | |||||
lazy_static = "1" | |||||
syntect = "2" |
@@ -4,7 +4,7 @@ version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | ||||
[dependencies] | [dependencies] | ||||
tera = "0.10" | |||||
tera = "0.11.0" | |||||
serde = "1.0" | serde = "1.0" | ||||
serde_derive = "1.0" | serde_derive = "1.0" | ||||
@@ -4,10 +4,10 @@ version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | ||||
[dependencies] | [dependencies] | ||||
tera = "0.10" | |||||
tera = "0.11.0" | |||||
regex = "0.2" | regex = "0.2" | ||||
lazy_static = "0.2" | |||||
syntect = { version = "1", features = ["static-onig"] } | |||||
lazy_static = "1" | |||||
syntect = "2" | |||||
pulldown-cmark = "0" | pulldown-cmark = "0" | ||||
slug = "0.1" | slug = "0.1" | ||||
serde = "1.0" | serde = "1.0" | ||||
@@ -54,7 +54,7 @@ fn can_highlight_code_block_with_lang() { | |||||
let res = markdown_to_html("```python\nlist.append(1)\n```", &context).unwrap(); | let res = markdown_to_html("```python\nlist.append(1)\n```", &context).unwrap(); | ||||
assert_eq!( | assert_eq!( | ||||
res.0, | res.0, | ||||
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">\n</span></pre>" | |||||
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)\n</span></pre>" | |||||
); | ); | ||||
} | } | ||||
@@ -4,14 +4,13 @@ version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | ||||
[dependencies] | [dependencies] | ||||
tera = "0.10" | |||||
tera = "0.11.0" | |||||
glob = "0.2" | glob = "0.2" | ||||
walkdir = "2" | walkdir = "2" | ||||
rayon = "0.8" | |||||
rayon = "0.9" | |||||
serde = "1.0" | serde = "1.0" | ||||
serde_derive = "1.0" | serde_derive = "1.0" | ||||
sass-rs = "0.2" | sass-rs = "0.2" | ||||
#sass-rs = { git = "https://github.com/compass-rs/sass-rs.git" } | |||||
errors = { path = "../errors" } | errors = { path = "../errors" } | ||||
config = { path = "../config" } | config = { path = "../config" } | ||||
@@ -151,8 +151,6 @@ impl Site { | |||||
orphans | orphans | ||||
} | } | ||||
/// Used by tests to change the output path to a tmp dir | |||||
#[doc(hidden)] | |||||
pub fn set_output_path<P: AsRef<Path>>(&mut self, path: P) { | pub fn set_output_path<P: AsRef<Path>>(&mut self, path: P) { | ||||
self.output_path = path.as_ref().to_path_buf(); | self.output_path = path.as_ref().to_path_buf(); | ||||
} | } | ||||
@@ -219,38 +217,55 @@ impl Site { | |||||
self.add_page(p, false)?; | self.add_page(p, false)?; | ||||
} | } | ||||
{ | |||||
// Another silly thing needed to not borrow &self in parallel and | |||||
// make the borrow checker happy | |||||
let permalinks = &self.permalinks; | |||||
let tera = &self.tera; | |||||
let config = &self.config; | |||||
self.render_markdown()?; | |||||
self.populate_sections(); | |||||
self.populate_tags_and_categories(); | |||||
self.pages.par_iter_mut() | |||||
.map(|(_, page)| { | |||||
let insert_anchor = pages_insert_anchors[&page.file.path]; | |||||
page.render_markdown(permalinks, tera, config, insert_anchor) | |||||
}) | |||||
.fold(|| Ok(()), Result::and) | |||||
.reduce(|| Ok(()), Result::and)?; | |||||
self.register_tera_global_fns(); | |||||
self.sections.par_iter_mut() | |||||
.map(|(_, section)| section.render_markdown(permalinks, tera, config)) | |||||
.fold(|| Ok(()), Result::and) | |||||
.reduce(|| Ok(()), Result::and)?; | |||||
Ok(()) | |||||
} | |||||
/// Render the markdown of all pages/sections | |||||
/// Used in a build and in `serve` if a shortcode has changed | |||||
pub fn render_markdown(&mut self) -> Result<()> { | |||||
// Another silly thing needed to not borrow &self in parallel and | |||||
// make the borrow checker happy | |||||
let permalinks = &self.permalinks; | |||||
let tera = &self.tera; | |||||
let config = &self.config; | |||||
// TODO: avoid the duplication with function above for that part | |||||
// This is needed in the first place because of silly borrow checker | |||||
let mut pages_insert_anchors = HashMap::new(); | |||||
for (_, p) in &self.pages { | |||||
pages_insert_anchors.insert(p.file.path.clone(), self.find_parent_section_insert_anchor(&p.file.parent.clone())); | |||||
} | } | ||||
self.populate_sections(); | |||||
self.populate_tags_and_categories(); | |||||
self.pages.par_iter_mut() | |||||
.map(|(_, page)| { | |||||
let insert_anchor = pages_insert_anchors[&page.file.path]; | |||||
page.render_markdown(permalinks, tera, config, insert_anchor) | |||||
}) | |||||
.fold(|| Ok(()), Result::and) | |||||
.reduce(|| Ok(()), Result::and)?; | |||||
self.register_tera_global_fns(); | |||||
self.sections.par_iter_mut() | |||||
.map(|(_, section)| section.render_markdown(permalinks, tera, config)) | |||||
.fold(|| Ok(()), Result::and) | |||||
.reduce(|| Ok(()), Result::and)?; | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
pub fn register_tera_global_fns(&mut self) { | pub fn register_tera_global_fns(&mut self) { | ||||
self.tera.register_global_function("trans", global_fns::make_trans(self.config.clone())); | |||||
self.tera.register_global_function("get_page", global_fns::make_get_page(&self.pages)); | self.tera.register_global_function("get_page", global_fns::make_get_page(&self.pages)); | ||||
self.tera.register_global_function("get_section", global_fns::make_get_section(&self.sections)); | self.tera.register_global_function("get_section", global_fns::make_get_section(&self.sections)); | ||||
self.tera.register_global_function( | |||||
"get_taxonomy_url", | |||||
global_fns::make_get_taxonomy_url(self.tags.clone(), self.categories.clone()) | |||||
); | |||||
self.tera.register_global_function( | self.tera.register_global_function( | ||||
"get_url", | "get_url", | ||||
global_fns::make_get_url(self.permalinks.clone(), self.config.clone()) | global_fns::make_get_url(self.permalinks.clone(), self.config.clone()) | ||||
@@ -318,6 +333,8 @@ impl Site { | |||||
section.ignored_pages = vec![]; | section.ignored_pages = vec![]; | ||||
} | } | ||||
// TODO: use references instead of cloning to avoid having to call populate_section on | |||||
// content change | |||||
for page in self.pages.values() { | for page in self.pages.values() { | ||||
let parent_section_path = page.file.parent.join("_index.md"); | let parent_section_path = page.file.parent.join("_index.md"); | ||||
if self.sections.contains_key(&parent_section_path) { | if self.sections.contains_key(&parent_section_path) { | ||||
@@ -443,7 +460,7 @@ impl Site { | |||||
pub fn clean(&self) -> Result<()> { | pub fn clean(&self) -> Result<()> { | ||||
if self.output_path.exists() { | if self.output_path.exists() { | ||||
// Delete current `public` directory so we can start fresh | // Delete current `public` directory so we can start fresh | ||||
remove_dir_all(&self.output_path).chain_err(|| "Couldn't delete `public` directory")?; | |||||
remove_dir_all(&self.output_path).chain_err(|| "Couldn't delete output directory")?; | |||||
} | } | ||||
Ok(()) | Ok(()) | ||||
@@ -620,7 +637,13 @@ impl Site { | |||||
&self.pages | &self.pages | ||||
.values() | .values() | ||||
.filter(|p| !p.is_draft()) | .filter(|p| !p.is_draft()) | ||||
.map(|p| SitemapEntry::new(p.permalink.clone(), p.meta.date.clone())) | |||||
.map(|p| { | |||||
let date = match p.meta.date { | |||||
Some(ref d) => Some(d.to_string()), | |||||
None => None, | |||||
}; | |||||
SitemapEntry::new(p.permalink.clone(), date) | |||||
}) | |||||
.collect::<Vec<_>>() | .collect::<Vec<_>>() | ||||
); | ); | ||||
context.add( | context.add( | ||||
@@ -678,7 +701,7 @@ impl Site { | |||||
} | } | ||||
let (sorted_pages, _) = sort_pages(pages, SortBy::Date); | let (sorted_pages, _) = sort_pages(pages, SortBy::Date); | ||||
context.add("last_build_date", &sorted_pages[0].meta.date); | |||||
context.add("last_build_date", &sorted_pages[0].meta.date.clone().map(|d| d.to_string())); | |||||
// limit to the last n elements) | // limit to the last n elements) | ||||
context.add("pages", &sorted_pages.iter().take(self.config.rss_limit.unwrap()).collect::<Vec<_>>()); | context.add("pages", &sorted_pages.iter().take(self.config.rss_limit.unwrap()).collect::<Vec<_>>()); | ||||
context.add("config", &self.config); | context.add("config", &self.config); | ||||
@@ -1,6 +1,6 @@ | |||||
+++ | +++ | ||||
title = "A draft" | title = "A draft" | ||||
draft = true | draft = true | ||||
date = "2016-03-01" | |||||
date = 2016-03-01 | |||||
+++ | +++ | ||||
@@ -2,7 +2,7 @@ | |||||
title = "Fixed slug" | title = "Fixed slug" | ||||
description = "" | description = "" | ||||
slug = "something-else" | slug = "something-else" | ||||
date = "2017-01-01" | |||||
date = 2017-01-01 | |||||
aliases = ["/an-old-url/old-page"] | aliases = ["/an-old-url/old-page"] | ||||
+++ | +++ | ||||
@@ -2,7 +2,7 @@ | |||||
title = "Fixed URL" | title = "Fixed URL" | ||||
description = "" | description = "" | ||||
path = "a-fixed-url" | path = "a-fixed-url" | ||||
date = "2017-02-01" | |||||
date = 2017-02-01 | |||||
+++ | +++ | ||||
A simple page with fixed url | A simple page with fixed url |
@@ -1,7 +1,7 @@ | |||||
+++ | +++ | ||||
title = "Python in posts" | title = "Python in posts" | ||||
description = "" | description = "" | ||||
date = "2017-03-01" | |||||
date = 2017-03-01 | |||||
+++ | +++ | ||||
Same filename but different path | Same filename but different path | ||||
@@ -1,7 +1,7 @@ | |||||
+++ | +++ | ||||
title = "Simple article with shortcodes" | title = "Simple article with shortcodes" | ||||
description = "" | description = "" | ||||
date = "2017-04-01" | |||||
date = 2017-04-01 | |||||
+++ | +++ | ||||
A simple page | A simple page | ||||
@@ -1,7 +1,7 @@ | |||||
+++ | +++ | ||||
title = "Docker" | title = "Docker" | ||||
order = 1 | order = 1 | ||||
date = "2017-01-01" | |||||
date = 2017-01-01 | |||||
+++ | +++ | ||||
A simple page | A simple page |
@@ -1,7 +1,7 @@ | |||||
+++ | +++ | ||||
title = "Nix" | title = "Nix" | ||||
order = 2 | order = 2 | ||||
date = "2017-01-01" | |||||
date = 2017-01-01 | |||||
+++ | +++ | ||||
A simple page | A simple page |
@@ -1,7 +1,7 @@ | |||||
+++ | +++ | ||||
title = "Python tutorial" | title = "Python tutorial" | ||||
order = 1 | order = 1 | ||||
date = "2017-01-01" | |||||
date = 2017-01-01 | |||||
+++ | +++ | ||||
A simple page | A simple page |
@@ -1,7 +1,7 @@ | |||||
+++ | +++ | ||||
title = "Rust" | title = "Rust" | ||||
order = 2 | order = 2 | ||||
date = "2017-01-01" | |||||
date = 2017-01-01 | |||||
+++ | +++ | ||||
A simple page | A simple page |
@@ -1,5 +1,5 @@ | |||||
<!DOCTYPE html> | <!DOCTYPE html> | ||||
<html lang="{{ config.language_code }}"> | |||||
<html lang="en"> | |||||
<head> | <head> | ||||
<meta charset="UTF-8"> | <meta charset="UTF-8"> | ||||
<meta name="apple-mobile-web-app-capable" content="yes"> | <meta name="apple-mobile-web-app-capable" content="yes"> | ||||
@@ -1,5 +1,5 @@ | |||||
<!DOCTYPE html> | <!DOCTYPE html> | ||||
<html lang="{{ config.language_code }}"> | |||||
<html lang="en"> | |||||
<head> | <head> | ||||
<meta charset="UTF-8"> | <meta charset="UTF-8"> | ||||
<meta name="apple-mobile-web-app-capable" content="yes"> | <meta name="apple-mobile-web-app-capable" content="yes"> | ||||
@@ -4,7 +4,7 @@ version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | ||||
[dependencies] | [dependencies] | ||||
tera = "0.10" | |||||
tera = "0.11.0" | |||||
slug = "0.1" | slug = "0.1" | ||||
serde = "1.0" | serde = "1.0" | ||||
serde_derive = "1.0" | serde_derive = "1.0" | ||||
@@ -44,7 +44,7 @@ impl TaxonomyItem { | |||||
let (mut pages, ignored_pages) = sort_pages(pages, SortBy::Date); | let (mut pages, ignored_pages) = sort_pages(pages, SortBy::Date); | ||||
let slug = slugify(name); | let slug = slugify(name); | ||||
let permalink = { | let permalink = { | ||||
let kind_path = if kind == TaxonomyKind::Tags { "tag" } else { "category" }; | |||||
let kind_path = if kind == TaxonomyKind::Tags { "tags" } else { "categories" }; | |||||
config.make_permalink(&format!("/{}/{}", kind_path, slug)) | config.make_permalink(&format!("/{}/{}", kind_path, slug)) | ||||
}; | }; | ||||
@@ -188,27 +188,27 @@ mod tests { | |||||
assert_eq!(tags.items[0].name, "db"); | assert_eq!(tags.items[0].name, "db"); | ||||
assert_eq!(tags.items[0].slug, "db"); | assert_eq!(tags.items[0].slug, "db"); | ||||
assert_eq!(tags.items[0].permalink, "http://a-website.com/tag/db/"); | |||||
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/"); | |||||
assert_eq!(tags.items[0].pages.len(), 1); | assert_eq!(tags.items[0].pages.len(), 1); | ||||
assert_eq!(tags.items[1].name, "js"); | assert_eq!(tags.items[1].name, "js"); | ||||
assert_eq!(tags.items[1].slug, "js"); | assert_eq!(tags.items[1].slug, "js"); | ||||
assert_eq!(tags.items[1].permalink, "http://a-website.com/tag/js/"); | |||||
assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/js/"); | |||||
assert_eq!(tags.items[1].pages.len(), 2); | assert_eq!(tags.items[1].pages.len(), 2); | ||||
assert_eq!(tags.items[2].name, "rust"); | assert_eq!(tags.items[2].name, "rust"); | ||||
assert_eq!(tags.items[2].slug, "rust"); | assert_eq!(tags.items[2].slug, "rust"); | ||||
assert_eq!(tags.items[2].permalink, "http://a-website.com/tag/rust/"); | |||||
assert_eq!(tags.items[2].permalink, "http://a-website.com/tags/rust/"); | |||||
assert_eq!(tags.items[2].pages.len(), 2); | assert_eq!(tags.items[2].pages.len(), 2); | ||||
assert_eq!(categories.items[0].name, "Other"); | assert_eq!(categories.items[0].name, "Other"); | ||||
assert_eq!(categories.items[0].slug, "other"); | assert_eq!(categories.items[0].slug, "other"); | ||||
assert_eq!(categories.items[0].permalink, "http://a-website.com/category/other/"); | |||||
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/other/"); | |||||
assert_eq!(categories.items[0].pages.len(), 1); | assert_eq!(categories.items[0].pages.len(), 1); | ||||
assert_eq!(categories.items[1].name, "Programming tutorials"); | assert_eq!(categories.items[1].name, "Programming tutorials"); | ||||
assert_eq!(categories.items[1].slug, "programming-tutorials"); | assert_eq!(categories.items[1].slug, "programming-tutorials"); | ||||
assert_eq!(categories.items[1].permalink, "http://a-website.com/category/programming-tutorials/"); | |||||
assert_eq!(categories.items[1].permalink, "http://a-website.com/categories/programming-tutorials/"); | |||||
assert_eq!(categories.items[1].pages.len(), 1); | assert_eq!(categories.items[1].pages.len(), 1); | ||||
} | } | ||||
} | } |
@@ -4,12 +4,13 @@ version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | ||||
[dependencies] | [dependencies] | ||||
tera = "0.10" | |||||
base64 = "0.7" | |||||
lazy_static = "0.2" | |||||
tera = "0.11.0" | |||||
base64 = "0.9" | |||||
lazy_static = "1" | |||||
pulldown-cmark = "0" | pulldown-cmark = "0" | ||||
errors = { path = "../errors" } | errors = { path = "../errors" } | ||||
utils = { path = "../utils" } | utils = { path = "../utils" } | ||||
content = { path = "../content" } | content = { path = "../content" } | ||||
config = { path = "../config" } | config = { path = "../config" } | ||||
taxonomies = { path = "../taxonomies" } |
@@ -4,7 +4,7 @@ | |||||
<link>{{ config.base_url }}</link> | <link>{{ config.base_url }}</link> | ||||
<description>{{ config.description }}</description> | <description>{{ config.description }}</description> | ||||
<generator>Gutenberg</generator> | <generator>Gutenberg</generator> | ||||
<language>{{ config.language_code }}</language> | |||||
<language>{{ config.default_language }}</language> | |||||
<atom:link href="{{ feed_url }}" rel="self" type="application/rss+xml"/> | <atom:link href="{{ feed_url }}" rel="self" type="application/rss+xml"/> | ||||
<lastBuildDate>{{ last_build_date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate> | <lastBuildDate>{{ last_build_date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate> | ||||
{% for page in pages %} | {% for page in pages %} | ||||
@@ -6,6 +6,34 @@ use tera::{GlobalFn, Value, from_value, to_value, Result}; | |||||
use content::{Page, Section}; | use content::{Page, Section}; | ||||
use config::Config; | use config::Config; | ||||
use utils::site::resolve_internal_link; | use utils::site::resolve_internal_link; | ||||
use taxonomies::Taxonomy; | |||||
macro_rules! required_string_arg { | |||||
($e: expr, $err: expr) => { | |||||
match $e { | |||||
Some(v) => match from_value::<String>(v.clone()) { | |||||
Ok(u) => u, | |||||
Err(_) => return Err($err.into()) | |||||
}, | |||||
None => return Err($err.into()) | |||||
}; | |||||
}; | |||||
} | |||||
pub fn make_trans(config: Config) -> GlobalFn { | |||||
let translations_config = config.translations.unwrap(); | |||||
let default_lang = to_value(config.default_language.unwrap()).unwrap(); | |||||
Box::new(move |args| -> Result<Value> { | |||||
let key = required_string_arg!(args.get("key"), "`trans` requires a `key` argument."); | |||||
let lang_arg = args.get("lang").unwrap_or(&default_lang).clone(); | |||||
let lang = from_value::<String>(lang_arg).unwrap(); | |||||
let translations = &translations_config[lang.as_str()]; | |||||
Ok(to_value(&translations[key.as_str()]).unwrap()) | |||||
}) | |||||
} | |||||
pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn { | pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn { | ||||
@@ -15,17 +43,10 @@ pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn { | |||||
} | } | ||||
Box::new(move |args| -> Result<Value> { | Box::new(move |args| -> Result<Value> { | ||||
match args.get("path") { | |||||
Some(val) => match from_value::<String>(val.clone()) { | |||||
Ok(v) => { | |||||
match pages.get(&v) { | |||||
Some(p) => Ok(to_value(p).unwrap()), | |||||
None => Err(format!("Page `{}` not found.", v).into()) | |||||
} | |||||
}, | |||||
Err(_) => Err(format!("`get_page` received path={:?} but it requires a string", val).into()), | |||||
}, | |||||
None => Err("`get_page` requires a `path` argument.".into()), | |||||
let path = required_string_arg!(args.get("path"), "`get_page` requires a `path` argument with a string value"); | |||||
match pages.get(&path) { | |||||
Some(p) => Ok(to_value(p).unwrap()), | |||||
None => Err(format!("Page `{}` not found.", path).into()) | |||||
} | } | ||||
}) | }) | ||||
} | } | ||||
@@ -37,17 +58,10 @@ pub fn make_get_section(all_sections: &HashMap<PathBuf, Section>) -> GlobalFn { | |||||
} | } | ||||
Box::new(move |args| -> Result<Value> { | Box::new(move |args| -> Result<Value> { | ||||
match args.get("path") { | |||||
Some(val) => match from_value::<String>(val.clone()) { | |||||
Ok(v) => { | |||||
match sections.get(&v) { | |||||
Some(p) => Ok(to_value(p).unwrap()), | |||||
None => Err(format!("Section `{}` not found.", v).into()) | |||||
} | |||||
}, | |||||
Err(_) => Err(format!("`get_section` received path={:?} but it requires a string", val).into()), | |||||
}, | |||||
None => Err("`get_section` requires a `path` argument.".into()), | |||||
let path = required_string_arg!(args.get("path"), "`get_section` requires a `path` argument with a string value"); | |||||
match sections.get(&path) { | |||||
Some(p) => Ok(to_value(p).unwrap()), | |||||
None => Err(format!("Section `{}` not found.", path).into()) | |||||
} | } | ||||
}) | }) | ||||
} | } | ||||
@@ -60,40 +74,66 @@ pub fn make_get_url(permalinks: HashMap<String, String>, config: Config) -> Glob | |||||
from_value::<bool>(c.clone()).unwrap_or(false) | from_value::<bool>(c.clone()).unwrap_or(false) | ||||
}); | }); | ||||
match args.get("path") { | |||||
Some(val) => match from_value::<String>(val.clone()) { | |||||
Ok(v) => { | |||||
// Internal link | |||||
if v.starts_with("./") { | |||||
match resolve_internal_link(&v, &permalinks) { | |||||
Ok(url) => Ok(to_value(url).unwrap()), | |||||
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", v).into()) | |||||
} | |||||
} else { | |||||
// anything else | |||||
let mut permalink = config.make_permalink(&v); | |||||
if cachebust { | |||||
permalink = format!("{}?t={}", permalink, config.build_timestamp.unwrap()); | |||||
} | |||||
Ok(to_value(permalink).unwrap()) | |||||
} | |||||
}, | |||||
Err(_) => Err(format!("`get_url` received path={:?} but it requires a string", val).into()), | |||||
}, | |||||
None => Err("`get_url` requires a `path` argument.".into()), | |||||
let trailing_slash = args | |||||
.get("trailing_slash") | |||||
.map_or(true, |c| { | |||||
from_value::<bool>(c.clone()).unwrap_or(true) | |||||
}); | |||||
let path = required_string_arg!(args.get("path"), "`get_url` requires a `path` argument with a string value"); | |||||
if path.starts_with("./") { | |||||
match resolve_internal_link(&path, &permalinks) { | |||||
Ok(url) => Ok(to_value(url).unwrap()), | |||||
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", path).into()) | |||||
} | |||||
} else { | |||||
// anything else | |||||
let mut permalink = config.make_permalink(&path); | |||||
if !trailing_slash && permalink.ends_with("/") { | |||||
permalink.pop(); // Removes the slash | |||||
} | |||||
if cachebust { | |||||
permalink = format!("{}?t={}", permalink, config.build_timestamp.unwrap()); | |||||
} | |||||
Ok(to_value(permalink).unwrap()) | |||||
} | |||||
}) | |||||
} | |||||
pub fn make_get_taxonomy_url(tags: Option<Taxonomy>, categories: Option<Taxonomy>) -> GlobalFn { | |||||
Box::new(move |args| -> Result<Value> { | |||||
let kind = required_string_arg!(args.get("kind"), "`get_taxonomy_url` requires a `kind` argument with a string value"); | |||||
let name = required_string_arg!(args.get("name"), "`get_taxonomy_url` requires a `name` argument with a string value"); | |||||
let container = match kind.as_ref() { | |||||
"tag" => &tags, | |||||
"category" => &categories, | |||||
_ => return Err("`get_taxonomy_url` can only get `tag` or `category` for the `kind` argument".into()), | |||||
}; | |||||
if let Some(ref c) = *container { | |||||
for item in &c.items { | |||||
if item.name == name { | |||||
return Ok(to_value(item.permalink.clone()).unwrap()); | |||||
} | |||||
} | |||||
bail!("`get_taxonomy_url`: couldn't find `{}` in `{}` taxonomy", name, kind); | |||||
} else { | |||||
bail!("`get_taxonomy_url` tried to get a taxonomy of kind `{}` but there isn't any", kind); | |||||
} | } | ||||
}) | }) | ||||
} | } | ||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use super::make_get_url; | |||||
use super::{make_get_url, make_get_taxonomy_url, make_trans}; | |||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use tera::to_value; | use tera::to_value; | ||||
use config::Config; | use config::Config; | ||||
use taxonomies::{Taxonomy, TaxonomyKind, TaxonomyItem}; | |||||
#[test] | #[test] | ||||
@@ -106,6 +146,27 @@ mod tests { | |||||
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css/?t=1"); | assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css/?t=1"); | ||||
} | } | ||||
#[test] | |||||
fn can_remove_trailing_slashes() { | |||||
let config = Config::default(); | |||||
let static_fn = make_get_url(HashMap::new(), config); | |||||
let mut args = HashMap::new(); | |||||
args.insert("path".to_string(), to_value("app.css").unwrap()); | |||||
args.insert("trailing_slash".to_string(), to_value(false).unwrap()); | |||||
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css"); | |||||
} | |||||
#[test] | |||||
fn can_remove_slashes_and_cachebust() { | |||||
let config = Config::default(); | |||||
let static_fn = make_get_url(HashMap::new(), config); | |||||
let mut args = HashMap::new(); | |||||
args.insert("path".to_string(), to_value("app.css").unwrap()); | |||||
args.insert("trailing_slash".to_string(), to_value(false).unwrap()); | |||||
args.insert("cachebust".to_string(), to_value(true).unwrap()); | |||||
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css?t=1"); | |||||
} | |||||
#[test] | #[test] | ||||
fn can_link_to_some_static_file() { | fn can_link_to_some_static_file() { | ||||
let config = Config::default(); | let config = Config::default(); | ||||
@@ -114,4 +175,59 @@ mod tests { | |||||
args.insert("path".to_string(), to_value("app.css").unwrap()); | args.insert("path".to_string(), to_value("app.css").unwrap()); | ||||
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css/"); | assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css/"); | ||||
} | } | ||||
#[test] | |||||
fn can_get_tag_url() { | |||||
let tag = TaxonomyItem::new( | |||||
"Prog amming", | |||||
TaxonomyKind::Tags, | |||||
&Config::default(), | |||||
vec![], | |||||
); | |||||
let tags = Taxonomy { | |||||
kind: TaxonomyKind::Tags, | |||||
items: vec![tag], | |||||
}; | |||||
let static_fn = make_get_taxonomy_url(Some(tags), None); | |||||
// can find it correctly | |||||
let mut args = HashMap::new(); | |||||
args.insert("kind".to_string(), to_value("tag").unwrap()); | |||||
args.insert("name".to_string(), to_value("Prog amming").unwrap()); | |||||
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/tags/prog-amming/"); | |||||
// and errors if it can't find it | |||||
let mut args = HashMap::new(); | |||||
args.insert("kind".to_string(), to_value("tag").unwrap()); | |||||
args.insert("name".to_string(), to_value("random").unwrap()); | |||||
assert!(static_fn(args).is_err()); | |||||
} | |||||
#[test] | |||||
fn can_translate_a_string() { | |||||
let trans_config = r#" | |||||
base_url = "https://remplace-par-ton-url.fr" | |||||
default_language = "fr" | |||||
[translations] | |||||
[translations.fr] | |||||
title = "Un titre" | |||||
[translations.en] | |||||
title = "A title" | |||||
"#; | |||||
let config = Config::parse(trans_config).unwrap(); | |||||
let static_fn = make_trans(config); | |||||
let mut args = HashMap::new(); | |||||
args.insert("key".to_string(), to_value("title").unwrap()); | |||||
assert_eq!(static_fn(args.clone()).unwrap(), "Un titre"); | |||||
args.insert("lang".to_string(), to_value("en").unwrap()); | |||||
assert_eq!(static_fn(args.clone()).unwrap(), "A title"); | |||||
args.insert("lang".to_string(), to_value("fr").unwrap()); | |||||
assert_eq!(static_fn(args.clone()).unwrap(), "Un titre"); | |||||
} | |||||
} | } |
@@ -5,10 +5,12 @@ extern crate tera; | |||||
extern crate base64; | extern crate base64; | ||||
extern crate pulldown_cmark; | extern crate pulldown_cmark; | ||||
#[macro_use] | |||||
extern crate errors; | extern crate errors; | ||||
extern crate utils; | extern crate utils; | ||||
extern crate content; | extern crate content; | ||||
extern crate config; | extern crate config; | ||||
extern crate taxonomies; | |||||
pub mod filters; | pub mod filters; | ||||
pub mod global_fns; | pub mod global_fns; | ||||
@@ -5,7 +5,7 @@ authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | |||||
[dependencies] | [dependencies] | ||||
errors = { path = "../errors" } | errors = { path = "../errors" } | ||||
tera = "0.10" | |||||
tera = "0.11.0" | |||||
[dev-dependencies] | [dev-dependencies] | ||||
@@ -0,0 +1,43 @@ | |||||
<html> | |||||
<head> | |||||
<title>Gutenberg</title> | |||||
</head> | |||||
<body> | |||||
<div class="container"> | |||||
<h1>Welcome to Gutenberg!</h1> | |||||
<p> | |||||
You're seeing this page because we couldn't find a template to render. | |||||
</p> | |||||
<p> | |||||
To modify this page, create a <b>{{filename}}</b> file in the templates directory or | |||||
<a href="https://www.getgutenberg.io/documentation/themes/installing-and-using-themes/" target="_blank">install a theme</a>. | |||||
<br> | |||||
You can find what variables are available in this template in the <a href="{{url}}" target="_blank">documentation</a>. | |||||
</p> | |||||
</div> | |||||
<footer> | |||||
<a href="https://www.getgutenberg.io/documentation/getting-started/cli-usage/" target="_blank">Get started with Gutenberg</a> | |||||
</footer> | |||||
<style> | |||||
html { | |||||
line-height: 1.5; | |||||
} | |||||
h1 { | |||||
margin-bottom: 2rem; | |||||
} | |||||
.container { | |||||
font-family: "sans-serif"; | |||||
text-align: center; | |||||
margin-top: 20vh; | |||||
padding: 2rem; | |||||
background: #e9e9e9; | |||||
} | |||||
footer { | |||||
position: fixed; | |||||
width: 100%; | |||||
bottom: 1rem; | |||||
text-align: center; | |||||
} | |||||
</style> | |||||
</body> | |||||
</html> |
@@ -2,6 +2,20 @@ use tera::{Tera, Context}; | |||||
use errors::Result; | use errors::Result; | ||||
static DEFAULT_TPL: &str = include_str!("default_tpl.html"); | |||||
macro_rules! render_default_tpl { | |||||
($filename: expr, $url: expr) => { | |||||
{ | |||||
let mut context = Context::new(); | |||||
context.add("filename", $filename); | |||||
context.add("url", $url); | |||||
Tera::one_off(DEFAULT_TPL, &context, true).map_err(|e| e.into()) | |||||
} | |||||
}; | |||||
} | |||||
/// Renders the given template with the given context, but also ensures that, if the default file | /// Renders the given template with the given context, but also ensures that, if the default file | ||||
/// is not found, it will look up for the equivalent template for the current theme if there is one. | /// is not found, it will look up for the equivalent template for the current theme if there is one. | ||||
/// Lastly, if it's a default template (index, section or page), it will just return an empty string | /// Lastly, if it's a default template (index, section or page), it will just return an empty string | ||||
@@ -19,11 +33,19 @@ pub fn render_template(name: &str, tera: &Tera, context: &Context, theme: Option | |||||
.map_err(|e| e.into()); | .map_err(|e| e.into()); | ||||
} | } | ||||
if name == "index.html" || name == "section.html" || name == "page.html" { | |||||
return Ok(String::new()); | |||||
// maybe it's a default one? | |||||
match name { | |||||
"index.html" | "section.html" => { | |||||
render_default_tpl!(name, "https://www.getgutenberg.io/documentation/templates/pages-sections/#section-variables") | |||||
}, | |||||
"page.html" => { | |||||
render_default_tpl!(name, "https://www.getgutenberg.io/documentation/templates/pages-sections/#page-variables") | |||||
}, | |||||
"tag.html" | "tags.html" | "category.html" | "categories.html" => { | |||||
render_default_tpl!(name, "https://www.getgutenberg.io/documentation/templates/tags-categories/") | |||||
}, | |||||
_ => bail!("Tried to render `{}` but the template wasn't found", name) | |||||
} | } | ||||
bail!("Tried to render `{}` but the template wasn't found", name) | |||||
} | } | ||||
@@ -55,7 +77,7 @@ mod tests { | |||||
#[test] | #[test] | ||||
fn can_rewrite_all_paths_of_theme() { | fn can_rewrite_all_paths_of_theme() { | ||||
let mut tera = Tera::parse("templates/*.html").unwrap(); | |||||
let mut tera = Tera::parse("test-templates/*.html").unwrap(); | |||||
rewrite_theme_paths(&mut tera, "hyde"); | rewrite_theme_paths(&mut tera, "hyde"); | ||||
// special case to make the test work: we also rename the files to | // special case to make the test work: we also rename the files to | ||||
// match the imports | // match the imports | ||||
@@ -22,6 +22,7 @@ description = "" | |||||
# The date of the post. | # The date of the post. | ||||
# 2 formats are allowed: YYYY-MM-DD (2012-10-02) and RFC3339 (2002-10-02T15:00:00Z) | # 2 formats are allowed: YYYY-MM-DD (2012-10-02) and RFC3339 (2002-10-02T15:00:00Z) | ||||
# Do not wrap dates in quotes, the line below only indicates that there is no default date | |||||
date = "" | date = "" | ||||
# A draft page will not be present in prev/next pagination | # A draft page will not be present in prev/next pagination | ||||
@@ -32,7 +32,8 @@ Here is a full list of the supported languages and the short names you can use: | |||||
- Jinja2 -> ["j2", "jinja2"] | - Jinja2 -> ["j2", "jinja2"] | ||||
- Julia -> ["jl"] | - Julia -> ["jl"] | ||||
- Kotlin -> ["kt", "kts"] | - Kotlin -> ["kt", "kts"] | ||||
- LESS -> ["less"] | |||||
- Less -> ["less", "css.less"] | |||||
- Nim -> ["nim", "nims"] | |||||
- ASP -> ["asa"] | - ASP -> ["asa"] | ||||
- HTML (ASP) -> ["asp"] | - HTML (ASP) -> ["asp"] | ||||
- ActionScript -> ["as"] | - ActionScript -> ["as"] | ||||
@@ -57,12 +58,12 @@ Here is a full list of the supported languages and the short names you can use: | |||||
- Java Server Page (JSP) -> ["jsp"] | - Java Server Page (JSP) -> ["jsp"] | ||||
- Java -> ["java", "bsh"] | - Java -> ["java", "bsh"] | ||||
- Java Properties -> ["properties"] | - Java Properties -> ["properties"] | ||||
- JSON -> ["json", "sublime-settings", "sublime-menu", "sublime-keymap", "sublime-mousemap", "sublime-theme", "sublime-build", "sublime-project", "sublime-completions", "sublime-commands", "sublime-macro"] | |||||
- JSON -> ["json", "sublime-settings", "sublime-menu", "sublime-keymap", "sublime-mousemap", "sublime-theme", "sublime-build", "sublime-project", "sublime-completions", "sublime-commands", "sublime-macro", "sublime-color-scheme"] | |||||
- JavaScript -> ["js", "htc"] | - JavaScript -> ["js", "htc"] | ||||
- BibTeX -> ["bib"] | - BibTeX -> ["bib"] | ||||
- LaTeX -> ["tex", "ltx"] | - LaTeX -> ["tex", "ltx"] | ||||
- TeX -> ["sty", "cls"] | - TeX -> ["sty", "cls"] | ||||
- Lisp -> ["lisp", "cl", "l", "mud", "el", "scm", "ss", "lsp", "fasl"] | |||||
- Lisp -> ["lisp", "cl", "clisp", "l", "mud", "el", "scm", "ss", "lsp", "fasl"] | |||||
- Lua -> ["lua"] | - Lua -> ["lua"] | ||||
- Makefile -> ["make", "GNUmakefile", "makefile", "Makefile", "OCamlMakefile", "mak", "mk"] | - Makefile -> ["make", "GNUmakefile", "makefile", "Makefile", "OCamlMakefile", "mak", "mk"] | ||||
- Markdown -> ["md", "mdown", "markdown", "markdn"] | - Markdown -> ["md", "mdown", "markdown", "markdn"] | ||||
@@ -89,13 +90,14 @@ Here is a full list of the supported languages and the short names you can use: | |||||
- Rust -> ["rs"] | - Rust -> ["rs"] | ||||
- SQL -> ["sql", "ddl", "dml"] | - SQL -> ["sql", "ddl", "dml"] | ||||
- Scala -> ["scala", "sbt"] | - Scala -> ["scala", "sbt"] | ||||
- Bourne Again Shell (bash) -> ["sh", "bash", "zsh", "fish", ".bash_aliases", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init"] | |||||
- Bourne Again Shell (bash) -> ["sh", "bash", "zsh", "fish", ".bash_aliases", ".bash_completions", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init"] | |||||
- HTML (Tcl) -> ["adp"] | - HTML (Tcl) -> ["adp"] | ||||
- Tcl -> ["tcl"] | - Tcl -> ["tcl"] | ||||
- Textile -> ["textile"] | - Textile -> ["textile"] | ||||
- XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"] | - XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"] | ||||
- YAML -> ["yaml", "yml", "sublime-syntax"] | - YAML -> ["yaml", "yml", "sublime-syntax"] | ||||
- Generic Config -> ["cfg", "conf", "config", "ini", "pro"] | |||||
- SWI-Prolog -> ["pro"] | |||||
- Generic Config -> ["cfg", "conf", "config", "ini", "pro", "mak", "mk", "Doxyfile", "inputrc", ".inputrc", "dircolors", ".dircolors", "gitmodules", ".gitmodules", "gitignore", ".gitignore", "gitattributes", ".gitattributes"] | |||||
- Linker Script -> ["ld"] | - Linker Script -> ["ld"] | ||||
- TOML -> ["toml", "tml"] | - TOML -> ["toml", "tml"] | ||||
- TypeScript -> ["ts"] | - TypeScript -> ["ts"] | ||||
@@ -15,9 +15,9 @@ are available at the following paths: | |||||
```plain | ```plain | ||||
$BASE_URL/tags/ | $BASE_URL/tags/ | ||||
$BASE_URL/tag/$TAG_SLUG | |||||
$BASE_URL/tags/$TAG_SLUG | |||||
$BASE_URL/categories/ | $BASE_URL/categories/ | ||||
$BASE_URL/category/$CATEGORY_SLUG | |||||
$BASE_URL/categories/$CATEGORY_SLUG | |||||
``` | ``` | ||||
It is currently not possible to change those paths or to create custom taxonomies. | It is currently not possible to change those paths or to create custom taxonomies. |
@@ -36,6 +36,11 @@ $ gutenberg 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 | This is useful for example when you want to deploy previews of a site to a dynamic URL, such as Netlify | ||||
deploy previews. | deploy previews. | ||||
+You can override the default output directory 'public' by passing a other value to the `output-dir` flag. | |||||
```bash | |||||
$ gutenberg build --output-dir $DOCUMENT_ROOT | |||||
``` | |||||
## serve | ## serve | ||||
This will build and serve the site using a local server. You can also specify | This will build and serve the site using a local server. You can also specify | ||||
@@ -46,6 +51,7 @@ $ gutenberg serve | |||||
$ gutenberg serve --port 2000 | $ gutenberg serve --port 2000 | ||||
$ gutenberg serve --interface 0.0.0.0 | $ gutenberg serve --interface 0.0.0.0 | ||||
$ gutenberg serve --interface 0.0.0.0 --port 2000 | $ gutenberg serve --interface 0.0.0.0 --port 2000 | ||||
$ gutenberg serve --interface 0.0.0.0 --port 2000 --output-dir www/public | |||||
``` | ``` | ||||
The serve command will watch all your content and will provide live reload, without | The serve command will watch all your content and will provide live reload, without | ||||
@@ -21,7 +21,8 @@ base_url = "mywebsite.com" | |||||
# Used in RSS by default | # Used in RSS by default | ||||
title = "" | title = "" | ||||
description = "" | description = "" | ||||
language_code = "en" | |||||
# the default language, used in RSS and coming i18n | |||||
default_language = "en" | |||||
# Theme name to use | # Theme name to use | ||||
theme = "" | theme = "" | ||||
@@ -50,6 +51,9 @@ generate_categories_pages = false | |||||
# Whether to compile the Sass files found in the `sass` directory | # Whether to compile the Sass files found in the `sass` directory | ||||
compile_sass = false | compile_sass = false | ||||
# Optional translation object. The key if present should be a language code | |||||
[translations] | |||||
# You can put any kind of data in there and it | # You can put any kind of data in there and it | ||||
# will be accessible in all templates | # will be accessible in all templates | ||||
[extra] | [extra] | ||||
@@ -70,5 +70,31 @@ we want to link to the file that is located at `static/css/app.css`: | |||||
{{ get_url(path="css/app.css") }} | {{ get_url(path="css/app.css") }} | ||||
``` | ``` | ||||
For assets it is reccommended that you pass `trailing_slash=false` to the `get_url` function. This prevents errors | |||||
when dealing with certain hosting providers. An example is: | |||||
```jinja2 | |||||
{{ get_url(path="css/app.css", trailing_slash=false) }} | |||||
``` | |||||
In the case of non-internal links, you can also add a cachebust of the format `?t=1290192` at the end of a URL | In the case of non-internal links, you can also add a cachebust of the format `?t=1290192` at the end of a URL | ||||
by passing `cachebust=true` to the `get_url` function. | by passing `cachebust=true` to the `get_url` function. | ||||
### `get_taxonomy_url` | |||||
Gets the permalink for the tag or category given. | |||||
```jinja2 | |||||
{% set url = get_taxonomy_url(kind="category", name=page.category) %} | |||||
``` | |||||
The `name` will almost come from a variable but in case you want to do it manually, | |||||
the value should be the same as the one in the front-matter, not the slugified version. | |||||
### `trans` | |||||
Gets the translation of the given `key`, for the `default_language` or the `language given | |||||
```jinja2 | |||||
{{ trans(key="title") }} | |||||
{{ trans(key="title", lang="fr") }} | |||||
``` |
@@ -57,3 +57,8 @@ To be featured on this site, the theme will require two more things: | |||||
of importance | of importance | ||||
A simple theme you can use as example is [Hyde](https://github.com/Keats/hyde). | A simple theme you can use as example is [Hyde](https://github.com/Keats/hyde). | ||||
# Caveat | |||||
Please note that [include paths](https://tera.netlify.com/docs/templates/#include) can only be used in used in normal templates. Theme templates should use [macro's](https://tera.netlify.com/docs/templates/#macros) instead. | |||||
@@ -7,8 +7,8 @@ | |||||
<meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}"> | <meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}"> | ||||
<meta name="author" content="{{ config.extra.author }}"> | <meta name="author" content="{{ config.extra.author }}"> | ||||
<title>{% block title %}{{ config.title }}{% endblock title %}</title> | <title>{% block title %}{{ config.title }}{% endblock title %}</title> | ||||
<link rel="stylesheet" href="{{ get_url(path="site.css") }}"/> | |||||
<link rel="icon" href="{{ get_url(path="favicon.ico") }}"> | |||||
<link rel="stylesheet" href="{{ get_url(path="site.css", trailing_slash=false) }}"/> | |||||
<link rel="icon" href="{{ get_url(path="favicon.ico", trailing_slash=false) }}"> | |||||
</head> | </head> | ||||
<body> | <body> | ||||
@@ -28,6 +28,12 @@ pub fn build_cli() -> App<'static, 'static> { | |||||
.long("base-url") | .long("base-url") | ||||
.takes_value(true) | .takes_value(true) | ||||
.help("Force the base URL to be that value (default to the one in config.toml)"), | .help("Force the base URL to be that value (default to the one in config.toml)"), | ||||
Arg::with_name("output_dir") | |||||
.short("o") | |||||
.long("output-dir") | |||||
.default_value("public") | |||||
.takes_value(true) | |||||
.help("Outputs the generated site in the given path"), | |||||
]), | ]), | ||||
SubCommand::with_name("serve") | SubCommand::with_name("serve") | ||||
.about("Serve the site. Rebuild and reload on change automatically") | .about("Serve the site. Rebuild and reload on change automatically") | ||||
@@ -42,6 +48,12 @@ pub fn build_cli() -> App<'static, 'static> { | |||||
.long("port") | .long("port") | ||||
.default_value("1111") | .default_value("1111") | ||||
.help("Which port to use"), | .help("Which port to use"), | ||||
Arg::with_name("output_dir") | |||||
.short("o") | |||||
.long("output-dir") | |||||
.default_value("public") | |||||
.takes_value(true) | |||||
.help("Outputs the generated site in the given path"), | |||||
]), | ]), | ||||
]) | ]) | ||||
} | } |
@@ -5,8 +5,9 @@ use site::Site; | |||||
use console; | use console; | ||||
pub fn build(config_file: &str, base_url: Option<&str>) -> Result<()> { | |||||
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)?; | let mut site = Site::new(env::current_dir().unwrap(), config_file)?; | ||||
site.set_output_path(output_dir); | |||||
if let Some(b) = base_url { | if let Some(b) = base_url { | ||||
site.config.base_url = b.to_string(); | site.config.base_url = b.to_string(); | ||||
} | } | ||||
@@ -57,8 +57,7 @@ pub fn create_new_project(name: &str) -> Result<()> { | |||||
println!(); | println!(); | ||||
console::success(&format!("Done! Your site was created in {:?}", canonicalize(path).unwrap())); | console::success(&format!("Done! Your site was created in {:?}", canonicalize(path).unwrap())); | ||||
println!(); | println!(); | ||||
console::info("Get started by using the built-in server: `gutenberg serve`"); | |||||
println!("There is no built-in theme so you will see a white page."); | |||||
println!("Visit https://github.com/Keats/gutenberg for the full documentation."); | |||||
console::info("Get started by moving into the directory and using the built-in server: `gutenberg serve`"); | |||||
println!("Visit https://www.getgutenberg.io for the full documentation."); | |||||
Ok(()) | Ok(()) | ||||
} | } |
@@ -22,6 +22,7 @@ | |||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
use std::env; | use std::env; | ||||
use std::fs::remove_dir_all; | |||||
use std::path::Path; | use std::path::Path; | ||||
use std::sync::mpsc::channel; | use std::sync::mpsc::channel; | ||||
use std::time::{Instant, Duration}; | use std::time::{Instant, Duration}; | ||||
@@ -33,6 +34,8 @@ use mount::Mount; | |||||
use staticfile::Static; | use staticfile::Static; | ||||
use notify::{Watcher, RecursiveMode, watcher}; | use notify::{Watcher, RecursiveMode, watcher}; | ||||
use ws::{WebSocket, Sender, Message}; | use ws::{WebSocket, Sender, Message}; | ||||
use ctrlc; | |||||
use site::Site; | use site::Site; | ||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
@@ -45,6 +48,7 @@ enum ChangeKind { | |||||
Templates, | Templates, | ||||
StaticFiles, | StaticFiles, | ||||
Sass, | Sass, | ||||
Config, | |||||
} | } | ||||
// Uglified using uglifyjs | // Uglified using uglifyjs | ||||
@@ -77,8 +81,7 @@ fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &st | |||||
} | } | ||||
} | } | ||||
pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> { | |||||
let start = Instant::now(); | |||||
fn create_new_site(interface: &str, port: &str, output_dir: &str, config_file: &str) -> Result<(Site, String)> { | |||||
let mut site = Site::new(env::current_dir().unwrap(), config_file)?; | let mut site = Site::new(env::current_dir().unwrap(), config_file)?; | ||||
let address = format!("{}:{}", interface, port); | let address = format!("{}:{}", interface, port); | ||||
@@ -88,22 +91,30 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> { | |||||
} else { | } else { | ||||
format!("http://{}", address) | format!("http://{}", address) | ||||
}; | }; | ||||
site.set_output_path(output_dir); | |||||
site.load()?; | site.load()?; | ||||
site.enable_live_reload(); | site.enable_live_reload(); | ||||
console::notify_site_size(&site); | console::notify_site_size(&site); | ||||
console::warn_about_ignored_pages(&site); | console::warn_about_ignored_pages(&site); | ||||
site.build()?; | site.build()?; | ||||
Ok((site, address)) | |||||
} | |||||
pub fn serve(interface: &str, port: &str, output_dir: &str, config_file: &str) -> Result<()> { | |||||
let start = Instant::now(); | |||||
let (mut site, address) = create_new_site(interface, port, output_dir, config_file)?; | |||||
console::report_elapsed_time(start); | console::report_elapsed_time(start); | ||||
let mut watching_static = false; | |||||
// Setup watchers | // Setup watchers | ||||
let mut watching_static = false; | |||||
let (tx, rx) = channel(); | let (tx, rx) = channel(); | ||||
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); | let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); | ||||
watcher.watch("content/", RecursiveMode::Recursive) | watcher.watch("content/", RecursiveMode::Recursive) | ||||
.chain_err(|| "Can't watch the `content` folder. Does it exist?")?; | .chain_err(|| "Can't watch the `content` folder. Does it exist?")?; | ||||
watcher.watch("templates/", RecursiveMode::Recursive) | watcher.watch("templates/", RecursiveMode::Recursive) | ||||
.chain_err(|| "Can't watch the `templates` folder. Does it exist?")?; | .chain_err(|| "Can't watch the `templates` folder. Does it exist?")?; | ||||
watcher.watch("config.toml", RecursiveMode::Recursive) | |||||
.chain_err(|| "Can't watch the `config.toml` file. Does it exist?")?; | |||||
if Path::new("static").exists() { | if Path::new("static").exists() { | ||||
watching_static = true; | watching_static = true; | ||||
@@ -116,9 +127,9 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> { | |||||
let ws_address = format!("{}:{}", interface, "1112"); | let ws_address = format!("{}:{}", interface, "1112"); | ||||
// Start a webserver that serves the `public` directory | |||||
// Start a webserver that serves the `output_dir` directory | |||||
let mut mount = Mount::new(); | let mut mount = Mount::new(); | ||||
mount.mount("/", Static::new(Path::new("public/"))); | |||||
mount.mount("/", Static::new(Path::new(output_dir))); | |||||
mount.mount("/livereload.js", livereload_handler); | mount.mount("/livereload.js", livereload_handler); | ||||
// Starts with a _ to not trigger the unused lint | // Starts with a _ to not trigger the unused lint | ||||
// we need to assign to a variable otherwise it will block | // we need to assign to a variable otherwise it will block | ||||
@@ -147,7 +158,7 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> { | |||||
let pwd = format!("{}", env::current_dir().unwrap().display()); | let pwd = format!("{}", env::current_dir().unwrap().display()); | ||||
let mut watchers = vec!["content", "templates"]; | |||||
let mut watchers = vec!["content", "templates", "config.toml"]; | |||||
if watching_static { | if watching_static { | ||||
watchers.push("static"); | watchers.push("static"); | ||||
} | } | ||||
@@ -158,6 +169,12 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> { | |||||
println!("Listening for changes in {}/{{{}}}", pwd, watchers.join(", ")); | println!("Listening for changes in {}/{{{}}}", pwd, watchers.join(", ")); | ||||
println!("Web server is available at http://{}", address); | println!("Web server is available at http://{}", address); | ||||
println!("Press Ctrl+C to stop\n"); | println!("Press Ctrl+C to stop\n"); | ||||
// Delete the output folder on ctrl+C | |||||
let output_path = Path::new(output_dir).to_path_buf(); | |||||
ctrlc::set_handler(move || { | |||||
remove_dir_all(&output_path).expect("Failed to delete output directory"); | |||||
::std::process::exit(0); | |||||
}).expect("Error setting Ctrl-C handler"); | |||||
use notify::DebouncedEvent::*; | use notify::DebouncedEvent::*; | ||||
@@ -196,6 +213,10 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> { | |||||
console::info(&format!("-> Sass file changed {}", path.display())); | console::info(&format!("-> Sass file changed {}", path.display())); | ||||
rebuild_done_handling(&broadcaster, site.compile_sass(&site.base_path), &p); | rebuild_done_handling(&broadcaster, site.compile_sass(&site.base_path), &p); | ||||
}, | }, | ||||
(ChangeKind::Config, _) => { | |||||
console::info(&format!("-> Config changed. The whole site will be reloaded. The browser needs to be refreshed to make the changes visible.")); | |||||
site = create_new_site(interface, port, output_dir, config_file).unwrap().0; | |||||
} | |||||
}; | }; | ||||
console::report_elapsed_time(start); | console::report_elapsed_time(start); | ||||
} | } | ||||
@@ -249,6 +270,8 @@ fn detect_change_kind(pwd: &str, path: &Path) -> (ChangeKind, String) { | |||||
ChangeKind::StaticFiles | ChangeKind::StaticFiles | ||||
} else if path_str.starts_with("/sass") { | } else if path_str.starts_with("/sass") { | ||||
ChangeKind::Sass | ChangeKind::Sass | ||||
} else if path_str == "/config.toml" { | |||||
ChangeKind::Config | |||||
} else { | } else { | ||||
unreachable!("Got a change in an unexpected path: {}", path_str) | unreachable!("Got a change in an unexpected path: {}", path_str) | ||||
}; | }; | ||||
@@ -299,7 +322,11 @@ mod tests { | |||||
( | ( | ||||
(ChangeKind::Sass, "/sass/print.scss".to_string()), | (ChangeKind::Sass, "/sass/print.scss".to_string()), | ||||
"/home/vincent/site", Path::new("/home/vincent/site/sass/print.scss") | "/home/vincent/site", Path::new("/home/vincent/site/sass/print.scss") | ||||
) | |||||
), | |||||
( | |||||
(ChangeKind::Config, "/config.toml".to_string()), | |||||
"/home/vincent/site", Path::new("/home/vincent/site/config.toml") | |||||
), | |||||
]; | ]; | ||||
for (expected, pwd, path) in test_cases { | for (expected, pwd, path) in test_cases { | ||||
@@ -8,6 +8,7 @@ extern crate mount; | |||||
extern crate notify; | extern crate notify; | ||||
extern crate url; | extern crate url; | ||||
extern crate ws; | extern crate ws; | ||||
extern crate ctrlc; | |||||
extern crate site; | extern crate site; | ||||
#[macro_use] | #[macro_use] | ||||
@@ -43,7 +44,8 @@ fn main() { | |||||
("build", Some(matches)) => { | ("build", Some(matches)) => { | ||||
console::info("Building site..."); | console::info("Building site..."); | ||||
let start = Instant::now(); | let start = Instant::now(); | ||||
match cmd::build(config_file, matches.value_of("base_url")) { | |||||
let output_dir = matches.value_of("output_dir").unwrap(); | |||||
match cmd::build(config_file, matches.value_of("base_url"), output_dir) { | |||||
Ok(()) => console::report_elapsed_time(start), | Ok(()) => console::report_elapsed_time(start), | ||||
Err(e) => { | Err(e) => { | ||||
console::unravel_errors("Failed to build the site", &e); | console::unravel_errors("Failed to build the site", &e); | ||||
@@ -54,8 +56,9 @@ fn main() { | |||||
("serve", Some(matches)) => { | ("serve", Some(matches)) => { | ||||
let interface = matches.value_of("interface").unwrap_or("127.0.0.1"); | let interface = matches.value_of("interface").unwrap_or("127.0.0.1"); | ||||
let port = matches.value_of("port").unwrap_or("1111"); | let port = matches.value_of("port").unwrap_or("1111"); | ||||
let output_dir = matches.value_of("output_dir").unwrap(); | |||||
console::info("Building site..."); | console::info("Building site..."); | ||||
match cmd::serve(interface, port, config_file) { | |||||
match cmd::serve(interface, port, output_dir, config_file) { | |||||
Ok(()) => (), | Ok(()) => (), | ||||
Err(e) => { | Err(e) => { | ||||
console::unravel_errors("", &e); | console::unravel_errors("", &e); | ||||
@@ -1,4 +1,4 @@ | |||||
use std::path::Path; | |||||
use std::path::{Path, Component}; | |||||
use errors::Result; | use errors::Result; | ||||
use site::Site; | use site::Site; | ||||
@@ -231,8 +231,9 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
/// What happens when a template is changed | /// What happens when a template is changed | ||||
pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> { | pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> { | ||||
site.tera.full_reload()?; | site.tera.full_reload()?; | ||||
let filename = path.file_name().unwrap().to_str().unwrap(); | |||||
match path.file_name().unwrap().to_str().unwrap() { | |||||
match filename { | |||||
"sitemap.xml" => site.render_sitemap(), | "sitemap.xml" => site.render_sitemap(), | ||||
"rss.xml" => site.render_rss_feed(), | "rss.xml" => site.render_rss_feed(), | ||||
"robots.txt" => site.render_robots(), | "robots.txt" => site.render_robots(), | ||||
@@ -247,6 +248,14 @@ pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
// We can't really know what this change affects so rebuild all | // We can't really know what this change affects so rebuild all | ||||
// the things | // the things | ||||
_ => { | _ => { | ||||
// If we are updating a shortcode, re-render the markdown of all pages/site | |||||
// because we have no clue which one needs rebuilding | |||||
// TODO: look if there the shortcode is used in the markdown instead of re-rendering | |||||
// everything | |||||
if path.components().collect::<Vec<_>>().contains(&Component::Normal("shortcodes".as_ref())) { | |||||
site.render_markdown()?; | |||||
} | |||||
site.populate_sections(); | |||||
site.render_sections()?; | site.render_sections()?; | ||||
site.render_orphan_pages()?; | site.render_orphan_pages()?; | ||||
site.render_categories()?; | site.render_categories()?; | ||||
@@ -25,7 +25,7 @@ contexts: | |||||
captures: | captures: | ||||
1: keyword.operator.other.elixir | 1: keyword.operator.other.elixir | ||||
2: keyword.control.elixir | 2: keyword.control.elixir | ||||
3: punctuation.definition.parameters.elixir | |||||
3: punctuation.section.function.elixir | |||||
pop: true | pop: true | ||||
- include: core_syntax | - include: core_syntax | ||||
- include: core_syntax | - include: core_syntax | ||||
@@ -37,7 +37,7 @@ contexts: | |||||
captures: | captures: | ||||
1: keyword.operator.other.elixir | 1: keyword.operator.other.elixir | ||||
2: keyword.control.elixir | 2: keyword.control.elixir | ||||
3: punctuation.definition.parameters.elixir | |||||
3: punctuation.section.function.elixir | |||||
pop: true | pop: true | ||||
- include: core_syntax | - include: core_syntax | ||||
core_syntax: | core_syntax: | ||||
@@ -78,7 +78,7 @@ contexts: | |||||
captures: | captures: | ||||
1: keyword.control.module.elixir | 1: keyword.control.module.elixir | ||||
2: entity.name.function.public.elixir | 2: entity.name.function.public.elixir | ||||
4: punctuation.definition.parameters.elixir | |||||
4: punctuation.section.function.elixir | |||||
push: | push: | ||||
- meta_scope: meta.function.public.elixir | - meta_scope: meta.function.public.elixir | ||||
- match: (\bdo:)|(\bdo\b)|(?=\s+(def|defmacro)\b) | - match: (\bdo:)|(\bdo\b)|(?=\s+(def|defmacro)\b) | ||||
@@ -100,7 +100,7 @@ contexts: | |||||
captures: | captures: | ||||
1: keyword.control.module.elixir | 1: keyword.control.module.elixir | ||||
2: entity.name.function.private.elixir | 2: entity.name.function.private.elixir | ||||
4: punctuation.definition.parameters.elixir | |||||
4: punctuation.section.function.elixir | |||||
push: | push: | ||||
- meta_scope: meta.function.private.elixir | - meta_scope: meta.function.private.elixir | ||||
- match: (\bdo:)|(\bdo\b)|(?=\s+(defp|defmacrop)\b) | - match: (\bdo:)|(\bdo\b)|(?=\s+(defp|defmacrop)\b) | ||||
@@ -157,18 +157,6 @@ contexts: | |||||
pop: true | pop: true | ||||
- include: interpolated_elixir | - include: interpolated_elixir | ||||
- include: escaped_char | - include: escaped_char | ||||
- match: (::) | |||||
captures: | |||||
0: punctuation.binary.elixir | |||||
push: | |||||
- match: (,|>>|$) | |||||
captures: | |||||
0: punctuation.binary.elixir | |||||
pop: true | |||||
- match: '\b[a-z]\w*\b' | |||||
scope: support.type.binary.elixir | |||||
- match: '\b(0x[0-9A-Fa-f](?>_?[0-9A-Fa-f])*|\d(?>_?\d)*(\.(?![^[:space:][:digit:]])(?>_?\d)*)?([eE][-+]?\d(?>_?\d)*)?|0b[01]+|0o[0-7]+)\b' | |||||
scope: constant.numeric.elixir | |||||
- match: '(?<!\.)\b(do|end|case|bc|lc|for|if|cond|unless|try|receive|fn|defmodule|defp?|defprotocol|defimpl|defrecord|defstruct|defmacrop?|defdelegate|defcallback|defmacrocallback|defexception|defoverridable|exit|after|rescue|catch|else|raise|throw|import|require|alias|use|quote|unquote|super|with)\b(?![?!:])' | - match: '(?<!\.)\b(do|end|case|bc|lc|for|if|cond|unless|try|receive|fn|defmodule|defp?|defprotocol|defimpl|defrecord|defstruct|defmacrop?|defdelegate|defcallback|defmacrocallback|defexception|defoverridable|exit|after|rescue|catch|else|raise|throw|import|require|alias|use|quote|unquote|super|with)\b(?![?!:])' | ||||
scope: keyword.control.elixir | scope: keyword.control.elixir | ||||
- match: (?<!\.)\b(and|not|or|when|xor|in)\b | - match: (?<!\.)\b(and|not|or|when|xor|in)\b | ||||
@@ -433,6 +421,14 @@ contexts: | |||||
the negative lookbehind prevents against matching | the negative lookbehind prevents against matching | ||||
p(42.tainted?) | p(42.tainted?) | ||||
scope: constant.numeric.elixir | scope: constant.numeric.elixir | ||||
- match: \+\+|\-\-|<\|> | |||||
scope: keyword.operator.concatenation.elixir | |||||
- match: \|\>|<~>|<>|<<<|>>>|~>>|<<~|~>|<~|<\|> | |||||
scope: keyword.operator.sigils_1.elixir | |||||
- match: "&&&|&&" | |||||
scope: keyword.operator.sigils_2.elixir | |||||
- match: <\-|\\\\ | |||||
scope: keyword.operator.sigils_3.elixir | |||||
- match: "===?|!==?|<=?|>=?" | - match: "===?|!==?|<=?|>=?" | ||||
scope: keyword.operator.comparison.elixir | scope: keyword.operator.comparison.elixir | ||||
- match: (\|\|\||&&&|^^^|<<<|>>>|~~~) | - match: (\|\|\||&&&|^^^|<<<|>>>|~~~) | ||||
@@ -1,200 +0,0 @@ | |||||
%YAML 1.2 | |||||
--- | |||||
# http://www.sublimetext.com/docs/3/syntax.html | |||||
name: Elm | |||||
file_extensions: | |||||
- elm | |||||
scope: source.elm | |||||
contexts: | |||||
main: | |||||
- match: "(`)[a-zA-Z_']*?(`)" | |||||
scope: keyword.operator.function.infix.elm | |||||
captures: | |||||
1: punctuation.definition.entity.elm | |||||
2: punctuation.definition.entity.elm | |||||
- match: \(\) | |||||
scope: constant.language.unit.elm | |||||
- match: ^\b((effect|port)\s+)?(module)\s+ | |||||
captures: | |||||
1: keyword.other.elm | |||||
3: keyword.other.elm | |||||
push: | |||||
- meta_scope: meta.declaration.module.elm | |||||
- match: $|; | |||||
captures: | |||||
1: keyword.other.elm | |||||
pop: true | |||||
- include: module_name | |||||
- match: '(where)\s*\{' | |||||
captures: | |||||
1: keyword.other.elm | |||||
push: | |||||
- match: '\}' | |||||
pop: true | |||||
- include: type_signature | |||||
- match: (exposing) | |||||
scope: keyword.other.elm | |||||
- include: module_exports | |||||
- match: (where) | |||||
scope: keyword.other.elm | |||||
- match: "[a-z]+" | |||||
scope: invalid | |||||
- match: ^\b(import)\s+((open)\s+)? | |||||
captures: | |||||
1: keyword.other.elm | |||||
3: invalid | |||||
push: | |||||
- meta_scope: meta.import.elm | |||||
- match: ($|;) | |||||
pop: true | |||||
- match: (as|exposing) | |||||
scope: keyword.import.elm | |||||
- include: module_name | |||||
- include: module_exports | |||||
- match: '(\[)(glsl)(\|)' | |||||
captures: | |||||
1: keyword.other.elm | |||||
2: support.function.prelude.elm | |||||
3: keyword.other.elm | |||||
push: | |||||
- meta_scope: entity.glsl.elm | |||||
- match: '(\|\])' | |||||
captures: | |||||
1: keyword.other.elm | |||||
pop: true | |||||
- include: scope:source.glsl | |||||
- match: \b(type alias|type|case|of|let|in|as)\s+ | |||||
scope: keyword.other.elm | |||||
- match: \b(if|then|else)\s+ | |||||
scope: keyword.control.elm | |||||
- match: '\b([0-9]+\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)\b' | |||||
comment: Floats are always decimal | |||||
scope: constant.numeric.float.elm | |||||
- match: '\b([0-9]+)\b' | |||||
scope: constant.numeric.elm | |||||
- match: '"""' | |||||
captures: | |||||
0: punctuation.definition.string.begin.elm | |||||
push: | |||||
- meta_scope: string.quoted.double.elm | |||||
- match: '"""' | |||||
captures: | |||||
0: punctuation.definition.string.end.elm | |||||
pop: true | |||||
- match: '\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\''\&])' | |||||
scope: constant.character.escape.elm | |||||
- match: '\^[A-Z@\[\]\\\^_]' | |||||
scope: constant.character.escape.control.elm | |||||
- match: '"' | |||||
captures: | |||||
0: punctuation.definition.string.begin.elm | |||||
push: | |||||
- meta_scope: string.quoted.double.elm | |||||
- match: '"' | |||||
captures: | |||||
0: punctuation.definition.string.end.elm | |||||
pop: true | |||||
- match: '\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\\"''\&])' | |||||
scope: constant.character.escape.elm | |||||
- match: '\^[A-Z@\[\]\\\^_]' | |||||
scope: constant.character.escape.control.elm | |||||
- match: |- | |||||
(?x) | |||||
(') | |||||
(?: | |||||
[\ -\[\]-~] # Basic Char | |||||
| (\\(?:NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE | |||||
|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS | |||||
|US|SP|DEL|[abfnrtv\\\"'\&])) # Escapes | |||||
| (\^[A-Z@\[\]\\\^_]) # Control Chars | |||||
) | |||||
(') | |||||
scope: string.quoted.single.elm | |||||
captures: | |||||
1: punctuation.definition.string.begin.elm | |||||
2: constant.character.escape.elm | |||||
3: punctuation.definition.string.end.elm | |||||
- match: '^(port\s+)?([a-z_][a-zA-Z0-9_'']*|\([|!%$+\-.,=</>]+\))\s*((:)([:]+)?)' | |||||
captures: | |||||
1: keyword.other.port.elm | |||||
2: entity.name.function.elm | |||||
4: keyword.other.colon.elm | |||||
5: invalid | |||||
push: | |||||
- meta_scope: meta.function.type-declaration.elm | |||||
- match: $\n? | |||||
pop: true | |||||
- include: type_signature | |||||
- match: \bport\s+ | |||||
scope: keyword.other.port.elm | |||||
- match: '\b[A-Z]\w*\b' | |||||
scope: constant.other.elm | |||||
- include: comments | |||||
- match: '^[a-z][A-Za-z0-9_'']*\s+' | |||||
scope: entity.name.function.elm | |||||
- include: infix_op | |||||
- match: '[|!%$?~+:\-.=</>&\\*^]+' | |||||
scope: keyword.operator.elm | |||||
- match: '([\[\]\{\},])' | |||||
scope: constant.language.delimiter.elm | |||||
captures: | |||||
1: support.function.delimiter.elm | |||||
- match: '([\(\)])' | |||||
scope: keyword.other.parenthesis.elm | |||||
block_comment: | |||||
- match: '\{-(?!#)' | |||||
captures: | |||||
0: punctuation.definition.comment.elm | |||||
push: | |||||
- meta_scope: comment.block.elm | |||||
- include: block_comment | |||||
- match: '-\}' | |||||
captures: | |||||
0: punctuation.definition.comment.elm | |||||
pop: true | |||||
comments: | |||||
- match: (--).*$\n? | |||||
scope: comment.line.double-dash.elm | |||||
captures: | |||||
1: punctuation.definition.comment.elm | |||||
- include: block_comment | |||||
infix_op: | |||||
- match: '(\([|!%$+:\-.=</>]+\)|\(,+\))' | |||||
scope: entity.name.function.infix.elm | |||||
module_exports: | |||||
- match: \( | |||||
push: | |||||
- meta_scope: meta.declaration.exports.elm | |||||
- match: \) | |||||
pop: true | |||||
- match: '\b[a-z][a-zA-Z_''0-9]*' | |||||
scope: entity.name.function.elm | |||||
- match: '\b[A-Z][A-Za-z_''0-9]*' | |||||
scope: storage.type.elm | |||||
- match: "," | |||||
scope: punctuation.separator.comma.elm | |||||
- include: infix_op | |||||
- match: \(.*?\) | |||||
comment: So named because I don't know what to call this. | |||||
scope: meta.other.unknown.elm | |||||
module_name: | |||||
- match: "[A-Z][A-Za-z._']*" | |||||
scope: support.other.module.elm | |||||
type_signature: | |||||
- match: '\(\s*([A-Z][A-Za-z]*)\s+([a-z][A-Za-z_'']*)\)\s*(=>)' | |||||
scope: meta.class-constraint.elm | |||||
captures: | |||||
1: entity.other.inherited-class.elm | |||||
2: variable.other.generic-type.elm | |||||
3: keyword.other.big-arrow.elm | |||||
- match: "->" | |||||
scope: keyword.other.arrow.elm | |||||
- match: "=>" | |||||
scope: keyword.other.big-arrow.elm | |||||
- match: '\b[a-z][a-zA-Z0-9_'']*\b' | |||||
scope: variable.other.generic-type.elm | |||||
- match: '\b[A-Z][a-zA-Z0-9_'']*\b' | |||||
scope: storage.type.elm | |||||
- match: \(\) | |||||
scope: support.constant.unit.elm | |||||
- include: comments |
@@ -1 +1 @@ | |||||
Subproject commit 581b9e6f5bfdf55e506ac5a2b18422296c375ca2 | |||||
Subproject commit 6f2e53603a62663463f95079b308a1c9adbabeec |
@@ -1 +1 @@ | |||||
Subproject commit 3cf94d55b2eafd41461c45570d982eef1d65778c | |||||
Subproject commit 581805e47c7af5ab0a880aaef5b27f8c1ccc29aa |
@@ -1 +1 @@ | |||||
Subproject commit ebd4a2f049fb61686664a68c8d0f34d83292e07e | |||||
Subproject commit df5a27523dd37ebe67ba4c7d36ea162dae95b2c3 |
@@ -1,326 +0,0 @@ | |||||
%YAML 1.2 | |||||
--- | |||||
# http://www.sublimetext.com/docs/3/syntax.html | |||||
name: LESS | |||||
comment: LESS | |||||
file_extensions: | |||||
- less | |||||
scope: source.less | |||||
contexts: | |||||
main: | |||||
- include: comment-block | |||||
- include: comment-line | |||||
- include: less-at-rules | |||||
- include: less-declarations | |||||
- include: less-variables | |||||
- include: less-functions | |||||
- include: string-double | |||||
- include: string-single | |||||
- include: selector | |||||
- include: rule-list | |||||
- include: less-operators | |||||
- include: less-parameters | |||||
- include: numeric-values | |||||
color-values: | |||||
- match: \b(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)\b | |||||
comment: http://www.w3.org/TR/CSS21/syndata.html#value-def-color | |||||
scope: support.constant.color.w3c-standard-color-name.css | |||||
- match: \b(aliceblue|antiquewhite|aquamarine|azure|beige|bisque|blanchedalmond|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|gainsboro|ghostwhite|gold|goldenrod|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|limegreen|linen|magenta|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|oldlace|olivedrab|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|thistle|tomato|turquoise|violet|wheat|whitesmoke|yellowgreen)\b | |||||
comment: "These colours are mostly recognised but will not validate. ref: http://www.w3schools.com/css/css_colornames.asp" | |||||
scope: support.constant.color.non-standard | |||||
- match: (hsla?|rgba?)\s*(\() | |||||
captures: | |||||
1: support.function.misc.css | |||||
2: punctuation.section.function.css | |||||
push: | |||||
- match: (\)) | |||||
captures: | |||||
1: punctuation.section.function.css | |||||
pop: true | |||||
- match: '(?x)\b(0*((1?[0-9]{1,2})|(2([0-4][0-9]|5[0-5])))\s*,\s*){2}(0*((1?[0-9]{1,2})|(2([0-4][0-9]|5[0-5])))\b)(\s*,\s*((0?\.[0-9]+)|[0-1]))?' | |||||
scope: constant.other.color.rgb-value.css | |||||
- match: '\b([0-9]{1,2}|100)\s*%,\s*([0-9]{1,2}|100)\s*%,\s*([0-9]{1,2}|100)\s*%' | |||||
scope: constant.other.color.rgb-percentage.css | |||||
- include: numeric-values | |||||
comment-block: | |||||
- match: /\* | |||||
captures: | |||||
0: punctuation.definition.comment.css | |||||
push: | |||||
- meta_scope: comment.block.css | |||||
- match: \*/ | |||||
captures: | |||||
0: punctuation.definition.comment.css | |||||
pop: true | |||||
comment-line: | |||||
- match: // | |||||
captures: | |||||
0: punctuation.definition.comment.css | |||||
push: | |||||
- meta_scope: comment.line.double-slash.less | |||||
- match: $\n? | |||||
captures: | |||||
0: punctuation.definition.comment.css | |||||
pop: true | |||||
less-at-rules: | |||||
- match: ^\s*((@)(?:-(?:webkit|moz|o)-)?(charset|import|namespace|media|page|font-face|keyframes|supports|document)\b) | |||||
scope: meta.at-rule.css | |||||
captures: | |||||
1: keyword.control.at-rule.css | |||||
2: punctuation.definition.keyword.css | |||||
less-data-uri: | |||||
- match: (url)(\()(data:) | |||||
captures: | |||||
1: support.function.misc.css | |||||
2: punctuation.section.function.css | |||||
3: parameter.less.data-uri comment markup.raw | |||||
push: | |||||
- meta_content_scope: parameter.less.data-uri comment markup.raw | |||||
- match: (\)) | |||||
captures: | |||||
1: punctuation.section.function.css | |||||
pop: true | |||||
- match: (url)(\()("data:) | |||||
captures: | |||||
1: support.function.misc.css | |||||
2: punctuation.section.function.css | |||||
3: parameter.less.data-uri comment markup.raw | |||||
push: | |||||
- meta_content_scope: parameter.less.data-uri comment markup.raw | |||||
- match: (?<=")(\)) | |||||
captures: | |||||
1: punctuation.section.function.css | |||||
pop: true | |||||
- match: (url)(\()('data:) | |||||
captures: | |||||
1: support.function.misc.css | |||||
2: punctuation.section.function.css | |||||
3: parameter.less.data-uri comment markup.raw | |||||
push: | |||||
- meta_content_scope: parameter.less.data-uri comment markup.raw | |||||
- match: (?<=\')(\)) | |||||
captures: | |||||
1: punctuation.section.function.css | |||||
pop: true | |||||
less-declarations: | |||||
- match: '(?>@[a-zA-Z0-9_-][\w-]*+)(?!:)' | |||||
scope: variable.other.less | |||||
- match: '@[a-zA-Z0-9_-][\w-]*' | |||||
scope: variable.declaration.less | |||||
less-functions: | |||||
- match: '([%a-zA-Z\-\_\d]*)(?=\()' | |||||
scope: support.function.less | |||||
- match: '([\.#](?![0-9])[a-zA-Z0-9_-]+(?=\())' | |||||
captures: | |||||
1: entity.other.attribute-name.class.css entity.other.less.mixin | |||||
less-operators: | |||||
- match: /|!important|$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\|\||\?\:|(?<!\()/=|%=|\+=|\-=|&=|when\b|and\b|not\b | |||||
scope: keyword.operator.less | |||||
less-parameters: | |||||
- match: '\(|(}\s*,)' | |||||
push: | |||||
- meta_scope: parameter.less | |||||
- match: '\)|{' | |||||
pop: true | |||||
- include: color-values | |||||
- include: less-parameters | |||||
- include: less-functions | |||||
- include: numeric-values | |||||
- include: string-double | |||||
- include: string-single | |||||
- include: less-variables | |||||
- match: '(?:\:/)?[\/\.a-zA-Z0-9_\-]*' | |||||
scope: variable.parameter.misc.css | |||||
- include: less-operators | |||||
less-variables: | |||||
- match: '@[a-zA-Z0-9_-][\w-]*' | |||||
scope: variable.other.less | |||||
- match: '@{[a-zA-Z0-9_-][\w-]*}' | |||||
scope: variable.interpolation.less | |||||
- match: "`" | |||||
push: | |||||
- meta_scope: comment markup.raw | |||||
- match: "`" | |||||
pop: true | |||||
- match: (~)` | |||||
captures: | |||||
1: keyword.operator.less | |||||
push: | |||||
- meta_scope: comment markup.raw | |||||
- match: "`" | |||||
pop: true | |||||
- match: (~)" | |||||
captures: | |||||
1: keyword.operator.less | |||||
push: | |||||
- meta_scope: string.quoted.double.css comment markup.raw | |||||
- match: '"' | |||||
pop: true | |||||
- match: (~)' | |||||
captures: | |||||
1: keyword.operator.less | |||||
push: | |||||
- meta_scope: string.quoted.single.css comment markup.raw | |||||
- match: "'" | |||||
pop: true | |||||
numeric-values: | |||||
- match: '(#)([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b' | |||||
scope: constant.other.color.rgb-value.css | |||||
captures: | |||||
1: punctuation.definition.constant.css | |||||
- match: '(?x)(?:-|\+)?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+))((?:px|pt|ch|cm|mm|in|r?em|ex|pc|deg|g?rad|dpi|dpcm|ms|s)\b|%)?' | |||||
scope: constant.numeric.css | |||||
captures: | |||||
1: keyword.other.unit.css | |||||
property-names: | |||||
- match: "(?<![-a-z])(?=[-a-z])" | |||||
push: | |||||
- meta_scope: support.type.property-name.css | |||||
- match: "$|(?![-a-z])" | |||||
pop: true | |||||
- match: \b(word)\b | |||||
scope: support.type.property-name.css | |||||
property-values: | |||||
- include: less-variables | |||||
- include: less-data-uri | |||||
- include: less-functions | |||||
- include: less-operators | |||||
- include: color-values | |||||
- include: numeric-values | |||||
- include: less-parameters | |||||
- match: \b(absolute|all(-scroll)?|always|subpixel-antialiased|antialiased|armenian|auto|avoid|baseline|below|bidi-override|block|bold|bolder|border-box|both|bottom|break-all|break-word|capitalize|center|char|circle|cjk-ideographic|content-box|col-resize|collapse|crosshair|dashed|decimal-leading-zero|decimal|default|disabled|disc|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ellipsis|fixed|geometricPrecision|georgian|groove|hand|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|inactive|inherit|inline-block|inline|inset|inside|inter-ideograph|inter-word|italic|justify|katakana-iroha|katakana|keep-all|left|lighter|line-edge|line-through|line|list-item|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|medium|middle|move|n-resize|ne-resize|newspaper|no-drop|no-repeat|nw-resize|none|normal|not-allowed|nowrap|oblique|optimize(Legibility|Quality|Speed)|outset|outside|overline|pointer|pre(-(wrap|line))?|progress|relative|repeat-x|repeat-y|repeat|right|ridge|row-resize|rtl|s-resize|scroll|se-resize|separate|small-caps|solid|square|static|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table|tb-rl|text-bottom|text-top|textfield|text|thick|thin|top|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|vertical(-(ideographic|text))?|visible(Painted|Fill|Stroke)?|w-resize|wait|whitespace|zero|smaller|larger|((xx?-)?(small|large))|painted|fill|stroke)\b | |||||
scope: support.constant.property-value.css | |||||
- include: selector | |||||
- match: (\b(?i:arial|century|comic|courier|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace)\b) | |||||
scope: support.constant.font-name.css | |||||
- include: string-double | |||||
- include: string-single | |||||
- match: (rect)\s*(\() | |||||
captures: | |||||
1: support.function.misc.css | |||||
2: punctuation.section.function.css | |||||
push: | |||||
- match: (\)) | |||||
captures: | |||||
1: punctuation.section.function.css | |||||
pop: true | |||||
- include: numeric-values | |||||
- match: (format|local|url|attr|counter|counters)\s*(\() | |||||
captures: | |||||
1: support.function.misc.css | |||||
2: punctuation.section.function.css | |||||
push: | |||||
- match: (\)) | |||||
captures: | |||||
1: punctuation.section.function.css | |||||
pop: true | |||||
- include: string-single | |||||
- include: string-double | |||||
- match: '[^''") \t]+' | |||||
scope: variable.parameter.misc.css | |||||
- match: \!\s*important | |||||
scope: keyword.other.important.css | |||||
rule-list: | |||||
- include: comment-block | |||||
- include: comment-line | |||||
- include: selector | |||||
- include: property-names | |||||
- match: (:)\s* | |||||
captures: | |||||
1: punctuation.separator.key-value.css | |||||
push: | |||||
- meta_scope: meta.property-value.css | |||||
- match: '\s*(;|(?=[{}]))' | |||||
captures: | |||||
1: punctuation.terminator.rule.css | |||||
pop: true | |||||
- include: property-values | |||||
selector: | |||||
- match: '\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|eventsource|fieldset|figure|figcaption|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|samp|script|section|select|small|span|strike|strong|style|sub|summary|sup|svg|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\b' | |||||
scope: entity.name.tag.css, keyword.control.html.elements | |||||
- match: '(\.)-?[a-zA-Z_][a-zA-Z0-9_-]*' | |||||
scope: entity.other.attribute-name.class.css | |||||
captures: | |||||
1: punctuation.definition.entity.css | |||||
- match: "(#)-?[a-zA-Z_][a-zA-Z0-9_-]*" | |||||
scope: entity.other.attribute-name.id.css | |||||
captures: | |||||
1: punctuation.definition.entity.css | |||||
- match: \* | |||||
scope: entity.name.tag.wildcard.css | |||||
- match: (:+)((first|last|only)-child|(first|last|only)-of-type|empty|root|target|first-letter|first-line|first|left|right|lang)\b | |||||
scope: entity.other.attribute-name.pseudo-class.css | |||||
captures: | |||||
1: punctuation.definition.entity.css | |||||
- match: (:)(extend)\b | |||||
scope: entity.other.attribute-name.pseudo-class.less | |||||
captures: | |||||
1: punctuation.definition.entity.css | |||||
- match: (:)(checked|enabled|default|disabled|indeterminate|invalid|optional|required|valid)\b | |||||
scope: entity.other.attribute-name.pseudo-class.ui-state.css | |||||
captures: | |||||
1: punctuation.definition.entity.css | |||||
- match: ((:)not)(\() | |||||
captures: | |||||
1: entity.other.attribute-name.pseudo-class.css | |||||
2: punctuation.definition.entity.css | |||||
3: punctuation.section.function.css | |||||
push: | |||||
- match: \) | |||||
captures: | |||||
0: punctuation.section.function.css | |||||
pop: true | |||||
- include: selector | |||||
- match: ((:)nth-(?:(?:last-)?child|(?:last-)?of-type))(\() | |||||
captures: | |||||
1: entity.other.attribute-name.pseudo-class.css | |||||
2: punctuation.definition.entity.css | |||||
3: punctuation.section.function.css | |||||
push: | |||||
- meta_content_scope: constant.numeric.css | |||||
- match: (\)) | |||||
captures: | |||||
1: punctuation.section.function.css | |||||
pop: true | |||||
- match: (:+)(?:-(?:webkit|moz|o)-)?(after|before|selection|scrollbar|placeholder|input-placeholder)\b | |||||
scope: entity.other.attribute-name.pseudo-element.css | |||||
captures: | |||||
1: punctuation.definition.entity.css | |||||
- match: (:+)(?:-(?:webkit|moz|o)-)?(active|hover|link|visited|focus-inner|focus)\b | |||||
scope: entity.other.attribute-name.pseudo-class.css | |||||
captures: | |||||
1: punctuation.definition.entity.css | |||||
- match: '(?i)(\[)\s*(-?[_a-z\\[[:^ascii:]]][_a-z0-9\-\\[[:^ascii:]]]*)(?:\s*([~|^$*]?=)\s*(?:(-?[_a-z\\[[:^ascii:]]][_a-z0-9\-\\[[:^ascii:]]]*)|((?>([''"])(?:[^\\]|\\.)*?(\6)))))?\s*(\])' | |||||
scope: meta.attribute-selector.css | |||||
captures: | |||||
1: punctuation.definition.entity.css | |||||
2: entity.other.attribute-name.attribute.css | |||||
3: punctuation.separator.operator.css | |||||
4: string.unquoted.attribute-value.css | |||||
5: string.quoted.double.attribute-value.css | |||||
6: punctuation.definition.string.begin.css | |||||
7: punctuation.definition.string.end.css | |||||
string-double: | |||||
- match: '"' | |||||
captures: | |||||
0: punctuation.definition.string.begin.css | |||||
push: | |||||
- meta_scope: string.quoted.double.css | |||||
- match: '"' | |||||
captures: | |||||
0: punctuation.definition.string.end.css | |||||
pop: true | |||||
- match: \\. | |||||
scope: constant.character.escape.css | |||||
string-single: | |||||
- match: "'" | |||||
captures: | |||||
0: punctuation.definition.string.begin.css | |||||
push: | |||||
- meta_scope: string.quoted.single.css | |||||
- match: "'" | |||||
captures: | |||||
0: punctuation.definition.string.end.css | |||||
pop: true | |||||
- match: \\. | |||||
scope: constant.character.escape.css |
@@ -1 +1 @@ | |||||
Subproject commit 928a7a618d99631ea424c45e74fb01d1fb6f6853 | |||||
Subproject commit ce7af4d6177d0340230893e49742070ae4143246 |
@@ -0,0 +1,135 @@ | |||||
%YAML 1.2 | |||||
--- | |||||
# http://www.sublimetext.com/docs/3/syntax.html | |||||
name: SWI-Prolog | |||||
comment: This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. | |||||
file_extensions: | |||||
- pro | |||||
scope: source.prolog | |||||
contexts: | |||||
main: | |||||
- include: comments | |||||
- match: (?<=:-)\s* | |||||
push: | |||||
- meta_scope: meta.clause.body.prolog | |||||
- match: (\.) | |||||
captures: | |||||
1: keyword.control.clause.bodyend.prolog | |||||
pop: true | |||||
- include: comments | |||||
- include: builtin | |||||
- include: controlandkeywords | |||||
- include: atom | |||||
- include: variable | |||||
- include: constants | |||||
- match: . | |||||
scope: meta.clause.body.prolog | |||||
- match: '^\s*([a-z][a-zA-Z0-9_]*)(\(?)(?=.*:-.*)' | |||||
captures: | |||||
1: entity.name.function.clause.prolog | |||||
2: punctuation.definition.parameters.begin | |||||
push: | |||||
- meta_scope: meta.clause.head.prolog | |||||
- match: ((\)?))\s*(:-) | |||||
captures: | |||||
1: punctuation.definition.parameters.end | |||||
3: keyword.control.clause.bodybegin.prolog | |||||
pop: true | |||||
- include: atom | |||||
- include: variable | |||||
- include: constants | |||||
- match: '^\s*([a-z][a-zA-Z0-9_]*)(\(?)(?=.*-->.*)' | |||||
captures: | |||||
1: entity.name.function.dcg.prolog | |||||
2: punctuation.definition.parameters.begin | |||||
push: | |||||
- meta_scope: meta.dcg.head.prolog | |||||
- match: ((\)?))\s*(-->) | |||||
captures: | |||||
1: punctuation.definition.parameters.end | |||||
3: keyword.control.dcg.bodybegin.prolog | |||||
pop: true | |||||
- include: atom | |||||
- include: variable | |||||
- include: constants | |||||
- match: (?<=-->)\s* | |||||
push: | |||||
- meta_scope: meta.dcg.body.prolog | |||||
- match: (\.) | |||||
captures: | |||||
1: keyword.control.dcg.bodyend.prolog | |||||
pop: true | |||||
- include: comments | |||||
- include: controlandkeywords | |||||
- include: atom | |||||
- include: variable | |||||
- include: constants | |||||
- match: . | |||||
scope: meta.dcg.body.prolog | |||||
- match: '^\s*([a-zA-Z][a-zA-Z0-9_]*)(\(?)(?!.*(:-|-->).*)' | |||||
captures: | |||||
1: entity.name.function.fact.prolog | |||||
2: punctuation.definition.parameters.begin | |||||
push: | |||||
- meta_scope: meta.fact.prolog | |||||
- match: ((\)?))\s*(\.)(?!\d+) | |||||
captures: | |||||
1: punctuation.definition.parameters.end | |||||
3: keyword.control.fact.end.prolog | |||||
pop: true | |||||
- include: atom | |||||
- include: variable | |||||
- include: constants | |||||
atom: | |||||
- match: '(?<![a-zA-Z0-9_])[a-z][a-zA-Z0-9_]*(?!\s*\(|[a-zA-Z0-9_])' | |||||
scope: constant.other.atom.simple.prolog | |||||
- match: "'.*?'" | |||||
scope: constant.other.atom.quoted.prolog | |||||
- match: '\[\]' | |||||
scope: constant.other.atom.emptylist.prolog | |||||
builtin: | |||||
- match: \b(op|findall|write|nl|writeln|fail|use_module|module)\b | |||||
scope: keyword.other | |||||
comments: | |||||
- match: "%.*" | |||||
scope: comment.line.percent-sign.prolog | |||||
- match: /\* | |||||
captures: | |||||
0: punctuation.definition.comment.prolog | |||||
push: | |||||
- meta_scope: comment.block.prolog | |||||
- match: \*/ | |||||
captures: | |||||
0: punctuation.definition.comment.prolog | |||||
pop: true | |||||
constants: | |||||
- match: '(?<![a-zA-Z]|/)(\d+|(\d+\.\d+))' | |||||
scope: constant.numeric.integer.prolog | |||||
- match: '".*?"' | |||||
scope: string.quoted.double.prolog | |||||
controlandkeywords: | |||||
- match: (->) | |||||
captures: | |||||
1: keyword.control.if.prolog | |||||
push: | |||||
- meta_scope: meta.if.prolog | |||||
- match: (;) | |||||
captures: | |||||
1: keyword.control.else.prolog | |||||
pop: true | |||||
- include: main | |||||
- include: builtin | |||||
- include: comments | |||||
- include: atom | |||||
- include: variable | |||||
- match: . | |||||
scope: meta.if.body.prolog | |||||
- match: "!" | |||||
scope: keyword.control.cut.prolog | |||||
- match: (\s(is)\s)|=:=|=?\\?=|\\\+|@?>|@?=?<|\+|\*|\- | |||||
scope: keyword.operator.prolog | |||||
variable: | |||||
- match: "(?<![a-zA-Z0-9_])[A-Z][a-zA-Z0-9_]*" | |||||
scope: variable.parameter.uppercase.prolog | |||||
- match: (?<!\w)_ | |||||
scope: variable.language.anonymous.prolog |
@@ -1 +1 @@ | |||||
Subproject commit 5f12fdaae34303ab2550394a14599ad0885b26ec | |||||
Subproject commit 04ec6d7165e971db1fefd604942b04ccf86fb920 |
@@ -1 +0,0 @@ | |||||
Subproject commit 79bf8ddfb8a05a2b104f3937cd91b6f2afbbb943 |
@@ -0,0 +1 @@ | |||||
Subproject commit c29d12d8aceb1a68af4cb6e466199846f41dd2ed |