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"]
path = sublime_syntaxes/LESS-sublime
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"]
path = sublime_syntaxes/Handlebars
url = https://github.com/daaain/Handlebars.git
@ -31,3 +28,6 @@
[submodule "sublime_syntaxes/Sublime-VimL"]
path = sublime_syntaxes/Sublime-VimL
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
language: rust
services: docker
sudo: required
env:
global:
@ -20,6 +19,9 @@ matrix:
rust: beta
- env: TARGET=x86_64-unknown-linux-gnu
rust: nightly
# The earliest stable Rust version that works
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.23.0
before_install: set -e

View file

@ -1,5 +1,26 @@
# 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)
- 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]
name = "gutenberg"
version = "0.2.2"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
version = "0.3.0"
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
license = "MIT"
readme = "README.md"
description = "Static site generator"
description = "A static site generator with everything built-in"
homepage = "https://github.com/Keats/gutenberg"
repository = "https://github.com/Keats/gutenberg"
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
url = "1.5"
# Below is for the serve cmd
staticfile = "0.4"
iron = "0.5"
mount = "0.3"
staticfile = "0.5"
iron = "0.6"
mount = "0.4"
notify = "4"
ws = "0.7"
ctrlc = "3"
site = { path = "components/site" }
errors = { path = "components/errors" }

View file

@ -1,7 +1,7 @@
#[macro_use]
extern crate clap;
use clap::Shell;
// use clap::Shell;
include!("src/cli.rs");

View file

@ -32,6 +32,8 @@ _arguments -s -S -C \
_arguments -s -S -C \
'-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)]' \
'-o+[Outputs the generated site in the given path]' \
'--output-dir+[Outputs the generated site in the given path]' \
'-h[Prints help information]' \
'--help[Prints help information]' \
'-V[Prints version information]' \
@ -44,6 +46,8 @@ _arguments -s -S -C \
'--interface+[Interface to bind on]' \
'-p+[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]' \
'--help[Prints help information]' \
'-V[Prints version information]' \

View file

