@@ -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 |
@@ -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 | |||
@@ -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 | |||
@@ -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" } | |||
@@ -1,7 +1,7 @@ | |||
#[macro_use] | |||
extern crate clap; | |||
use clap::Shell; | |||
// use clap::Shell; | |||
include!("src/cli.rs"); | |||
@@ -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]' \ | |||
@@ -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' { | |||
@@ -0,0 +1,153 @@ | |||
_gutenberg() { | |||
local i cur prev opts cmds | |||
COMPREPLY=() | |||
cur="${COMP_WORDS[COMP_CWORD]}" | |||
prev="${COMP_WORDS[COMP_CWORD-1]}" | |||
cmd="" | |||
opts="" | |||
for i in ${COMP_WORDS[@]} | |||
do | |||
case "${i}" in | |||
gutenberg) | |||
cmd="gutenberg" | |||
;; | |||
build) | |||
cmd+="__build" | |||
;; | |||
help) | |||
cmd+="__help" | |||
;; | |||
init) | |||
cmd+="__init" | |||
;; | |||
serve) | |||
cmd+="__serve" | |||
;; | |||
*) | |||
;; | |||
esac | |||
done | |||
case "${cmd}" in | |||
gutenberg) | |||
opts=" -c -h -V --config --help --version init build serve help" | |||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
fi | |||
case "${prev}" in | |||
*) | |||
COMPREPLY=() | |||
;; | |||
esac | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
;; | |||
gutenberg__build) | |||
opts=" -h -V -u -o --help --version --base-url --output-dir " | |||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
fi | |||
case "${prev}" in | |||
--base-url) | |||
COMPREPLY=("<base_url>") | |||
return 0 | |||
;; | |||
-u) | |||
COMPREPLY=("<base_url>") | |||
return 0 | |||
;; | |||
--output-dir) | |||
COMPREPLY=("<output_dir>") | |||
return 0 | |||
;; | |||
-o) | |||
COMPREPLY=("<output_dir>") | |||
return 0 | |||
;; | |||
*) | |||
COMPREPLY=() | |||
;; | |||
esac | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
;; | |||
gutenberg__help) | |||
opts=" -h -V --help --version " | |||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
fi | |||
case "${prev}" in | |||
*) | |||
COMPREPLY=() | |||
;; | |||
esac | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
;; | |||
gutenberg__init) | |||
opts=" -h -V --help --version <name> " | |||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
fi | |||
case "${prev}" in | |||
*) | |||
COMPREPLY=() | |||
;; | |||
esac | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
;; | |||
gutenberg__serve) | |||
opts=" -h -V -i -p -o --help --version --interface --port --output-dir " | |||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
fi | |||
case "${prev}" in | |||
--interface) | |||
COMPREPLY=("<interface>") | |||
return 0 | |||
;; | |||
-i) | |||
COMPREPLY=("<interface>") | |||
return 0 | |||
;; | |||
--port) | |||
COMPREPLY=("<port>") | |||
return 0 | |||
;; | |||
-p) | |||
COMPREPLY=("<port>") | |||
return 0 | |||
;; | |||
--output-dir) | |||
COMPREPLY=("<output_dir>") | |||
return 0 | |||
;; | |||
-o) | |||
COMPREPLY=("<output_dir>") | |||
return 0 | |||
;; | |||
*) | |||
COMPREPLY=() | |||
;; | |||
esac | |||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) | |||
return 0 | |||
;; | |||
esac | |||
} | |||
complete -F _gutenberg -o bashdefault -o default gutenberg |
@@ -21,10 +21,12 @@ complete -c gutenberg -n "__fish_using_command gutenberg" -f -a "help" -d 'Print | |||
complete -c gutenberg -n "__fish_using_command gutenberg init" -s h -l help -d 'Prints help information' | |||
complete -c gutenberg -n "__fish_using_command gutenberg init" -s 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' | |||
@@ -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"); | |||
} | |||
} |
@@ -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" |
@@ -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()); | |||
@@ -11,6 +11,8 @@ extern crate utils; | |||
#[cfg(test)] | |||
extern crate tempdir; | |||
#[cfg(test)] | |||
extern crate toml; | |||
mod file_info; | |||
mod page; | |||
@@ -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)?; | |||
@@ -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] | |||
@@ -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" |
@@ -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" } |
@@ -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()); | |||
} | |||
@@ -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())) | |||
} else { | |||
NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0))) | |||
} | |||
}, | |||
None => None, | |||
if let Some(ref d) = self.date { | |||
let d2 = d.to_string(); | |||
if d2.contains('T') { | |||
DateTime::parse_from_rfc3339(&d2).ok().and_then(|s| Some(s.naive_local())) | |||
} else { | |||
NaiveDate::parse_from_str(&d2, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0, 0, 0))) | |||
} | |||
} else { | |||
None | |||
} | |||
} | |||
@@ -121,6 +122,7 @@ impl Default for PageFrontMatter { | |||
} | |||
} | |||
#[cfg(test)] | |||
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()); | |||
} | |||
} |
@@ -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" |
@@ -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" | |||
@@ -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" | |||
@@ -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>" | |||
); | |||
} | |||
@@ -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" } | |||
@@ -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,38 +217,55 @@ impl Site { | |||
self.add_page(p, false)?; | |||
} | |||
{ | |||
// Another silly thing needed to not borrow &self in parallel and | |||
// make the borrow checker happy | |||
let permalinks = &self.permalinks; | |||
let tera = &self.tera; | |||
let config = &self.config; | |||
self.render_markdown()?; | |||
self.populate_sections(); | |||
self.populate_tags_and_categories(); | |||
self.pages.par_iter_mut() | |||
.map(|(_, page)| { | |||
let insert_anchor = pages_insert_anchors[&page.file.path]; | |||
page.render_markdown(permalinks, tera, config, insert_anchor) | |||
}) | |||
.fold(|| Ok(()), Result::and) | |||
.reduce(|| Ok(()), Result::and)?; | |||
self.register_tera_global_fns(); | |||
self.sections.par_iter_mut() | |||
.map(|(_, section)| section.render_markdown(permalinks, tera, config)) | |||
.fold(|| Ok(()), Result::and) | |||
.reduce(|| Ok(()), Result::and)?; | |||
Ok(()) | |||
} | |||
/// Render the markdown of all pages/sections | |||
/// Used in a build and in `serve` if a shortcode has changed | |||
pub fn render_markdown(&mut self) -> Result<()> { | |||
// Another silly thing needed to not borrow &self in parallel and | |||
// make the borrow checker happy | |||
let permalinks = &self.permalinks; | |||
let tera = &self.tera; | |||
let config = &self.config; | |||
// TODO: avoid the duplication with function above for that part | |||
// This is needed in the first place because of silly borrow checker | |||
let mut pages_insert_anchors = HashMap::new(); | |||
for (_, p) in &self.pages { | |||
pages_insert_anchors.insert(p.file.path.clone(), self.find_parent_section_insert_anchor(&p.file.parent.clone())); | |||
} | |||
self.populate_sections(); | |||
self.populate_tags_and_categories(); | |||
self.pages.par_iter_mut() | |||
.map(|(_, page)| { | |||
let insert_anchor = pages_insert_anchors[&page.file.path]; | |||
page.render_markdown(permalinks, tera, config, insert_anchor) | |||
}) | |||
.fold(|| Ok(()), Result::and) | |||
.reduce(|| Ok(()), Result::and)?; | |||
self.register_tera_global_fns(); | |||
self.sections.par_iter_mut() | |||
.map(|(_, section)| section.render_markdown(permalinks, tera, config)) | |||
.fold(|| Ok(()), Result::and) | |||
.reduce(|| Ok(()), Result::and)?; | |||
Ok(()) | |||
} | |||
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); | |||
@@ -1,6 +1,6 @@ | |||
+++ | |||
title = "A draft" | |||
draft = true | |||
date = "2016-03-01" | |||
date = 2016-03-01 | |||
+++ | |||
@@ -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"] | |||
+++ | |||
@@ -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 |
@@ -1,7 +1,7 @@ | |||
+++ | |||
title = "Python in posts" | |||
description = "" | |||
date = "2017-03-01" | |||
date = 2017-03-01 | |||
+++ | |||
Same filename but different path | |||
@@ -1,7 +1,7 @@ | |||
+++ | |||
title = "Simple article with shortcodes" | |||
description = "" | |||
date = "2017-04-01" | |||
date = 2017-04-01 | |||
+++ | |||
A simple page | |||
@@ -1,7 +1,7 @@ | |||
+++ | |||
title = "Docker" | |||
order = 1 | |||
date = "2017-01-01" | |||
date = 2017-01-01 | |||
+++ | |||
A simple page |
@@ -1,7 +1,7 @@ | |||
+++ | |||
title = "Nix" | |||
order = 2 | |||
date = "2017-01-01" | |||
date = 2017-01-01 | |||
+++ | |||
A simple page |
@@ -1,7 +1,7 @@ | |||
+++ | |||
title = "Python tutorial" | |||
order = 1 | |||
date = "2017-01-01" | |||
date = 2017-01-01 | |||
+++ | |||
A simple page |
@@ -1,7 +1,7 @@ | |||
+++ | |||
title = "Rust" | |||
order = 2 | |||
date = "2017-01-01" | |||
date = 2017-01-01 | |||
+++ | |||
A simple page |
@@ -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"> | |||
@@ -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"> | |||
@@ -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" | |||
@@ -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); | |||
} | |||
} |
@@ -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" } |
@@ -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 %} | |||
@@ -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) { | |||
Some(p) => Ok(to_value(p).unwrap()), | |||
None => Err(format!("Page `{}` not found.", v).into()) | |||
} | |||
}, | |||
Err(_) => Err(format!("`get_page` received path={:?} but it requires a string", val).into()), | |||
}, | |||
None => Err("`get_page` requires a `path` argument.".into()), | |||
let path = required_string_arg!(args.get("path"), "`get_page` requires a `path` argument with a string value"); | |||
match pages.get(&path) { | |||
Some(p) => Ok(to_value(p).unwrap()), | |||
None => Err(format!("Page `{}` not found.", path).into()) | |||
} | |||
}) | |||
} | |||
@@ -37,17 +58,10 @@ pub fn make_get_section(all_sections: &HashMap<PathBuf, Section>) -> GlobalFn { | |||
} | |||
Box::new(move |args| -> Result<Value> { | |||
match args.get("path") { | |||
Some(val) => match from_value::<String>(val.clone()) { | |||
Ok(v) => { | |||
match sections.get(&v) { | |||
Some(p) => Ok(to_value(p).unwrap()), | |||
None => Err(format!("Section `{}` not found.", v).into()) | |||
} | |||
}, | |||
Err(_) => Err(format!("`get_section` received path={:?} but it requires a string", val).into()), | |||
}, | |||
None => Err("`get_section` requires a `path` argument.".into()), | |||
let path = required_string_arg!(args.get("path"), "`get_section` requires a `path` argument with a string value"); | |||
match sections.get(&path) { | |||
Some(p) => Ok(to_value(p).unwrap()), | |||
None => Err(format!("Section `{}` not found.", path).into()) | |||
} | |||
}) | |||
} | |||
@@ -60,40 +74,66 @@ pub fn make_get_url(permalinks: HashMap<String, String>, config: Config) -> Glob | |||
from_value::<bool>(c.clone()).unwrap_or(false) | |||
}); | |||
match args.get("path") { | |||
Some(val) => match from_value::<String>(val.clone()) { | |||
Ok(v) => { | |||
// Internal link | |||
if v.starts_with("./") { | |||
match resolve_internal_link(&v, &permalinks) { | |||
Ok(url) => Ok(to_value(url).unwrap()), | |||
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", v).into()) | |||
} | |||
} else { | |||
// anything else | |||
let mut permalink = config.make_permalink(&v); | |||
if cachebust { | |||
permalink = format!("{}?t={}", permalink, config.build_timestamp.unwrap()); | |||
} | |||
Ok(to_value(permalink).unwrap()) | |||
} | |||
}, | |||
Err(_) => Err(format!("`get_url` received path={:?} but it requires a string", val).into()), | |||
}, | |||
None => Err("`get_url` requires a `path` argument.".into()), | |||
let trailing_slash = args | |||
.get("trailing_slash") | |||
.map_or(true, |c| { | |||
from_value::<bool>(c.clone()).unwrap_or(true) | |||
}); | |||
let path = required_string_arg!(args.get("path"), "`get_url` requires a `path` argument with a string value"); | |||
if path.starts_with("./") { | |||
match resolve_internal_link(&path, &permalinks) { | |||
Ok(url) => Ok(to_value(url).unwrap()), | |||
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", path).into()) | |||
} | |||
} else { | |||
// anything else | |||
let mut permalink = config.make_permalink(&path); | |||
if !trailing_slash && permalink.ends_with("/") { | |||
permalink.pop(); // Removes the slash | |||
} | |||
if cachebust { | |||
permalink = format!("{}?t={}", permalink, config.build_timestamp.unwrap()); | |||
} | |||
Ok(to_value(permalink).unwrap()) | |||
} | |||
}) | |||
} | |||
pub fn make_get_taxonomy_url(tags: Option<Taxonomy>, categories: Option<Taxonomy>) -> GlobalFn { | |||
Box::new(move |args| -> Result<Value> { | |||
let kind = required_string_arg!(args.get("kind"), "`get_taxonomy_url` requires a `kind` argument with a string value"); | |||
let name = required_string_arg!(args.get("name"), "`get_taxonomy_url` requires a `name` argument with a string value"); | |||
let container = match kind.as_ref() { | |||
"tag" => &tags, | |||
"category" => &categories, | |||
_ => return Err("`get_taxonomy_url` can only get `tag` or `category` for the `kind` argument".into()), | |||
}; | |||
if let Some(ref c) = *container { | |||
for item in &c.items { | |||
if item.name == name { | |||
return Ok(to_value(item.permalink.clone()).unwrap()); | |||
} | |||
} | |||
bail!("`get_taxonomy_url`: couldn't find `{}` in `{}` taxonomy", name, kind); | |||
} else { | |||
bail!("`get_taxonomy_url` tried to get a taxonomy of kind `{}` but there isn't any", kind); | |||
} | |||
}) | |||
} | |||
#[cfg(test)] | |||
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"); | |||
} | |||
} |
@@ -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; | |||
@@ -5,7 +5,7 @@ authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | |||
[dependencies] | |||
errors = { path = "../errors" } | |||
tera = "0.10" | |||
tera = "0.11.0" | |||
[dev-dependencies] | |||
@@ -0,0 +1,43 @@ | |||
<html> | |||
<head> | |||
<title>Gutenberg</title> | |||
</head> | |||
<body> | |||
<div class="container"> | |||
<h1>Welcome to Gutenberg!</h1> | |||
<p> | |||
You're seeing this page because we couldn't find a template to render. | |||
</p> | |||
<p> | |||
To modify this page, create a <b>{{filename}}</b> file in the templates directory or | |||
<a href="https://www.getgutenberg.io/documentation/themes/installing-and-using-themes/" target="_blank">install a theme</a>. | |||
<br> | |||
You can find what variables are available in this template in the <a href="{{url}}" target="_blank">documentation</a>. | |||
</p> | |||
</div> | |||
<footer> | |||
<a href="https://www.getgutenberg.io/documentation/getting-started/cli-usage/" target="_blank">Get started with Gutenberg</a> | |||
</footer> | |||
<style> | |||
html { | |||
line-height: 1.5; | |||
} | |||
h1 { | |||
margin-bottom: 2rem; | |||
} | |||
.container { | |||
font-family: "sans-serif"; | |||
text-align: center; | |||
margin-top: 20vh; | |||
padding: 2rem; | |||
background: #e9e9e9; | |||
} | |||
footer { | |||
position: fixed; | |||
width: 100%; | |||
bottom: 1rem; | |||
text-align: center; | |||
} | |||
</style> | |||
</body> | |||
</html> |
@@ -2,6 +2,20 @@ use tera::{Tera, Context}; | |||
use errors::Result; | |||
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 | |||
@@ -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 | |||
@@ -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"] | |||
@@ -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. |
@@ -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 | |||
@@ -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] | |||
@@ -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") }} | |||
``` |
@@ -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. | |||
@@ -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> | |||
@@ -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"), | |||
]), | |||
]) | |||
} |
@@ -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(); | |||
} | |||
@@ -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(()) | |||
} |
@@ -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 { | |||
@@ -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); | |||
@@ -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()?; | |||
@@ -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: (\|\|\||&&&|^^^|<<<|>>>|~~~) | |||
@@ -1,200 +0,0 @@ | |||
%YAML 1.2 | |||
--- | |||
# http://www.sublimetext.com/docs/3/syntax.html | |||
name: Elm | |||
file_extensions: | |||
- elm | |||
scope: source.elm | |||
contexts: | |||
main: | |||
- match: "(`)[a-zA-Z_']*?(`)" | |||
scope: keyword.operator.function.infix.elm | |||
captures: | |||
1: punctuation.definition.entity.elm | |||
2: punctuation.definition.entity.elm | |||
- match: \(\) | |||
scope: constant.language.unit.elm | |||
- match: ^\b((effect|port)\s+)?(module)\s+ | |||
captures: | |||
1: keyword.other.elm | |||
3: keyword.other.elm | |||
push: | |||
- meta_scope: meta.declaration.module.elm | |||
- match: $|; | |||
captures: | |||
1: keyword.other.elm | |||
pop: true | |||
- include: module_name | |||
- match: '(where)\s*\{' | |||
captures: | |||
1: keyword.other.elm | |||
push: | |||
- match: '\}' | |||
pop: true | |||
- include: type_signature | |||
- match: (exposing) | |||
scope: keyword.other.elm | |||
- include: module_exports | |||
- match: (where) | |||
scope: keyword.other.elm | |||
- match: "[a-z]+" | |||
scope: invalid | |||
- match: ^\b(import)\s+((open)\s+)? | |||
captures: | |||
1: keyword.other.elm | |||
3: invalid | |||
push: | |||
- meta_scope: meta.import.elm | |||
- match: ($|;) | |||
pop: true | |||
- match: (as|exposing) | |||
scope: keyword.import.elm | |||
- include: module_name | |||
- include: module_exports | |||
- match: '(\[)(glsl)(\|)' | |||
captures: | |||
1: keyword.other.elm | |||
2: support.function.prelude.elm | |||
3: keyword.other.elm | |||
push: | |||
- meta_scope: entity.glsl.elm | |||
- match: '(\|\])' | |||
captures: | |||
1: keyword.other.elm | |||
pop: true | |||
- include: scope:source.glsl | |||
- match: \b(type alias|type|case|of|let|in|as)\s+ | |||
scope: keyword.other.elm | |||
- match: \b(if|then|else)\s+ | |||
scope: keyword.control.elm | |||
- match: '\b([0-9]+\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)\b' | |||
comment: Floats are always decimal | |||
scope: constant.numeric.float.elm | |||
- match: '\b([0-9]+)\b' | |||
scope: constant.numeric.elm | |||
- match: '"""' | |||
captures: | |||
0: punctuation.definition.string.begin.elm | |||
push: | |||
- meta_scope: string.quoted.double.elm | |||
- match: '"""' | |||
captures: | |||
0: punctuation.definition.string.end.elm | |||
pop: true | |||
- match: '\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\''\&])' | |||
scope: constant.character.escape.elm | |||
- match: '\^[A-Z@\[\]\\\^_]' | |||
scope: constant.character.escape.control.elm | |||
- match: '"' | |||
captures: | |||
0: punctuation.definition.string.begin.elm | |||
push: | |||
- meta_scope: string.quoted.double.elm | |||
- match: '"' | |||
captures: | |||
0: punctuation.definition.string.end.elm | |||
pop: true | |||
- match: '\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\\"''\&])' | |||
scope: constant.character.escape.elm | |||
- match: '\^[A-Z@\[\]\\\^_]' | |||
scope: constant.character.escape.control.elm | |||
- match: |- | |||
(?x) | |||
(') | |||
(?: | |||
[\ -\[\]-~] # Basic Char | |||
| (\\(?:NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE | |||
|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS | |||
|US|SP|DEL|[abfnrtv\\\"'\&])) # Escapes | |||
| (\^[A-Z@\[\]\\\^_]) # Control Chars | |||
) | |||
(') | |||
scope: string.quoted.single.elm | |||
captures: | |||
1: punctuation.definition.string.begin.elm | |||
2: constant.character.escape.elm | |||
3: punctuation.definition.string.end.elm | |||
- match: '^(port\s+)?([a-z_][a-zA-Z0-9_'']*|\([|!%$+\-.,=</>]+\))\s*((:)([:]+)?)' | |||
captures: | |||
1: keyword.other.port.elm | |||
2: entity.name.function.elm | |||
4: keyword.other.colon.elm | |||
5: invalid | |||
push: | |||
- meta_scope: meta.function.type-declaration.elm | |||
- match: $\n? | |||
pop: true | |||
- include: type_signature | |||
- match: \bport\s+ | |||
scope: keyword.other.port.elm | |||
- match: '\b[A-Z]\w*\b' | |||
scope: constant.other.elm | |||
- include: comments | |||
- match: '^[a-z][A-Za-z0-9_'']*\s+' | |||
scope: entity.name.function.elm | |||
- include: infix_op | |||
- match: '[|!%$?~+:\-.=</>&\\*^]+' | |||
scope: keyword.operator.elm | |||
- match: '([\[\]\{\},])' | |||
scope: constant.language.delimiter.elm | |||
captures: | |||
1: support.function.delimiter.elm | |||
- match: '([\(\)])' | |||
scope: keyword.other.parenthesis.elm | |||
block_comment: | |||
- match: '\{-(?!#)' | |||
captures: | |||
0: punctuation.definition.comment.elm | |||
push: | |||
- meta_scope: comment.block.elm | |||
- include: block_comment | |||
- match: '-\}' | |||
captures: | |||
0: punctuation.definition.comment.elm | |||
pop: true | |||
comments: | |||
- match: (--).*$\n? | |||
scope: comment.line.double-dash.elm | |||
captures: | |||
1: punctuation.definition.comment.elm | |||
- include: block_comment | |||
infix_op: | |||
- match: '(\([|!%$+:\-.=</>]+\)|\(,+\))' | |||
scope: entity.name.function.infix.elm | |||
module_exports: | |||
- match: \( | |||
push: | |||
- meta_scope: meta.declaration.exports.elm | |||
- match: \) | |||
pop: true | |||
- match: '\b[a-z][a-zA-Z_''0-9]*' | |||
scope: entity.name.function.elm | |||
- match: '\b[A-Z][A-Za-z_''0-9]*' | |||
scope: storage.type.elm | |||
- match: "," | |||
scope: punctuation.separator.comma.elm | |||
- include: infix_op | |||
- match: \(.*?\) | |||
comment: So named because I don't know what to call this. | |||
scope: meta.other.unknown.elm | |||
module_name: | |||
- match: "[A-Z][A-Za-z._']*" | |||
scope: support.other.module.elm | |||
type_signature: | |||
- match: '\(\s*([A-Z][A-Za-z]*)\s+([a-z][A-Za-z_'']*)\)\s*(=>)' | |||
scope: meta.class-constraint.elm | |||
captures: | |||
1: entity.other.inherited-class.elm | |||
2: variable.other.generic-type.elm | |||
3: keyword.other.big-arrow.elm | |||
- match: "->" | |||
scope: keyword.other.arrow.elm | |||
- match: "=>" | |||
scope: keyword.other.big-arrow.elm | |||
- match: '\b[a-z][a-zA-Z0-9_'']*\b' | |||
scope: variable.other.generic-type.elm | |||
- match: '\b[A-Z][a-zA-Z0-9_'']*\b' | |||
scope: storage.type.elm | |||
- match: \(\) | |||
scope: support.constant.unit.elm | |||
- include: comments |
@@ -1 +1 @@ | |||
Subproject commit 581b9e6f5bfdf55e506ac5a2b18422296c375ca2 | |||
Subproject commit 6f2e53603a62663463f95079b308a1c9adbabeec |
@@ -1 +1 @@ | |||
Subproject commit 3cf94d55b2eafd41461c45570d982eef1d65778c | |||
Subproject commit 581805e47c7af5ab0a880aaef5b27f8c1ccc29aa |
@@ -1 +1 @@ | |||
Subproject commit ebd4a2f049fb61686664a68c8d0f34d83292e07e | |||
Subproject commit df5a27523dd37ebe67ba4c7d36ea162dae95b2c3 |
@@ -1,326 +0,0 @@ | |||
%YAML 1.2 | |||
--- | |||
# http://www.sublimetext.com/docs/3/syntax.html | |||
name: LESS | |||
comment: LESS | |||
file_extensions: | |||
- less | |||
scope: source.less | |||
contexts: | |||
main: | |||
- include: comment-block | |||
- include: comment-line | |||
- include: less-at-rules | |||
- include: less-declarations | |||
- include: less-variables | |||
- include: less-functions | |||
- include: string-double | |||
- include: string-single | |||
- include: selector | |||
- include: rule-list | |||
- include: less-operators | |||
- include: less-parameters | |||
- include: numeric-values | |||
color-values: | |||
- match: \b(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)\b | |||
comment: http://www.w3.org/TR/CSS21/syndata.html#value-def-color | |||
scope: support.constant.color.w3c-standard-color-name.css | |||
- match: \b(aliceblue|antiquewhite|aquamarine|azure|beige|bisque|blanchedalmond|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|gainsboro|ghostwhite|gold|goldenrod|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|limegreen|linen|magenta|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|oldlace|olivedrab|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|thistle|tomato|turquoise|violet|wheat|whitesmoke|yellowgreen)\b | |||
comment: "These colours are mostly recognised but will not validate. ref: http://www.w3schools.com/css/css_colornames.asp" | |||
scope: support.constant.color.non-standard | |||
- match: (hsla?|rgba?)\s*(\() | |||
captures: | |||
1: support.function.misc.css | |||
2: punctuation.section.function.css | |||
push: | |||
- match: (\)) | |||
captures: | |||
1: punctuation.section.function.css | |||
pop: true | |||
- match: '(?x)\b(0*((1?[0-9]{1,2})|(2([0-4][0-9]|5[0-5])))\s*,\s*){2}(0*((1?[0-9]{1,2})|(2([0-4][0-9]|5[0-5])))\b)(\s*,\s*((0?\.[0-9]+)|[0-1]))?' | |||
scope: constant.other.color.rgb-value.css | |||
- match: '\b([0-9]{1,2}|100)\s*%,\s*([0-9]{1,2}|100)\s*%,\s*([0-9]{1,2}|100)\s*%' | |||
scope: constant.other.color.rgb-percentage.css | |||
- include: numeric-values | |||
comment-block: | |||
- match: /\* | |||
captures: | |||
0: punctuation.definition.comment.css | |||
push: | |||
- meta_scope: comment.block.css | |||
- match: \*/ | |||
captures: | |||
0: punctuation.definition.comment.css | |||
pop: true | |||
comment-line: | |||
- match: // | |||
captures: | |||
0: punctuation.definition.comment.css | |||
push: | |||
- meta_scope: comment.line.double-slash.less | |||
- match: $\n? | |||
captures: | |||
0: punctuation.definition.comment.css | |||
pop: true | |||
less-at-rules: | |||
- match: ^\s*((@)(?:-(?:webkit|moz|o)-)?(charset|import|namespace|media|page|font-face|keyframes|supports|document)\b) | |||
scope: meta.at-rule.css | |||
captures: | |||
1: keyword.control.at-rule.css | |||
2: punctuation.definition.keyword.css | |||
less-data-uri: | |||
- match: (url)(\()(data:) | |||
captures: | |||
1: support.function.misc.css | |||
2: punctuation.section.function.css | |||
3: parameter.less.data-uri comment markup.raw | |||
push: | |||
- meta_content_scope: parameter.less.data-uri comment markup.raw | |||
- match: (\)) | |||
captures: | |||
1: punctuation.section.function.css | |||
pop: true | |||
- match: (url)(\()("data:) | |||
captures: | |||
1: support.function.misc.css | |||
2: punctuation.section.function.css | |||
3: parameter.less.data-uri comment markup.raw | |||
push: | |||
- meta_content_scope: parameter.less.data-uri comment markup.raw | |||
- match: (?<=")(\)) | |||
captures: | |||
1: punctuation.section.function.css | |||
pop: true | |||
- match: (url)(\()('data:) | |||
captures: | |||
1: support.function.misc.css | |||
2: punctuation.section.function.css | |||
3: parameter.less.data-uri comment markup.raw | |||
push: | |||
- meta_content_scope: parameter.less.data-uri comment markup.raw | |||
- match: (?<=\')(\)) | |||
captures: | |||
1: punctuation.section.function.css | |||
pop: true | |||
less-declarations: | |||
- match: '(?>@[a-zA-Z0-9_-][\w-]*+)(?!:)' | |||
scope: variable.other.less | |||
- match: '@[a-zA-Z0-9_-][\w-]*' | |||
scope: variable.declaration.less | |||
less-functions: | |||
- match: '([%a-zA-Z\-\_\d]*)(?=\()' | |||
scope: support.function.less | |||
- match: '([\.#](?![0-9])[a-zA-Z0-9_-]+(?=\())' | |||
captures: | |||
1: entity.other.attribute-name.class.css entity.other.less.mixin | |||
less-operators: | |||
- match: /|!important|$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\|\||\?\:|(?<!\()/=|%=|\+=|\-=|&=|when\b|and\b|not\b | |||
scope: keyword.operator.less | |||
less-parameters: | |||
- match: '\(|(}\s*,)' | |||
push: | |||
- meta_scope: parameter.less | |||
- match: '\)|{' | |||
pop: true | |||
- include: color-values | |||
- include: less-parameters | |||
- include: less-functions | |||
- include: numeric-values | |||
- include: string-double | |||
- include: string-single | |||
- include: less-variables | |||
- match: '(?:\:/)?[\/\.a-zA-Z0-9_\-]*' | |||
scope: variable.parameter.misc.css | |||
- include: less-operators | |||
less-variables: | |||
- match: '@[a-zA-Z0-9_-][\w-]*' | |||
scope: variable.other.less | |||
- match: '@{[a-zA-Z0-9_-][\w-]*}' | |||
scope: variable.interpolation.less | |||
- match: "`" | |||
push: | |||
- meta_scope: comment markup.raw | |||
- match: "`" | |||
pop: true | |||
- match: (~)` | |||
captures: | |||
1: keyword.operator.less | |||
push: | |||
- meta_scope: comment markup.raw | |||
- match: "`" | |||
pop: true | |||
- match: (~)" | |||
captures: | |||
1: keyword.operator.less | |||
push: | |||
- meta_scope: string.quoted.double.css comment markup.raw | |||
- match: '"' | |||
pop: true | |||
- match: (~)' | |||
captures: | |||
1: keyword.operator.less | |||
push: | |||
- meta_scope: string.quoted.single.css comment markup.raw | |||
- match: "'" | |||
pop: true | |||
numeric-values: | |||
- match: '(#)([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b' | |||
scope: constant.other.color.rgb-value.css | |||
captures: | |||
1: punctuation.definition.constant.css | |||
- match: '(?x)(?:-|\+)?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+))((?:px|pt|ch|cm|mm|in|r?em|ex|pc|deg|g?rad|dpi|dpcm|ms|s)\b|%)?' | |||
scope: constant.numeric.css | |||
captures: | |||
1: keyword.other.unit.css | |||
property-names: | |||
- match: "(?<![-a-z])(?=[-a-z])" | |||
push: | |||
- meta_scope: support.type.property-name.css | |||
- match: "$|(?![-a-z])" | |||
pop: true | |||
- match: \b(word)\b | |||
scope: support.type.property-name.css | |||
property-values: | |||
- include: less-variables | |||
- include: less-data-uri | |||
- include: less-functions | |||
- include: less-operators | |||
- include: color-values | |||
- include: numeric-values | |||
- include: less-parameters | |||
- match: \b(absolute|all(-scroll)?|always|subpixel-antialiased|antialiased|armenian|auto|avoid|baseline|below|bidi-override|block|bold|bolder|border-box|both|bottom|break-all|break-word|capitalize|center|char|circle|cjk-ideographic|content-box|col-resize|collapse|crosshair|dashed|decimal-leading-zero|decimal|default|disabled|disc|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ellipsis|fixed|geometricPrecision|georgian|groove|hand|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|inactive|inherit|inline-block|inline|inset|inside|inter-ideograph|inter-word|italic|justify|katakana-iroha|katakana|keep-all|left|lighter|line-edge|line-through|line|list-item|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|medium|middle|move|n-resize|ne-resize|newspaper|no-drop|no-repeat|nw-resize|none|normal|not-allowed|nowrap|oblique|optimize(Legibility|Quality|Speed)|outset|outside|overline|pointer|pre(-(wrap|line))?|progress|relative|repeat-x|repeat-y|repeat|right|ridge|row-resize|rtl|s-resize|scroll|se-resize|separate|small-caps|solid|square|static|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table|tb-rl|text-bottom|text-top|textfield|text|thick|thin|top|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|vertical(-(ideographic|text))?|visible(Painted|Fill|Stroke)?|w-resize|wait|whitespace|zero|smaller|larger|((xx?-)?(small|large))|painted|fill|stroke)\b | |||
scope: support.constant.property-value.css | |||
- include: selector | |||
- match: (\b(?i:arial|century|comic|courier|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace)\b) | |||
scope: support.constant.font-name.css | |||
- include: string-double | |||
- include: string-single | |||
- match: (rect)\s*(\() | |||
captures: | |||
1: support.function.misc.css | |||
2: punctuation.section.function.css | |||
push: | |||
- match: (\)) | |||
captures: | |||
1: punctuation.section.function.css | |||
pop: true | |||
- include: numeric-values | |||
- match: (format|local|url|attr|counter|counters)\s*(\() | |||
captures: | |||
1: support.function.misc.css | |||
2: punctuation.section.function.css | |||
push: | |||
- match: (\)) | |||
captures: | |||
1: punctuation.section.function.css | |||
pop: true | |||
- include: string-single | |||
- include: string-double | |||
- match: '[^''") \t]+' | |||
scope: variable.parameter.misc.css | |||
- match: \!\s*important | |||
scope: keyword.other.important.css | |||
rule-list: | |||
- include: comment-block | |||
- include: comment-line | |||
- include: selector | |||
- include: property-names | |||
- match: (:)\s* | |||
captures: | |||
1: punctuation.separator.key-value.css | |||
push: | |||
- meta_scope: meta.property-value.css | |||
- match: '\s*(;|(?=[{}]))' | |||
captures: | |||
1: punctuation.terminator.rule.css | |||
pop: true | |||
- include: property-values | |||
selector: | |||
- match: '\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|eventsource|fieldset|figure|figcaption|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|samp|script|section|select|small|span|strike|strong|style|sub|summary|sup|svg|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\b' | |||
scope: entity.name.tag.css, keyword.control.html.elements | |||
- match: '(\.)-?[a-zA-Z_][a-zA-Z0-9_-]*' | |||
scope: entity.other.attribute-name.class.css | |||
captures: | |||
1: punctuation.definition.entity.css | |||
- match: "(#)-?[a-zA-Z_][a-zA-Z0-9_-]*" | |||
scope: entity.other.attribute-name.id.css | |||
captures: | |||
1: punctuation.definition.entity.css | |||
- match: \* | |||
scope: entity.name.tag.wildcard.css | |||
- match: (:+)((first|last|only)-child|(first|last|only)-of-type|empty|root|target|first-letter|first-line|first|left|right|lang)\b | |||
scope: entity.other.attribute-name.pseudo-class.css | |||
captures: | |||
1: punctuation.definition.entity.css | |||
- match: (:)(extend)\b | |||
scope: entity.other.attribute-name.pseudo-class.less | |||
captures: | |||
1: punctuation.definition.entity.css | |||
- match: (:)(checked|enabled|default|disabled|indeterminate|invalid|optional|required|valid)\b | |||
scope: entity.other.attribute-name.pseudo-class.ui-state.css | |||
captures: | |||
1: punctuation.definition.entity.css | |||
- match: ((:)not)(\() | |||
captures: | |||
1: entity.other.attribute-name.pseudo-class.css | |||
2: punctuation.definition.entity.css | |||
3: punctuation.section.function.css | |||
push: | |||
- match: \) | |||
captures: | |||
0: punctuation.section.function.css | |||
pop: true | |||
- include: selector | |||
- match: ((:)nth-(?:(?:last-)?child|(?:last-)?of-type))(\() | |||
captures: | |||
1: entity.other.attribute-name.pseudo-class.css | |||
2: punctuation.definition.entity.css | |||
3: punctuation.section.function.css | |||
push: | |||
- meta_content_scope: constant.numeric.css | |||
- match: (\)) | |||
captures: | |||
1: punctuation.section.function.css | |||
pop: true | |||
- match: (:+)(?:-(?:webkit|moz|o)-)?(after|before|selection|scrollbar|placeholder|input-placeholder)\b | |||
scope: entity.other.attribute-name.pseudo-element.css | |||
captures: | |||
1: punctuation.definition.entity.css | |||
- match: (:+)(?:-(?:webkit|moz|o)-)?(active|hover|link|visited|focus-inner|focus)\b | |||
scope: entity.other.attribute-name.pseudo-class.css | |||
captures: | |||
1: punctuation.definition.entity.css | |||
- match: '(?i)(\[)\s*(-?[_a-z\\[[:^ascii:]]][_a-z0-9\-\\[[:^ascii:]]]*)(?:\s*([~|^$*]?=)\s*(?:(-?[_a-z\\[[:^ascii:]]][_a-z0-9\-\\[[:^ascii:]]]*)|((?>([''"])(?:[^\\]|\\.)*?(\6)))))?\s*(\])' | |||
scope: meta.attribute-selector.css | |||
captures: | |||
1: punctuation.definition.entity.css | |||
2: entity.other.attribute-name.attribute.css | |||
3: punctuation.separator.operator.css | |||
4: string.unquoted.attribute-value.css | |||
5: string.quoted.double.attribute-value.css | |||
6: punctuation.definition.string.begin.css | |||
7: punctuation.definition.string.end.css | |||
string-double: | |||
- match: '"' | |||
captures: | |||
0: punctuation.definition.string.begin.css | |||
push: | |||
- meta_scope: string.quoted.double.css | |||
- match: '"' | |||
captures: | |||
0: punctuation.definition.string.end.css | |||
pop: true | |||
- match: \\. | |||
scope: constant.character.escape.css | |||
string-single: | |||
- match: "'" | |||
captures: | |||
0: punctuation.definition.string.begin.css | |||
push: | |||
- meta_scope: string.quoted.single.css | |||
- match: "'" | |||
captures: | |||
0: punctuation.definition.string.end.css | |||
pop: true | |||
- match: \\. | |||
scope: constant.character.escape.css |
@@ -1 +1 @@ | |||
Subproject commit 928a7a618d99631ea424c45e74fb01d1fb6f6853 | |||
Subproject commit ce7af4d6177d0340230893e49742070ae4143246 |
@@ -0,0 +1,135 @@ | |||
%YAML 1.2 | |||
--- | |||
# http://www.sublimetext.com/docs/3/syntax.html | |||
name: SWI-Prolog | |||
comment: This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. | |||
file_extensions: | |||
- pro | |||
scope: source.prolog | |||
contexts: | |||
main: | |||
- include: comments | |||
- match: (?<=:-)\s* | |||
push: | |||
- meta_scope: meta.clause.body.prolog | |||
- match: (\.) | |||
captures: | |||
1: keyword.control.clause.bodyend.prolog | |||
pop: true | |||
- include: comments | |||
- include: builtin | |||
- include: controlandkeywords | |||
- include: atom | |||
- include: variable | |||
- include: constants | |||
- match: . | |||
scope: meta.clause.body.prolog | |||
- match: '^\s*([a-z][a-zA-Z0-9_]*)(\(?)(?=.*:-.*)' | |||
captures: | |||
1: entity.name.function.clause.prolog | |||
2: punctuation.definition.parameters.begin | |||
push: | |||
- meta_scope: meta.clause.head.prolog | |||
- match: ((\)?))\s*(:-) | |||
captures: | |||
1: punctuation.definition.parameters.end | |||
3: keyword.control.clause.bodybegin.prolog | |||
pop: true | |||
- include: atom | |||
- include: variable | |||
- include: constants | |||
- match: '^\s*([a-z][a-zA-Z0-9_]*)(\(?)(?=.*-->.*)' | |||
captures: | |||
1: entity.name.function.dcg.prolog | |||
2: punctuation.definition.parameters.begin | |||
push: | |||
- meta_scope: meta.dcg.head.prolog | |||
- match: ((\)?))\s*(-->) | |||
captures: | |||
1: punctuation.definition.parameters.end | |||
3: keyword.control.dcg.bodybegin.prolog | |||
pop: true | |||
- include: atom | |||
- include: variable | |||
- include: constants | |||
- match: (?<=-->)\s* | |||
push: | |||
- meta_scope: meta.dcg.body.prolog | |||
- match: (\.) | |||
captures: | |||
1: keyword.control.dcg.bodyend.prolog | |||
pop: true | |||
- include: comments | |||
- include: controlandkeywords | |||
- include: atom | |||
- include: variable | |||
- include: constants | |||
- match: . | |||
scope: meta.dcg.body.prolog | |||
- match: '^\s*([a-zA-Z][a-zA-Z0-9_]*)(\(?)(?!.*(:-|-->).*)' | |||
captures: | |||
1: entity.name.function.fact.prolog | |||
2: punctuation.definition.parameters.begin | |||
push: | |||
- meta_scope: meta.fact.prolog | |||
- match: ((\)?))\s*(\.)(?!\d+) | |||
captures: | |||
1: punctuation.definition.parameters.end | |||
3: keyword.control.fact.end.prolog | |||
pop: true | |||
- include: atom | |||
- include: variable | |||
- include: constants | |||
atom: | |||
- match: '(?<![a-zA-Z0-9_])[a-z][a-zA-Z0-9_]*(?!\s*\(|[a-zA-Z0-9_])' | |||
scope: constant.other.atom.simple.prolog | |||
- match: "'.*?'" | |||
scope: constant.other.atom.quoted.prolog | |||
- match: '\[\]' | |||
scope: constant.other.atom.emptylist.prolog | |||
builtin: | |||
- match: \b(op|findall|write|nl|writeln|fail|use_module|module)\b | |||
scope: keyword.other | |||
comments: | |||
- match: "%.*" | |||
scope: comment.line.percent-sign.prolog | |||
- match: /\* | |||
captures: | |||
0: punctuation.definition.comment.prolog | |||
push: | |||
- meta_scope: comment.block.prolog | |||
- match: \*/ | |||
captures: | |||
0: punctuation.definition.comment.prolog | |||
pop: true | |||
constants: | |||
- match: '(?<![a-zA-Z]|/)(\d+|(\d+\.\d+))' | |||
scope: constant.numeric.integer.prolog | |||
- match: '".*?"' | |||
scope: string.quoted.double.prolog | |||
controlandkeywords: | |||
- match: (->) | |||
captures: | |||
1: keyword.control.if.prolog | |||
push: | |||
- meta_scope: meta.if.prolog | |||
- match: (;) | |||
captures: | |||
1: keyword.control.else.prolog | |||
pop: true | |||
- include: main | |||
- include: builtin | |||
- include: comments | |||
- include: atom | |||
- include: variable | |||
- match: . | |||
scope: meta.if.body.prolog | |||
- match: "!" | |||
scope: keyword.control.cut.prolog | |||
- match: (\s(is)\s)|=:=|=?\\?=|\\\+|@?>|@?=?<|\+|\*|\- | |||
scope: keyword.operator.prolog | |||
variable: | |||
- match: "(?<![a-zA-Z0-9_])[A-Z][a-zA-Z0-9_]*" | |||
scope: variable.parameter.uppercase.prolog | |||
- match: (?<!\w)_ | |||
scope: variable.language.anonymous.prolog |
@@ -1 +1 @@ | |||
Subproject commit 5f12fdaae34303ab2550394a14599ad0885b26ec | |||
Subproject commit 04ec6d7165e971db1fefd604942b04ccf86fb920 |
@@ -1 +0,0 @@ | |||
Subproject commit 79bf8ddfb8a05a2b104f3937cd91b6f2afbbb943 |
@@ -0,0 +1 @@ | |||
Subproject commit c29d12d8aceb1a68af4cb6e466199846f41dd2ed |