Merge pull request #175 from Keats/next

Next version
This commit is contained in:
Vincent Prouillet 2018-01-25 14:33:53 +01:00 committed by GitHub
commit c165c17c2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 3262 additions and 2532 deletions

6
.gitmodules vendored
View file

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

View file

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

View file

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

810
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
[package] [package]
name = "gutenberg" name = "gutenberg"
version = "0.2.2" version = "0.3.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] 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" staticfile = "0.5"
iron = "0.5" iron = "0.6"
mount = "0.3" 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" }

View file

@ -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");

View file

@ -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]' \

View file

@ -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' {

153
completions/gutenberg.bash Normal file
View file

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

View file

@ -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'

View file

@ -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");
}
} }

View file

@ -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"

View file

@ -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());

View file

@ -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;

View file

@ -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)?;

View file

@ -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[0].clone().meta.date.unwrap().to_string(), "2019-01-01");
assert_eq!(pages[1].clone().meta.date.unwrap(), "2018-01-01"); assert_eq!(pages[1].clone().meta.date.unwrap().to_string(), "2018-01-01");
assert_eq!(pages[2].clone().meta.date.unwrap(), "2017-01-01"); assert_eq!(pages[2].clone().meta.date.unwrap().to_string(), "2017-01-01");
} }
#[test] #[test]

View file

@ -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" error-chain = "0.11"
tera = "0.10" tera = "0.11.0"
toml = "0.4" toml = "0.4"

View file

@ -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" }

View file

@ -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());
} }

View file

@ -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 { if let Some(ref d) = self.date {
Some(ref d) => { let d2 = d.to_string();
if d.contains('T') { if d2.contains('T') {
DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local())) DateTime::parse_from_rfc3339(&d2).ok().and_then(|s| Some(s.naive_local()))
} else { } else {
NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0))) NaiveDate::parse_from_str(&d2, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0, 0, 0)))
} }
}, } else {
None => None, 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""#; date = 2002/10/12"#;
let res = PageFrontMatter::parse(content).unwrap(); let res = PageFrontMatter::parse(content);
assert!(res.date().is_none()); 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());
}
} }

View file

@ -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" lazy_static = "1"
syntect = { version = "1", features = ["static-onig"] } syntect = "2"

View file

@ -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"

View file

@ -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" lazy_static = "1"
syntect = { version = "1", features = ["static-onig"] } syntect = "2"
pulldown-cmark = "0" pulldown-cmark = "0"
slug = "0.1" slug = "0.1"
serde = "1.0" serde = "1.0"

View file

@ -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>"
); );
} }

View file

@ -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" }

View file

@ -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,13 +217,31 @@ impl Site {
self.add_page(p, false)?; self.add_page(p, false)?;
} }
{ self.render_markdown()?;
self.populate_sections();
self.populate_tags_and_categories();
self.register_tera_global_fns();
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 // Another silly thing needed to not borrow &self in parallel and
// make the borrow checker happy // make the borrow checker happy
let permalinks = &self.permalinks; let permalinks = &self.permalinks;
let tera = &self.tera; let tera = &self.tera;
let config = &self.config; 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.pages.par_iter_mut() self.pages.par_iter_mut()
.map(|(_, page)| { .map(|(_, page)| {
let insert_anchor = pages_insert_anchors[&page.file.path]; let insert_anchor = pages_insert_anchors[&page.file.path];
@ -238,19 +254,18 @@ impl Site {
.map(|(_, section)| section.render_markdown(permalinks, tera, config)) .map(|(_, section)| section.render_markdown(permalinks, tera, config))
.fold(|| Ok(()), Result::and) .fold(|| Ok(()), Result::and)
.reduce(|| Ok(()), Result::and)?; .reduce(|| Ok(()), Result::and)?;
}
self.populate_sections();
self.populate_tags_and_categories();
self.register_tera_global_fns();
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);

View file

@ -1,6 +1,6 @@
+++ +++
title = "A draft" title = "A draft"
draft = true draft = true
date = "2016-03-01" date = 2016-03-01
+++ +++

View file

@ -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"]
+++ +++

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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">

View file

@ -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">

View file

@ -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"

View file

@ -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);
} }
} }