@ -53,11 +53,11 @@
}
'_gutenberg_build' {
$completions = @('-h', '-V', '-u', '--help', '--version', '--base-url')
$completions = @('-h', '-V', '-u', '-o', '--help', '--version', '--base-url', '--output-dir')
}
'_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' {

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 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 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 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 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 V -l version -d 'Prints version 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
pub description: Option<String>,
/// 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
pub generate_rss: Option<bool>,
/// 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
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
pub extra: Option<HashMap<String, Toml>>,
@ -74,13 +77,14 @@ impl Config {
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.generate_rss, false);
set_default!(config.rss_limit, 20);
set_default!(config.generate_tags_pages, false);
set_default!(config.generate_categories_pages, false);
set_default!(config.compile_sass, false);
set_default!(config.translations, HashMap::new());
set_default!(config.extra, HashMap::new());
match config.highlight_theme {
@ -120,6 +124,8 @@ impl Config {
format!("{}{}{}", self.base_url, &path[1..], trailing_bit)
} else if self.base_url.ends_with('/') {
format!("{}{}{}", self.base_url, path, trailing_bit)
} else if path.starts_with('/') {
format!("{}{}{}", self.base_url, path, trailing_bit)
} else {
format!("{}/{}{}", self.base_url, path, trailing_bit)
}
@ -164,12 +170,13 @@ impl Default for Config {
highlight_code: Some(true),
highlight_theme: Some("base16-ocean-dark".to_string()),
description: None,
language_code: Some("en".to_string()),
default_language: Some("en".to_string()),
generate_rss: Some(false),
rss_limit: Some(10_000),
generate_tags_pages: Some(true),
generate_categories_pages: Some(true),
compile_sass: Some(false),
translations: None,
extra: None,
build_timestamp: Some(1),
}
@ -272,6 +279,13 @@ hello = "world"
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]
fn can_merge_with_theme_data_and_preserve_config_value() {
let config_str = r#"
@ -293,4 +307,27 @@ a_value = 10
assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string());
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>"]
[dependencies]
tera = "0.10"
tera = "0.11.0"
serde = "1.0"
slug = "0.1"
rayon = "0.8"
rayon = "0.9"
errors = { path = "../errors" }
config = { path = "../config" }
@ -17,3 +17,4 @@ front_matter = { path = "../front_matter" }
[dev-dependencies]
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![];
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 {
components.push(component.to_string());

View file

@ -11,6 +11,8 @@ extern crate utils;
#[cfg(test)]
extern crate tempdir;
#[cfg(test)]
extern crate toml;
mod file_info;
mod page;

View file

@ -103,7 +103,6 @@ impl Page {
if let Some(ref p) = page.meta.path {
page.path = p.trim().trim_left_matches('/').to_string();
} else {
page.path = if page.file.components.is_empty() {
page.slug.clone()
@ -207,7 +206,12 @@ impl ser::Serialize for Page {
state.serialize_field("content", &self.content)?;
state.serialize_field("title", &self.meta.title)?;
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("path", &self.path)?;
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)]
mod tests {
use std::str::FromStr;
use toml::value::Datetime;
use front_matter::{PageFrontMatter, SortBy};
use page::Page;
use super::{sort_pages, populate_previous_and_next_pages};
fn create_page_with_date(date: &str) -> Page {
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)
}
@ -136,9 +139,9 @@ mod tests {
];
let (pages, _) = sort_pages(input, SortBy::Date);
// Should be sorted by date
assert_eq!(pages[0].clone().meta.date.unwrap(), "2019-01-01");
assert_eq!(pages[1].clone().meta.date.unwrap(), "2018-01-01");
assert_eq!(pages[2].clone().meta.date.unwrap(), "2017-01-01");
assert_eq!(pages[0].clone().meta.date.unwrap().to_string(), "2019-01-01");
assert_eq!(pages[1].clone().meta.date.unwrap().to_string(), "2018-01-01");
assert_eq!(pages[2].clone().meta.date.unwrap().to_string(), "2017-01-01");
}
#[test]

View file

@ -4,6 +4,6 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
[dependencies]
error-chain = "0.10"
tera = "0.10"
error-chain = "0.11"
tera = "0.11.0"
toml = "0.4"

View file

@ -4,13 +4,13 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
[dependencies]
tera = "0.10"
tera = "0.11.0"
chrono = "0.4"
serde = "1.0"
serde_derive = "1.0"
toml = "0.4"
regex = "0.2"
lazy_static = "0.2"
lazy_static = "1"
errors = { path = "../errors" }

View file

@ -92,7 +92,7 @@ mod tests {
+++
title = "Title"
description = "hey there"
date = "2002/10/12"
date = 2002-10-12
+++
Hello
"#;
@ -120,7 +120,7 @@ Hello
+++
title = "Title"
description = "hey there"
date = "2002/10/12"
date = 2002-10-12
+++"#;
let (front_matter, content) = split_page_content(Path::new(""), content).unwrap();
assert_eq!(content, "");
@ -133,7 +133,7 @@ date = "2002/10/12"
+++
title = "Title"
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();
@ -147,7 +147,7 @@ date = "2002-10-02T15:00:00Z"
+++
title = "Title"
description = "hey there"
date = "2002/10/12""#;
date = 2002-10-12"#;
let res = split_page_content(Path::new(""), content);
assert!(res.is_err());
}

View file

@ -6,6 +6,7 @@ use toml;
use errors::Result;
/// The front matter of every page
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PageFrontMatter {
@ -14,7 +15,7 @@ pub struct PageFrontMatter {
/// Description in <meta> that appears when linked, e.g. on twitter
pub description: Option<String>,
/// 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
pub draft: Option<bool>,
/// The page slug. Will be used instead of the filename if present
@ -71,17 +72,17 @@ impl PageFrontMatter {
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> {
match self.date {
Some(ref d) => {
if d.contains('T') {
DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local()))
if let Some(ref d) = self.date {
let d2 = d.to_string();
if d2.contains('T') {
DateTime::parse_from_rfc3339(&d2).ok().and_then(|s| Some(s.naive_local()))
} else {
NaiveDate::parse_from_str(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)))
}
},
None => None,
} else {
None
}
}
@ -121,6 +122,7 @@ impl Default for PageFrontMatter {
}
}
#[cfg(test)]
mod tests {
use super::PageFrontMatter;
@ -203,9 +205,10 @@ mod tests {
let content = r#"
title = "Hello"
description = "hey there"
date = "2016-10-10""#;
date = 2016-10-10
"#;
let res = PageFrontMatter::parse(content).unwrap();
assert!(res.date().is_some());
assert!(res.date.is_some());
}
#[test]
@ -213,9 +216,10 @@ mod tests {
let content = r#"
title = "Hello"
description = "hey there"
date = "2002-10-02T15:00:00Z""#;
date = 2002-10-02T15:00:00Z
"#;
let res = PageFrontMatter::parse(content).unwrap();
assert!(res.date().is_some());
assert!(res.date.is_some());
}
#[test]
@ -223,9 +227,28 @@ mod tests {
let content = r#"
title = "Hello"
description = "hey there"
date = "2002/10/12""#;
let res = PageFrontMatter::parse(content).unwrap();
assert!(res.date().is_none());
date = 2002/10/12"#;
let res = PageFrontMatter::parse(content);
assert!(res.is_err());
}
#[test]
fn cannot_parse_invalid_date_format() {
let content = r#"
title = "Hello"
description = "hey there"
date = 2002-14-01"#;
let res = PageFrontMatter::parse(content);
assert!(res.is_err());
}
#[test]
fn cannot_parse_date_as_string() {
let content = r#"
title = "Hello"
description = "hey there"
date = "2002-14-01""#;
let res = PageFrontMatter::parse(content);
assert!(res.is_err());
}
}

View file

@ -4,5 +4,5 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
[dependencies]
lazy_static = "0.2"
syntect = { version = "1", features = ["static-onig"] }
lazy_static = "1"
syntect = "2"

View file

@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
[dependencies]
tera = "0.10"
tera = "0.11.0"
serde = "1.0"
serde_derive = "1.0"

View file

@ -4,10 +4,10 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
[dependencies]
tera = "0.10"
tera = "0.11.0"
regex = "0.2"
lazy_static = "0.2"
syntect = { version = "1", features = ["static-onig"] }
lazy_static = "1"
syntect = "2"
pulldown-cmark = "0"
slug = "0.1"
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();
assert_eq!(
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>"]
[dependencies]
tera = "0.10"
tera = "0.11.0"
glob = "0.2"
walkdir = "2"
rayon = "0.8"
rayon = "0.9"
serde = "1.0"
serde_derive = "1.0"
sass-rs = "0.2"
#sass-rs = { git = "https://github.com/compass-rs/sass-rs.git" }
errors = { path = "../errors" }
config = { path = "../config" }

View file

@ -151,8 +151,6 @@ impl Site {
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) {
self.output_path = path.as_ref().to_path_buf();
}
@ -219,13 +217,31 @@ impl Site {
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
// make the borrow checker happy
let permalinks = &self.permalinks;
let tera = &self.tera;
let config = &self.config;
// TODO: avoid the duplication with function above for that part
// This is needed in the first place because of silly borrow checker
let mut pages_insert_anchors = HashMap::new();
for (_, p) in &self.pages {
pages_insert_anchors.insert(p.file.path.clone(), self.find_parent_section_insert_anchor(&p.file.parent.clone()));
}
self.pages.par_iter_mut()
.map(|(_, page)| {
let insert_anchor = pages_insert_anchors[&page.file.path];
@ -238,19 +254,18 @@ impl Site {
.map(|(_, section)| section.render_markdown(permalinks, tera, config))
.fold(|| Ok(()), Result::and)
.reduce(|| Ok(()), Result::and)?;
}
self.populate_sections();
self.populate_tags_and_categories();
self.register_tera_global_fns();
Ok(())
}
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_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(
"get_url",
global_fns::make_get_url(self.permalinks.clone(), self.config.clone())
@ -318,6 +333,8 @@ impl Site {
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() {
let parent_section_path = page.file.parent.join("_index.md");
if self.sections.contains_key(&parent_section_path) {
@ -443,7 +460,7 @@ impl Site {
pub fn clean(&self) -> Result<()> {
if self.output_path.exists() {
// 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(())
@ -620,7 +637,13 @@ impl Site {
&self.pages
.values()
.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<_>>()
);
context.add(
@ -678,7 +701,7 @@ impl Site {
}
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)
context.add("pages", &sorted_pages.iter().take(self.config.rss_limit.unwrap()).collect::<Vec<_>>());
context.add("config", &self.config);

View file

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

View file

@ -2,7 +2,7 @@
title = "Fixed slug"
description = ""
slug = "something-else"
date = "2017-01-01"
date = 2017-01-01
aliases = ["/an-old-url/old-page"]
+++

View file

@ -2,7 +2,7 @@
title = "Fixed URL"
description = ""
path = "a-fixed-url"
date = "2017-02-01"
date = 2017-02-01
+++
A simple page with fixed url

View file

@ -1,7 +1,7 @@
+++
title = "Python in posts"
description = ""
date = "2017-03-01"
date = 2017-03-01
+++
Same filename but different path

View file

@ -1,7 +1,7 @@
+++
title = "Simple article with shortcodes"
description = ""
date = "2017-04-01"
date = 2017-04-01
+++
A simple page

View file

@ -1,7 +1,7 @@
+++
title = "Docker"
order = 1
date = "2017-01-01"
date = 2017-01-01
+++
A simple page

View file

@ -1,7 +1,7 @@
+++
title = "Nix"
order = 2
date = "2017-01-01"
date = 2017-01-01
+++
A simple page

View file

@ -1,7 +1,7 @@
+++
title = "Python tutorial"
order = 1
date = "2017-01-01"
date = 2017-01-01
+++
A simple page

View file

@ -1,7 +1,7 @@
+++
title = "Rust"
order = 2
date = "2017-01-01"
date = 2017-01-01
+++
A simple page

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{ config.language_code }}">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="apple-mobile-web-app-capable" content="yes">

View file

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{ config.language_code }}">
<html lang="en">
<head>
<meta charset="UTF-8">
<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>"]
[dependencies]
tera = "0.10"
tera = "0.11.0"
slug = "0.1"
serde = "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 slug = slugify(name);
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))
};
@ -188,27 +188,27 @@ mod tests {
assert_eq!(tags.items[0].name, "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[1].name, "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[2].name, "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!(categories.items[0].name, "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[1].name, "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);
}
}

View file

@ -4,12 +4,13 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
[dependencies]
tera = "0.10"
base64 = "0.7"
lazy_static = "0.2"
tera = "0.11.0"
base64 = "0.9"
lazy_static = "1"
pulldown-cmark = "0"
errors = { path = "../errors" }
utils = { path = "../utils" }
content = { path = "../content" }
config = { path = "../config" }
taxonomies = { path = "../taxonomies" }

View file

@ -4,7 +4,7 @@
<link>{{ config.base_url }}</link>
<description>{{ config.description }}</description>
<generator>Gutenberg</generator>
<language>{{ config.language_code }}</language>
<language>{{ config.default_language }}</language>
<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>
{% for page in pages %}

View file

@ -6,6 +6,34 @@ use tera::{GlobalFn, Value, from_value, to_value, Result};
use content::{Page, Section};
use config::Config;
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 {
@ -15,17 +43,10 @@ pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn {
}
Box::new(move |args| -> Result<Value> {
match args.get("path") {
Some(val) => match from_value::<String>(val.clone()) {
Ok(v) => {
match pages.get(&v) {
let path = required_string_arg!(args.get("path"), "`get_page` requires a `path` argument with a string value");
match pages.get(&path) {
Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Page `{}` not found.", v).into())
}
},
Err(_) => Err(format!("`get_page` received path={:?} but it requires a string", val).into()),
},
None => Err("`get_page` requires a `path` argument.".into()),
None => Err(format!("Page `{}` not found.", path).into())
}
})
}
@ -37,17 +58,10 @@ pub fn make_get_section(all_sections: &HashMap<PathBuf, Section>) -> GlobalFn {
}
Box::new(move |args| -> Result<Value> {
match args.get("path") {
Some(val) => match from_value::<String>(val.clone()) {
Ok(v) => {
match sections.get(&v) {
let path = required_string_arg!(args.get("path"), "`get_section` requires a `path` argument with a string value");
match sections.get(&path) {
Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Section `{}` not found.", v).into())
}
},
Err(_) => Err(format!("`get_section` received path={:?} but it requires a string", val).into()),
},
None => Err("`get_section` requires a `path` argument.".into()),
None => Err(format!("Section `{}` not found.", path).into())
}
})
}
@ -60,40 +74,66 @@ pub fn make_get_url(permalinks: HashMap<String, String>, config: Config) -> Glob
from_value::<bool>(c.clone()).unwrap_or(false)
});
match args.get("path") {
Some(val) => match from_value::<String>(val.clone()) {
Ok(v) => {
// Internal link
if v.starts_with("./") {
match resolve_internal_link(&v, &permalinks) {
let trailing_slash = args
.get("trailing_slash")
.map_or(true, |c| {
from_value::<bool>(c.clone()).unwrap_or(true)
});
let path = required_string_arg!(args.get("path"), "`get_url` requires a `path` argument with a string value");
if path.starts_with("./") {
match resolve_internal_link(&path, &permalinks) {
Ok(url) => Ok(to_value(url).unwrap()),
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", v).into())
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", path).into())
}
} 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 {
permalink = format!("{}?t={}", permalink, config.build_timestamp.unwrap());
}
Ok(to_value(permalink).unwrap())
}
},
Err(_) => Err(format!("`get_url` received path={:?} but it requires a string", val).into()),
},
None => Err("`get_url` requires a `path` argument.".into()),
})
}
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)]
mod tests {
use super::make_get_url;
use super::{make_get_url, make_get_taxonomy_url, make_trans};
use std::collections::HashMap;
use tera::to_value;
use config::Config;
use taxonomies::{Taxonomy, TaxonomyKind, TaxonomyItem};
#[test]
@ -106,6 +146,27 @@ mod tests {
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]
fn can_link_to_some_static_file() {
let config = Config::default();
@ -114,4 +175,59 @@ mod tests {
args.insert("path".to_string(), to_value("app.css").unwrap());
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 pulldown_cmark;
#[macro_use]
extern crate errors;
extern crate utils;
extern crate content;
extern crate config;
extern crate taxonomies;
pub mod filters;
pub mod global_fns;

View file

@ -5,7 +5,7 @@ authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
[dependencies]
errors = { path = "../errors" }
tera = "0.10"
tera = "0.11.0"
[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;
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
/// 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
@ -19,11 +33,19 @@ pub fn render_template(name: &str, tera: &Tera, context: &Context, theme: Option
.map_err(|e| e.into());
}
if name == "index.html" || name == "section.html" || name == "page.html" {
return Ok(String::new());
// maybe it's a default one?
match name {
"index.html" | "section.html" => {
render_default_tpl!(name, "https://www.getgutenberg.io/documentation/templates/pages-sections/#section-variables")
},
"page.html" => {
render_default_tpl!(name, "https://www.getgutenberg.io/documentation/templates/pages-sections/#page-variables")
},
"tag.html" | "tags.html" | "category.html" | "categories.html" => {
render_default_tpl!(name, "https://www.getgutenberg.io/documentation/templates/tags-categories/")
},
_ => bail!("Tried to render `{}` but the template wasn't found", name)
}
bail!("Tried to render `{}` but the template wasn't found", name)
}
@ -55,7 +77,7 @@ mod tests {
#[test]
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");
// special case to make the test work: we also rename the files to
// match the imports

View file

@ -22,6 +22,7 @@ description = ""
# The date of the post.
# 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 = ""
# 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"]
- Julia -> ["jl"]
- Kotlin -> ["kt", "kts"]
- LESS -> ["less"]
- Less -> ["less", "css.less"]
- Nim -> ["nim", "nims"]
- ASP -> ["asa"]
- HTML (ASP) -> ["asp"]
- 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 -> ["java", "bsh"]
- 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"]
- BibTeX -> ["bib"]
- LaTeX -> ["tex", "ltx"]
- 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"]
- Makefile -> ["make", "GNUmakefile", "makefile", "Makefile", "OCamlMakefile", "mak", "mk"]
- 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"]
- SQL -> ["sql", "ddl", "dml"]
- 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"]
- Tcl -> ["tcl"]
- Textile -> ["textile"]
- XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"]
- 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"]
- TOML -> ["toml", "tml"]
- TypeScript -> ["ts"]

View file

@ -15,9 +15,9 @@ are available at the following paths:
```plain
$BASE_URL/tags/
$BASE_URL/tag/$TAG_SLUG
$BASE_URL/tags/$TAG_SLUG
$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.

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
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
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 --interface 0.0.0.0
$ 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

View file

@ -21,7 +21,8 @@ base_url = "mywebsite.com"
# Used in RSS by default
title = ""
description = ""
language_code = "en"
# the default language, used in RSS and coming i18n
default_language = "en"
# Theme name to use
theme = ""
@ -50,6 +51,9 @@ generate_categories_pages = false
# Whether to compile the Sass files found in the `sass` directory
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
# will be accessible in all templates
[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") }}
```
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
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
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="author" content="{{ config.extra.author }}">
<title>{% block title %}{{ config.title }}{% endblock title %}</title>
<link rel="stylesheet" href="{{ get_url(path="site.css") }}"/>
<link rel="icon" href="{{ get_url(path="favicon.ico") }}">
<link rel="stylesheet" href="{{ get_url(path="site.css", trailing_slash=false) }}"/>
<link rel="icon" href="{{ get_url(path="favicon.ico", trailing_slash=false) }}">
</head>
<body>

View file

@ -28,6 +28,12 @@ pub fn build_cli() -> App<'static, 'static> {
.long("base-url")
.takes_value(true)
.help("Force the base URL to be that value (default to the one in config.toml)"),
Arg::with_name("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")
.about("Serve the site. Rebuild and reload on change automatically")
@ -42,6 +48,12 @@ pub fn build_cli() -> App<'static, 'static> {
.long("port")
.default_value("1111")
.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;
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)?;
site.set_output_path(output_dir);
if let Some(b) = base_url {
site.config.base_url = b.to_string();
}

View file

@ -57,8 +57,7 @@ pub fn create_new_project(name: &str) -> Result<()> {
println!();
console::success(&format!("Done! Your site was created in {:?}", canonicalize(path).unwrap()));
println!();
console::info("Get started by using the built-in server: `gutenberg serve`");
println!("There is no built-in theme so you will see a white page.");
println!("Visit https://github.com/Keats/gutenberg for the full documentation.");
console::info("Get started by moving into the directory and using the built-in server: `gutenberg serve`");
println!("Visit https://www.getgutenberg.io for the full documentation.");
Ok(())
}

View file

@ -22,6 +22,7 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
use std::env;
use std::fs::remove_dir_all;
use std::path::Path;
use std::sync::mpsc::channel;
use std::time::{Instant, Duration};
@ -33,6 +34,8 @@ use mount::Mount;
use staticfile::Static;
use notify::{Watcher, RecursiveMode, watcher};
use ws::{WebSocket, Sender, Message};
use ctrlc;
use site::Site;
use errors::{Result, ResultExt};
@ -45,6 +48,7 @@ enum ChangeKind {
Templates,
StaticFiles,
Sass,
Config,
}
// Uglified using uglifyjs
@ -77,8 +81,7 @@ fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &st
}
}
pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
let start = Instant::now();
fn create_new_site(interface: &str, port: &str, output_dir: &str, config_file: &str) -> Result<(Site, String)> {
let mut site = Site::new(env::current_dir().unwrap(), config_file)?;
let address = format!("{}:{}", interface, port);
@ -88,22 +91,30 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
} else {
format!("http://{}", address)
};
site.set_output_path(output_dir);
site.load()?;
site.enable_live_reload();
console::notify_site_size(&site);
console::warn_about_ignored_pages(&site);
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);
let mut watching_static = false;
// Setup watchers
let mut watching_static = false;
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
watcher.watch("content/", RecursiveMode::Recursive)
.chain_err(|| "Can't watch the `content` folder. Does it exist?")?;
watcher.watch("templates/", RecursiveMode::Recursive)
.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() {
watching_static = true;
@ -116,9 +127,9 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
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();
mount.mount("/", Static::new(Path::new("public/")));
mount.mount("/", Static::new(Path::new(output_dir)));
mount.mount("/livereload.js", livereload_handler);
// Starts with a _ to not trigger the unused lint
// 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 mut watchers = vec!["content", "templates"];
let mut watchers = vec!["content", "templates", "config.toml"];
if watching_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!("Web server is available at http://{}", address);
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::*;
@ -196,6 +213,10 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
console::info(&format!("-> Sass file changed {}", path.display()));
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);
}
@ -249,6 +270,8 @@ fn detect_change_kind(pwd: &str, path: &Path) -> (ChangeKind, String) {
ChangeKind::StaticFiles
} else if path_str.starts_with("/sass") {
ChangeKind::Sass
} else if path_str == "/config.toml" {
ChangeKind::Config
} else {
unreachable!("Got a change in an unexpected path: {}", path_str)
};
@ -299,7 +322,11 @@ mod tests {
(
(ChangeKind::Sass, "/sass/print.scss".to_string()),
"/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 {

View file

@ -8,6 +8,7 @@ extern crate mount;
extern crate notify;
extern crate url;
extern crate ws;
extern crate ctrlc;
extern crate site;
#[macro_use]
@ -43,7 +44,8 @@ fn main() {
("build", Some(matches)) => {
console::info("Building site...");
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),
Err(e) => {
console::unravel_errors("Failed to build the site", &e);
@ -54,8 +56,9 @@ fn main() {
("serve", Some(matches)) => {
let interface = matches.value_of("interface").unwrap_or("127.0.0.1");
let port = matches.value_of("port").unwrap_or("1111");
let output_dir = matches.value_of("output_dir").unwrap();
console::info("Building site...");
match cmd::serve(interface, port, config_file) {
match cmd::serve(interface, port, output_dir, config_file) {
Ok(()) => (),
Err(e) => {
console::unravel_errors("", &e);

View file

@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, Component};
use errors::Result;
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
pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
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(),
"rss.xml" => site.render_rss_feed(),
"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
// 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_orphan_pages()?;
site.render_categories()?;

View file

@ -25,7 +25,7 @@ contexts:
captures:
1: keyword.operator.other.elixir
2: keyword.control.elixir
3: punctuation.definition.parameters.elixir
3: punctuation.section.function.elixir
pop: true
- include: core_syntax
- include: core_syntax
@ -37,7 +37,7 @@ contexts:
captures:
1: keyword.operator.other.elixir
2: keyword.control.elixir
3: punctuation.definition.parameters.elixir
3: punctuation.section.function.elixir
pop: true
- include: core_syntax
core_syntax:
@ -78,7 +78,7 @@ contexts:
captures:
1: keyword.control.module.elixir
2: entity.name.function.public.elixir
4: punctuation.definition.parameters.elixir
4: punctuation.section.function.elixir
push:
- meta_scope: meta.function.public.elixir
- match: (\bdo:)|(\bdo\b)|(?=\s+(def|defmacro)\b)
@ -100,7 +100,7 @@ contexts:
captures:
1: keyword.control.module.elixir
2: entity.name.function.private.elixir
4: punctuation.definition.parameters.elixir
4: punctuation.section.function.elixir
push:
- meta_scope: meta.function.private.elixir
- match: (\bdo:)|(\bdo\b)|(?=\s+(defp|defmacrop)\b)
@ -157,18 +157,6 @@ contexts:
pop: true
- include: interpolated_elixir
- 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(?![?!:])'
scope: keyword.control.elixir
- match: (?<!\.)\b(and|not|or|when|xor|in)\b
@ -433,6 +421,14 @@ contexts:
the negative lookbehind prevents against matching
p(42.tainted?)
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: "===?|!==?|<=?|>=?"
scope: keyword.operator.comparison.elixir
- 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.