View file

@ -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" tera = "0.11.0"
base64 = "0.7" base64 = "0.9"
lazy_static = "0.2" 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" }

View file

@ -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 %}

View file

@ -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") { let path = required_string_arg!(args.get("path"), "`get_page` requires a `path` argument with a string value");
Some(val) => match from_value::<String>(val.clone()) { match pages.get(&path) {
Ok(v) => {
match pages.get(&v) {
Some(p) => Ok(to_value(p).unwrap()), Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Page `{}` not found.", v).into()) None => Err(format!("Page `{}` not found.", path).into())
}
},
Err(_) => Err(format!("`get_page` received path={:?} but it requires a string", val).into()),
},
None => Err("`get_page` requires a `path` argument.".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") { let path = required_string_arg!(args.get("path"), "`get_section` requires a `path` argument with a string value");
Some(val) => match from_value::<String>(val.clone()) { match sections.get(&path) {
Ok(v) => {
match sections.get(&v) {
Some(p) => Ok(to_value(p).unwrap()), Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Section `{}` not found.", v).into()) None => Err(format!("Section `{}` not found.", path).into())
}
},
Err(_) => Err(format!("`get_section` received path={:?} but it requires a string", val).into()),
},
None => Err("`get_section` requires a `path` argument.".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") { let trailing_slash = args
Some(val) => match from_value::<String>(val.clone()) { .get("trailing_slash")
Ok(v) => { .map_or(true, |c| {
// Internal link from_value::<bool>(c.clone()).unwrap_or(true)
if v.starts_with("./") { });
match resolve_internal_link(&v, &permalinks) {
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()), Ok(url) => Ok(to_value(url).unwrap()),
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", v).into()) Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", path).into())
} }
} else { } else {
// anything else // anything else
let mut permalink = config.make_permalink(&v); let mut permalink = config.make_permalink(&path);
if !trailing_slash && permalink.ends_with("/") {
permalink.pop(); // Removes the slash
}
if cachebust { if cachebust {
permalink = format!("{}?t={}", permalink, config.build_timestamp.unwrap()); permalink = format!("{}?t={}", permalink, config.build_timestamp.unwrap());
} }
Ok(to_value(permalink).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()), 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");
}
} }

View file

@ -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;

View file

@ -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]

View file

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

View file

@ -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" { // maybe it's a default one?
return Ok(String::new()); 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

View file

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

View file

@ -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"]

View file

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

View file

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

View file

@ -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]

View file

@ -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") }}
```

View file

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

View file

@ -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="stylesheet" href="{{ get_url(path="site.css", trailing_slash=false) }}"/>
<link rel="icon" href="{{ get_url(path="favicon.ico") }}"> <link rel="icon" href="{{ get_url(path="favicon.ico", trailing_slash=false) }}">
</head> </head>
<body> <body>

View file

@ -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"),
]), ]),
]) ])
} }

View file

@ -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();
} }

View file

@ -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`"); console::info("Get started by moving into the directory and using the built-in server: `gutenberg serve`");
println!("There is no built-in theme so you will see a white page."); println!("Visit https://www.getgutenberg.io for the full documentation.");
println!("Visit https://github.com/Keats/gutenberg for the full documentation.");
Ok(()) Ok(())
} }

View file

@ -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<()> { fn create_new_site(interface: &str, port: &str, output_dir: &str, config_file: &str) -> Result<(Site, String)> {
let start = Instant::now();
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 {

View file

@ -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);

View file

@ -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()?;

View file

@ -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: (\|\|\||&&&|^^^|<<<|>>>|~~~)

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.