Browse Source

Merge pull request #175 from Keats/next

Next version
index-subcmd
Vincent Prouillet GitHub 6 years ago
parent
commit
c165c17c2f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 3250 additions and 2520 deletions
  1. +3
    -3
      .gitmodules
  2. +3
    -1
      .travis.yml
  3. +21
    -0
      CHANGELOG.md
  4. +461
    -341
      Cargo.lock
  5. +7
    -6
      Cargo.toml
  6. +1
    -1
      build.rs
  7. +4
    -0
      completions/_gutenberg
  8. +2
    -2
      completions/_gutenberg.ps1
  9. +153
    -0
      completions/gutenberg.bash
  10. +2
    -0
      completions/gutenberg.fish
  11. +40
    -3
      components/config/src/lib.rs
  12. +3
    -2
      components/content/Cargo.toml
  13. +1
    -1
      components/content/src/file_info.rs
  14. +2
    -0
      components/content/src/lib.rs
  15. +6
    -2
      components/content/src/page.rs
  16. +7
    -4
      components/content/src/sorting.rs
  17. +2
    -2
      components/errors/Cargo.toml
  18. +2
    -2
      components/front_matter/Cargo.toml
  19. +4
    -4
      components/front_matter/src/lib.rs
  20. +41
    -18
      components/front_matter/src/page.rs
  21. +2
    -2
      components/highlighting/Cargo.toml
  22. +1
    -1
      components/pagination/Cargo.toml
  23. +3
    -3
      components/rendering/Cargo.toml
  24. +1
    -1
      components/rendering/tests/markdown.rs
  25. +2
    -3
      components/site/Cargo.toml
  26. +48
    -25
      components/site/src/lib.rs
  27. +1
    -1
      components/site/test_site/content/posts/draft.md
  28. +1
    -1
      components/site/test_site/content/posts/fixed-slug.md
  29. +1
    -1
      components/site/test_site/content/posts/fixed-url.md
  30. +1
    -1
      components/site/test_site/content/posts/python.md
  31. +1
    -1
      components/site/test_site/content/posts/simple.md
  32. +1
    -1
      components/site/test_site/content/posts/tutorials/devops/docker.md
  33. +1
    -1
      components/site/test_site/content/posts/tutorials/devops/nix.md
  34. +1
    -1
      components/site/test_site/content/posts/tutorials/programming/python.md
  35. +1
    -1
      components/site/test_site/content/posts/tutorials/programming/rust.md
  36. +1
    -1
      components/site/test_site/templates/index.html
  37. +1
    -1
      components/site/test_site/templates/index_paginated.html
  38. +1
    -1
      components/taxonomies/Cargo.toml
  39. +6
    -6
      components/taxonomies/src/lib.rs
  40. +4
    -3
      components/templates/Cargo.toml
  41. +1
    -1
      components/templates/src/builtins/rss.xml
  42. +160
    -44
      components/templates/src/global_fns.rs
  43. +2
    -0
      components/templates/src/lib.rs
  44. +1
    -1
      components/utils/Cargo.toml
  45. +43
    -0
      components/utils/src/default_tpl.html
  46. +27
    -5
      components/utils/src/templates.rs
  47. +0
    -0
      components/utils/test-templates/child.html
  48. +0
    -0
      components/utils/test-templates/included.html
  49. +0
    -0
      components/utils/test-templates/index.html
  50. +0
    -0
      components/utils/test-templates/macros.html
  51. +0
    -0
      components/utils/test-templates/using-macros.html
  52. +1
    -0
      docs/content/documentation/content/page.md
  53. +7
    -5
      docs/content/documentation/content/syntax-highlighting.md
  54. +2
    -2
      docs/content/documentation/content/tags-categories.md
  55. +6
    -0
      docs/content/documentation/getting-started/cli-usage.md
  56. +5
    -1
      docs/content/documentation/getting-started/configuration.md
  57. +26
    -0
      docs/content/documentation/templates/overview.md
  58. +5
    -0
      docs/content/documentation/themes/creating-a-theme.md
  59. +2
    -2
      docs/templates/index.html
  60. +12
    -0
      src/cli.rs
  61. +2
    -1
      src/cmd/build.rs
  62. +2
    -3
      src/cmd/init.rs
  63. +35
    -8
      src/cmd/serve.rs
  64. +5
    -2
      src/main.rs
  65. +11
    -2
      src/rebuild.rs
  66. +12
    -16
      sublime_syntaxes/Elixir.sublime-syntax
  67. +0
    -200
      sublime_syntaxes/Elm.sublime-syntax
  68. +1
    -1
      sublime_syntaxes/Elm.tmLanguage
  69. +1
    -1
      sublime_syntaxes/Julia-sublime
  70. +1
    -1
      sublime_syntaxes/LESS-sublime
  71. +0
    -326
      sublime_syntaxes/LESS.sublime-syntax
  72. +1
    -1
      sublime_syntaxes/Packages
  73. +135
    -0
      sublime_syntaxes/Prolog.sublime-syntax
  74. +1
    -1
      sublime_syntaxes/Sublime-GenericConfig
  75. +0
    -1
      sublime_syntaxes/TypeScript-Sublime-Plugin
  76. +1
    -0
      sublime_syntaxes/TypeScript-TmLanguage
  77. +942
    -716
      sublime_syntaxes/TypeScript.sublime-syntax
  78. +956
    -731
      sublime_syntaxes/TypeScriptReact.sublime-syntax
  79. BIN
      sublime_syntaxes/newlines.packdump
  80. BIN
      sublime_syntaxes/nonewlines.packdump

+ 3
- 3
.gitmodules View File

@@ -7,9 +7,6 @@
[submodule "sublime_syntaxes/LESS-sublime"] [submodule "sublime_syntaxes/LESS-sublime"]
path = sublime_syntaxes/LESS-sublime path = sublime_syntaxes/LESS-sublime
url = https://github.com/danro/LESS-sublime.git url = https://github.com/danro/LESS-sublime.git
[submodule "sublime_syntaxes/TypeScript-Sublime-Plugin"]
path = sublime_syntaxes/TypeScript-Sublime-Plugin
url = https://github.com/Microsoft/TypeScript-Sublime-Plugin.git
[submodule "sublime_syntaxes/Handlebars"] [submodule "sublime_syntaxes/Handlebars"]
path = sublime_syntaxes/Handlebars path = sublime_syntaxes/Handlebars
url = https://github.com/daaain/Handlebars.git url = https://github.com/daaain/Handlebars.git
@@ -31,3 +28,6 @@
[submodule "sublime_syntaxes/Sublime-VimL"] [submodule "sublime_syntaxes/Sublime-VimL"]
path = sublime_syntaxes/Sublime-VimL path = sublime_syntaxes/Sublime-VimL
url = https://github.com/SalGnt/Sublime-VimL.git url = https://github.com/SalGnt/Sublime-VimL.git
[submodule "sublime_syntaxes/TypeScript-TmLanguage"]
path = sublime_syntaxes/TypeScript-TmLanguage
url = https://github.com/Microsoft/TypeScript-TmLanguage

+ 3
- 1
.travis.yml View File

@@ -1,7 +1,6 @@
dist: trusty dist: trusty
language: rust language: rust
services: docker services: docker
sudo: required


env: env:
global: global:
@@ -20,6 +19,9 @@ matrix:
rust: beta rust: beta
- env: TARGET=x86_64-unknown-linux-gnu - env: TARGET=x86_64-unknown-linux-gnu
rust: nightly rust: nightly
# The earliest stable Rust version that works
- env: TARGET=x86_64-unknown-linux-gnu
rust: 1.23.0




before_install: set -e before_install: set -e


+ 21
- 0
CHANGELOG.md View File

@@ -1,5 +1,26 @@
# Changelog # Changelog


## 0.3.0 (2017-01-25)

### Breaking
- Change names of individual taxonomies to be plural (ie `tags/my-tag` instead of `tag/my-tag`)
- Front matter now uses TOML dates rather strings: remove quotes from your date value to fix it.
For example: `date = "2001-10-10"` becomes `date = 2001-10-10`
- `language_code` has been renamed `default_language` in preparations of i18n support

### Others
- Add `get_taxonomy_url` to retrieve the permalink of a tag/category
- Fix bug when generating permalinks for taxonomies
- Update to Tera 0.11
- Better UX on first `serve` thanks to some default templates.
- Add `output-dir` to `build` and `serve` to generate the site in a folder other than `public`
- Add Prolog syntax highlighting and update all current syntaxes
- Live reloading now works on shortcode template changes
- `gutenberg serve` now reloads site on `config.toml` changes: you will need to F5 to see any changes though
- Add a `trans` global function that will get return the translation of the given key for the given lang, defaulting
to `config.default_language` if not given
- `gutenberg serve` cleans after itself and deletes the output directory on CTRL+C

## 0.2.2 (2017-11-01) ## 0.2.2 (2017-11-01)


- Fix shortcodes without arguments being ignored - Fix shortcodes without arguments being ignored


+ 461
- 341
Cargo.lock
File diff suppressed because it is too large
View File


+ 7
- 6
Cargo.toml View File

@@ -1,10 +1,10 @@
[package] [package]
name = "gutenberg" name = "gutenberg"
version = "0.2.2"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
version = "0.3.0"
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"
description = "Static site generator"
description = "A static site generator with everything built-in"
homepage = "https://github.com/Keats/gutenberg" homepage = "https://github.com/Keats/gutenberg"
repository = "https://github.com/Keats/gutenberg" repository = "https://github.com/Keats/gutenberg"
keywords = ["static", "site", "generator", "blog"] keywords = ["static", "site", "generator", "blog"]
@@ -24,11 +24,12 @@ term-painter = "0.2"
# Used in init to ensure the url given as base_url is a valid one # Used in init to ensure the url given as base_url is a valid one
url = "1.5" url = "1.5"
# Below is for the serve cmd # Below is for the serve cmd
staticfile = "0.4"
iron = "0.5"
mount = "0.3"
staticfile = "0.5"
iron = "0.6"
mount = "0.4"
notify = "4" notify = "4"
ws = "0.7" ws = "0.7"
ctrlc = "3"


site = { path = "components/site" } site = { path = "components/site" }
errors = { path = "components/errors" } errors = { path = "components/errors" }


+ 1
- 1
build.rs View File

@@ -1,7 +1,7 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;


use clap::Shell;
// use clap::Shell;


include!("src/cli.rs"); include!("src/cli.rs");




+ 4
- 0
completions/_gutenberg View File

@@ -32,6 +32,8 @@ _arguments -s -S -C \
_arguments -s -S -C \ _arguments -s -S -C \
'-u+[Force the base URL to be that value (default to the one in config.toml)]' \ '-u+[Force the base URL to be that value (default to the one in config.toml)]' \
'--base-url+[Force the base URL to be that value (default to the one in config.toml)]' \ '--base-url+[Force the base URL to be that value (default to the one in config.toml)]' \
'-o+[Outputs the generated site in the given path]' \
'--output-dir+[Outputs the generated site in the given path]' \
'-h[Prints help information]' \ '-h[Prints help information]' \
'--help[Prints help information]' \ '--help[Prints help information]' \
'-V[Prints version information]' \ '-V[Prints version information]' \
@@ -44,6 +46,8 @@ _arguments -s -S -C \
'--interface+[Interface to bind on]' \ '--interface+[Interface to bind on]' \
'-p+[Which port to use]' \ '-p+[Which port to use]' \
'--port+[Which port to use]' \ '--port+[Which port to use]' \
'-o+[Outputs the generated site in the given path]' \
'--output-dir+[Outputs the generated site in the given path]' \
'-h[Prints help information]' \ '-h[Prints help information]' \
'--help[Prints help information]' \ '--help[Prints help information]' \
'-V[Prints version information]' \ '-V[Prints version information]' \


+ 2
- 2
completions/_gutenberg.ps1 View File

@@ -53,11 +53,11 @@
} }


'_gutenberg_build' { '_gutenberg_build' {
$completions = @('-h', '-V', '-u', '--help', '--version', '--base-url')
$completions = @('-h', '-V', '-u', '-o', '--help', '--version', '--base-url', '--output-dir')
} }


'_gutenberg_serve' { '_gutenberg_serve' {
$completions = @('-h', '-V', '-i', '-p', '--help', '--version', '--interface', '--port')
$completions = @('-h', '-V', '-i', '-p', '-o', '--help', '--version', '--interface', '--port', '--output-dir')
} }


'_gutenberg_help' { '_gutenberg_help' {


+ 153
- 0
completions/gutenberg.bash View File

@@ -0,0 +1,153 @@
_gutenberg() {
local i cur prev opts cmds
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
cmd=""
opts=""

for i in ${COMP_WORDS[@]}
do
case "${i}" in
gutenberg)
cmd="gutenberg"
;;
build)
cmd+="__build"
;;
help)
cmd+="__help"
;;
init)
cmd+="__init"
;;
serve)
cmd+="__serve"
;;
*)
;;
esac
done

case "${cmd}" in
gutenberg)
opts=" -c -h -V --config --help --version init build serve help"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
gutenberg__build)
opts=" -h -V -u -o --help --version --base-url --output-dir "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
case "${prev}" in
--base-url)
COMPREPLY=("<base_url>")
return 0
;;
-u)
COMPREPLY=("<base_url>")
return 0
;;
--output-dir)
COMPREPLY=("<output_dir>")
return 0
;;
-o)
COMPREPLY=("<output_dir>")
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
gutenberg__help)
opts=" -h -V --help --version "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
gutenberg__init)
opts=" -h -V --help --version <name> "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
case "${prev}" in
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
gutenberg__serve)
opts=" -h -V -i -p -o --help --version --interface --port --output-dir "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
case "${prev}" in
--interface)
COMPREPLY=("<interface>")
return 0
;;
-i)
COMPREPLY=("<interface>")
return 0
;;
--port)
COMPREPLY=("<port>")
return 0
;;
-p)
COMPREPLY=("<port>")
return 0
;;
--output-dir)
COMPREPLY=("<output_dir>")
return 0
;;
-o)
COMPREPLY=("<output_dir>")
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
esac
}

complete -F _gutenberg -o bashdefault -o default gutenberg

+ 2
- 0
completions/gutenberg.fish View File

@@ -21,10 +21,12 @@ complete -c gutenberg -n "__fish_using_command gutenberg" -f -a "help" -d 'Print
complete -c gutenberg -n "__fish_using_command gutenberg init" -s h -l help -d 'Prints help information' complete -c gutenberg -n "__fish_using_command gutenberg init" -s h -l help -d 'Prints help information'
complete -c gutenberg -n "__fish_using_command gutenberg init" -s V -l version -d 'Prints version information' complete -c gutenberg -n "__fish_using_command gutenberg init" -s V -l version -d 'Prints version information'
complete -c gutenberg -n "__fish_using_command gutenberg build" -s u -l base-url -d 'Force the base URL to be that value (default to the one in config.toml)' complete -c gutenberg -n "__fish_using_command gutenberg build" -s u -l base-url -d 'Force the base URL to be that value (default to the one in config.toml)'
complete -c gutenberg -n "__fish_using_command gutenberg build" -s o -l output-dir -d 'Outputs the generated site in the given path'
complete -c gutenberg -n "__fish_using_command gutenberg build" -s h -l help -d 'Prints help information' complete -c gutenberg -n "__fish_using_command gutenberg build" -s h -l help -d 'Prints help information'
complete -c gutenberg -n "__fish_using_command gutenberg build" -s V -l version -d 'Prints version information' complete -c gutenberg -n "__fish_using_command gutenberg build" -s V -l version -d 'Prints version information'
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s i -l interface -d 'Interface to bind on' complete -c gutenberg -n "__fish_using_command gutenberg serve" -s i -l interface -d 'Interface to bind on'
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s p -l port -d 'Which port to use' complete -c gutenberg -n "__fish_using_command gutenberg serve" -s p -l port -d 'Which port to use'
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s o -l output-dir -d 'Outputs the generated site in the given path'
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s h -l help -d 'Prints help information' complete -c gutenberg -n "__fish_using_command gutenberg serve" -s h -l help -d 'Prints help information'
complete -c gutenberg -n "__fish_using_command gutenberg serve" -s V -l version -d 'Prints version information' complete -c gutenberg -n "__fish_using_command gutenberg serve" -s V -l version -d 'Prints version information'
complete -c gutenberg -n "__fish_using_command gutenberg help" -s h -l help -d 'Prints help information' complete -c gutenberg -n "__fish_using_command gutenberg help" -s h -l help -d 'Prints help information'


+ 40
- 3
components/config/src/lib.rs View File

@@ -38,7 +38,7 @@ pub struct Config {
/// Description of the site /// Description of the site
pub description: Option<String>, pub description: Option<String>,
/// The language used in the site. Defaults to "en" /// The language used in the site. Defaults to "en"
pub language_code: Option<String>,
pub default_language: Option<String>,
/// Whether to generate RSS. Defaults to false /// Whether to generate RSS. Defaults to false
pub generate_rss: Option<bool>, pub generate_rss: Option<bool>,
/// The number of articles to include in the RSS feed. Defaults to unlimited /// The number of articles to include in the RSS feed. Defaults to unlimited
@@ -50,6 +50,9 @@ pub struct Config {
/// Whether to compile the `sass` directory and output the css files into the static folder /// Whether to compile the `sass` directory and output the css files into the static folder
pub compile_sass: Option<bool>, pub compile_sass: Option<bool>,


/// Languages list and translated strings
pub translations: Option<HashMap<String, Toml>>,

/// All user params set in [extra] in the config /// All user params set in [extra] in the config
pub extra: Option<HashMap<String, Toml>>, pub extra: Option<HashMap<String, Toml>>,


@@ -74,13 +77,14 @@ impl Config {
Err(e) => bail!(e) Err(e) => bail!(e)
}; };


set_default!(config.language_code, "en".to_string());
set_default!(config.default_language, "en".to_string());
set_default!(config.highlight_code, false); set_default!(config.highlight_code, false);
set_default!(config.generate_rss, false); set_default!(config.generate_rss, false);
set_default!(config.rss_limit, 20); set_default!(config.rss_limit, 20);
set_default!(config.generate_tags_pages, false); set_default!(config.generate_tags_pages, false);
set_default!(config.generate_categories_pages, false); set_default!(config.generate_categories_pages, false);
set_default!(config.compile_sass, false); set_default!(config.compile_sass, false);
set_default!(config.translations, HashMap::new());
set_default!(config.extra, HashMap::new()); set_default!(config.extra, HashMap::new());


match config.highlight_theme { match config.highlight_theme {
@@ -120,6 +124,8 @@ impl Config {
format!("{}{}{}", self.base_url, &path[1..], trailing_bit) format!("{}{}{}", self.base_url, &path[1..], trailing_bit)
} else if self.base_url.ends_with('/') { } else if self.base_url.ends_with('/') {
format!("{}{}{}", self.base_url, path, trailing_bit) format!("{}{}{}", self.base_url, path, trailing_bit)
} else if path.starts_with('/') {
format!("{}{}{}", self.base_url, path, trailing_bit)
} else { } else {
format!("{}/{}{}", self.base_url, path, trailing_bit) format!("{}/{}{}", self.base_url, path, trailing_bit)
} }
@@ -164,12 +170,13 @@ impl Default for Config {
highlight_code: Some(true), highlight_code: Some(true),
highlight_theme: Some("base16-ocean-dark".to_string()), highlight_theme: Some("base16-ocean-dark".to_string()),
description: None, description: None,
language_code: Some("en".to_string()),
default_language: Some("en".to_string()),
generate_rss: Some(false), generate_rss: Some(false),
rss_limit: Some(10_000), rss_limit: Some(10_000),
generate_tags_pages: Some(true), generate_tags_pages: Some(true),
generate_categories_pages: Some(true), generate_categories_pages: Some(true),
compile_sass: Some(false), compile_sass: Some(false),
translations: None,
extra: None, extra: None,
build_timestamp: Some(1), build_timestamp: Some(1),
} }
@@ -272,6 +279,13 @@ hello = "world"
assert_eq!(config.make_permalink("/hello"), "http://vincent.is/hello/"); assert_eq!(config.make_permalink("/hello"), "http://vincent.is/hello/");
} }


#[test]
fn can_make_url_with_localhost() {
let mut config = Config::default();
config.base_url = "http://127.0.0.1:1111".to_string();
assert_eq!(config.make_permalink("/tags/rust"), "http://127.0.0.1:1111/tags/rust/");
}

#[test] #[test]
fn can_merge_with_theme_data_and_preserve_config_value() { fn can_merge_with_theme_data_and_preserve_config_value() {
let config_str = r#" let config_str = r#"
@@ -293,4 +307,27 @@ a_value = 10
assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string()); assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string());
assert_eq!(extra["a_value"].as_integer().unwrap(), 10); assert_eq!(extra["a_value"].as_integer().unwrap(), 10);
} }

#[test]
fn can_use_language_configuration() {
let config = r#"
base_url = "https://remplace-par-ton-url.fr"
default_language = "fr"

[translations]
[translations.fr]
title = "Un titre"

[translations.en]
title = "A title"

"#;

let config = Config::parse(config);
assert!(config.is_ok());
let translations = config.unwrap().translations.unwrap();
assert_eq!(translations["fr"]["title"].as_str().unwrap(), "Un titre");
assert_eq!(translations["en"]["title"].as_str().unwrap(), "A title");
}

} }

+ 3
- 2
components/content/Cargo.toml View File

@@ -4,10 +4,10 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
tera = "0.10"
tera = "0.11.0"
serde = "1.0" serde = "1.0"
slug = "0.1" slug = "0.1"
rayon = "0.8"
rayon = "0.9"


errors = { path = "../errors" } errors = { path = "../errors" }
config = { path = "../config" } config = { path = "../config" }
@@ -17,3 +17,4 @@ front_matter = { path = "../front_matter" }


[dev-dependencies] [dev-dependencies]
tempdir = "0.3" tempdir = "0.3"
toml = "0.4"

+ 1
- 1
components/content/src/file_info.rs View File

@@ -8,7 +8,7 @@ pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> {
let mut components = vec![]; let mut components = vec![];


for section in path.parent().unwrap().components() { for section in path.parent().unwrap().components() {
let component = section.as_ref().to_string_lossy();
let component = section.as_os_str().to_string_lossy();


if is_in_content { if is_in_content {
components.push(component.to_string()); components.push(component.to_string());


+ 2
- 0
components/content/src/lib.rs View File

@@ -11,6 +11,8 @@ extern crate utils;


#[cfg(test)] #[cfg(test)]
extern crate tempdir; extern crate tempdir;
#[cfg(test)]
extern crate toml;


mod file_info; mod file_info;
mod page; mod page;


+ 6
- 2
components/content/src/page.rs View File

@@ -103,7 +103,6 @@ impl Page {


if let Some(ref p) = page.meta.path { if let Some(ref p) = page.meta.path {
page.path = p.trim().trim_left_matches('/').to_string(); page.path = p.trim().trim_left_matches('/').to_string();

} else { } else {
page.path = if page.file.components.is_empty() { page.path = if page.file.components.is_empty() {
page.slug.clone() page.slug.clone()
@@ -207,7 +206,12 @@ impl ser::Serialize for Page {
state.serialize_field("content", &self.content)?; state.serialize_field("content", &self.content)?;
state.serialize_field("title", &self.meta.title)?; state.serialize_field("title", &self.meta.title)?;
state.serialize_field("description", &self.meta.description)?; state.serialize_field("description", &self.meta.description)?;
state.serialize_field("date", &self.meta.date)?;
// From a TOML datetime to a String first
let date = match self.meta.date {
Some(ref d) => Some(d.to_string()),
None => None,
};
state.serialize_field("date", &date)?;
state.serialize_field("slug", &self.slug)?; state.serialize_field("slug", &self.slug)?;
state.serialize_field("path", &self.path)?; state.serialize_field("path", &self.path)?;
state.serialize_field("components", &self.components)?; state.serialize_field("components", &self.components)?;


+ 7
- 4
components/content/src/sorting.rs View File

@@ -98,13 +98,16 @@ pub fn populate_previous_and_next_pages(input: &[Page]) -> Vec<Page> {


#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str::FromStr;
use toml::value::Datetime;

use front_matter::{PageFrontMatter, SortBy}; use front_matter::{PageFrontMatter, SortBy};
use page::Page; use page::Page;
use super::{sort_pages, populate_previous_and_next_pages}; use super::{sort_pages, populate_previous_and_next_pages};


fn create_page_with_date(date: &str) -> Page { fn create_page_with_date(date: &str) -> Page {
let mut front_matter = PageFrontMatter::default(); let mut front_matter = PageFrontMatter::default();
front_matter.date = Some(date.to_string());
front_matter.date = Some(Datetime::from_str(date).unwrap());
Page::new("content/hello.md", front_matter) Page::new("content/hello.md", front_matter)
} }


@@ -136,9 +139,9 @@ mod tests {
]; ];
let (pages, _) = sort_pages(input, SortBy::Date); let (pages, _) = sort_pages(input, SortBy::Date);
// Should be sorted by date // Should be sorted by date
assert_eq!(pages[0].clone().meta.date.unwrap(), "2019-01-01");
assert_eq!(pages[1].clone().meta.date.unwrap(), "2018-01-01");
assert_eq!(pages[2].clone().meta.date.unwrap(), "2017-01-01");
assert_eq!(pages[0].clone().meta.date.unwrap().to_string(), "2019-01-01");
assert_eq!(pages[1].clone().meta.date.unwrap().to_string(), "2018-01-01");
assert_eq!(pages[2].clone().meta.date.unwrap().to_string(), "2017-01-01");
} }


#[test] #[test]


+ 2
- 2
components/errors/Cargo.toml View File

@@ -4,6 +4,6 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
error-chain = "0.10"
tera = "0.10"
error-chain = "0.11"
tera = "0.11.0"
toml = "0.4" toml = "0.4"

+ 2
- 2
components/front_matter/Cargo.toml View File

@@ -4,13 +4,13 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
tera = "0.10"
tera = "0.11.0"
chrono = "0.4" chrono = "0.4"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
toml = "0.4" toml = "0.4"
regex = "0.2" regex = "0.2"
lazy_static = "0.2"
lazy_static = "1"




errors = { path = "../errors" } errors = { path = "../errors" }

+ 4
- 4
components/front_matter/src/lib.rs View File

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


+ 41
- 18
components/front_matter/src/page.rs View File

@@ -6,6 +6,7 @@ use toml;


use errors::Result; use errors::Result;



/// The front matter of every page /// The front matter of every page
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PageFrontMatter { pub struct PageFrontMatter {
@@ -14,7 +15,7 @@ pub struct PageFrontMatter {
/// Description in <meta> that appears when linked, e.g. on twitter /// Description in <meta> that appears when linked, e.g. on twitter
pub description: Option<String>, pub description: Option<String>,
/// Date if we want to order pages (ie blog post) /// Date if we want to order pages (ie blog post)
pub date: Option<String>,
pub date: Option<toml::value::Datetime>,
/// Whether this page is a draft and should be ignored for pagination etc /// Whether this page is a draft and should be ignored for pagination etc
pub draft: Option<bool>, pub draft: Option<bool>,
/// The page slug. Will be used instead of the filename if present /// The page slug. Will be used instead of the filename if present
@@ -71,17 +72,17 @@ impl PageFrontMatter {
Ok(f) Ok(f)
} }


/// Converts the date in the front matter, which can be in 2 formats, into a NaiveDateTime
/// Converts the TOML datetime to a Chrono naive datetime
pub fn date(&self) -> Option<NaiveDateTime> { pub fn date(&self) -> Option<NaiveDateTime> {
match self.date {
Some(ref d) => {
if d.contains('T') {
DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local()))
} else {
NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0)))
}
},
None => None,
if let Some(ref d) = self.date {
let d2 = d.to_string();
if d2.contains('T') {
DateTime::parse_from_rfc3339(&d2).ok().and_then(|s| Some(s.naive_local()))
} else {
NaiveDate::parse_from_str(&d2, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0, 0, 0)))
}
} else {
None
} }
} }


@@ -121,6 +122,7 @@ impl Default for PageFrontMatter {
} }
} }



#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::PageFrontMatter; use super::PageFrontMatter;
@@ -203,9 +205,10 @@ mod tests {
let content = r#" let content = r#"
title = "Hello" title = "Hello"
description = "hey there" description = "hey there"
date = "2016-10-10""#;
date = 2016-10-10
"#;
let res = PageFrontMatter::parse(content).unwrap(); let res = PageFrontMatter::parse(content).unwrap();
assert!(res.date().is_some());
assert!(res.date.is_some());
} }


#[test] #[test]
@@ -213,9 +216,10 @@ mod tests {
let content = r#" let content = r#"
title = "Hello" title = "Hello"
description = "hey there" description = "hey there"
date = "2002-10-02T15:00:00Z""#;
date = 2002-10-02T15:00:00Z
"#;
let res = PageFrontMatter::parse(content).unwrap(); let res = PageFrontMatter::parse(content).unwrap();
assert!(res.date().is_some());
assert!(res.date.is_some());
} }


#[test] #[test]
@@ -223,9 +227,28 @@ mod tests {
let content = r#" let content = r#"
title = "Hello" title = "Hello"
description = "hey there" description = "hey there"
date = "2002/10/12""#;
let res = PageFrontMatter::parse(content).unwrap();
assert!(res.date().is_none());
date = 2002/10/12"#;
let res = PageFrontMatter::parse(content);
assert!(res.is_err());
} }


#[test]
fn cannot_parse_invalid_date_format() {
let content = r#"
title = "Hello"
description = "hey there"
date = 2002-14-01"#;
let res = PageFrontMatter::parse(content);
assert!(res.is_err());
}

#[test]
fn cannot_parse_date_as_string() {
let content = r#"
title = "Hello"
description = "hey there"
date = "2002-14-01""#;
let res = PageFrontMatter::parse(content);
assert!(res.is_err());
}
} }

+ 2
- 2
components/highlighting/Cargo.toml View File

@@ -4,5 +4,5 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
lazy_static = "0.2"
syntect = { version = "1", features = ["static-onig"] }
lazy_static = "1"
syntect = "2"

+ 1
- 1
components/pagination/Cargo.toml View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
tera = "0.10"
tera = "0.11.0"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"




+ 3
- 3
components/rendering/Cargo.toml View File

@@ -4,10 +4,10 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
tera = "0.10"
tera = "0.11.0"
regex = "0.2" regex = "0.2"
lazy_static = "0.2"
syntect = { version = "1", features = ["static-onig"] }
lazy_static = "1"
syntect = "2"
pulldown-cmark = "0" pulldown-cmark = "0"
slug = "0.1" slug = "0.1"
serde = "1.0" serde = "1.0"


+ 1
- 1
components/rendering/tests/markdown.rs View File

@@ -54,7 +54,7 @@ fn can_highlight_code_block_with_lang() {
let res = markdown_to_html("```python\nlist.append(1)\n```", &context).unwrap(); let res = markdown_to_html("```python\nlist.append(1)\n```", &context).unwrap();
assert_eq!( assert_eq!(
res.0, res.0,
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">\n</span></pre>"
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)\n</span></pre>"
); );
} }




+ 2
- 3
components/site/Cargo.toml View File

@@ -4,14 +4,13 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
tera = "0.10"
tera = "0.11.0"
glob = "0.2" glob = "0.2"
walkdir = "2" walkdir = "2"
rayon = "0.8"
rayon = "0.9"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
sass-rs = "0.2" sass-rs = "0.2"
#sass-rs = { git = "https://github.com/compass-rs/sass-rs.git" }


errors = { path = "../errors" } errors = { path = "../errors" }
config = { path = "../config" } config = { path = "../config" }


+ 48
- 25
components/site/src/lib.rs View File

@@ -151,8 +151,6 @@ impl Site {
orphans orphans
} }


/// Used by tests to change the output path to a tmp dir
#[doc(hidden)]
pub fn set_output_path<P: AsRef<Path>>(&mut self, path: P) { pub fn set_output_path<P: AsRef<Path>>(&mut self, path: P) {
self.output_path = path.as_ref().to_path_buf(); self.output_path = path.as_ref().to_path_buf();
} }
@@ -219,38 +217,55 @@ impl Site {
self.add_page(p, false)?; self.add_page(p, false)?;
} }


{
// Another silly thing needed to not borrow &self in parallel and
// make the borrow checker happy
let permalinks = &self.permalinks;
let tera = &self.tera;
let config = &self.config;
self.render_markdown()?;
self.populate_sections();
self.populate_tags_and_categories();


self.pages.par_iter_mut()
.map(|(_, page)| {
let insert_anchor = pages_insert_anchors[&page.file.path];
page.render_markdown(permalinks, tera, config, insert_anchor)
})
.fold(|| Ok(()), Result::and)
.reduce(|| Ok(()), Result::and)?;
self.register_tera_global_fns();


self.sections.par_iter_mut()
.map(|(_, section)| section.render_markdown(permalinks, tera, config))
.fold(|| Ok(()), Result::and)
.reduce(|| Ok(()), Result::and)?;
Ok(())
}

/// Render the markdown of all pages/sections
/// Used in a build and in `serve` if a shortcode has changed
pub fn render_markdown(&mut self) -> Result<()> {
// Another silly thing needed to not borrow &self in parallel and
// make the borrow checker happy
let permalinks = &self.permalinks;
let tera = &self.tera;
let config = &self.config;

// TODO: avoid the duplication with function above for that part
// This is needed in the first place because of silly borrow checker
let mut pages_insert_anchors = HashMap::new();
for (_, p) in &self.pages {
pages_insert_anchors.insert(p.file.path.clone(), self.find_parent_section_insert_anchor(&p.file.parent.clone()));
} }


self.populate_sections();
self.populate_tags_and_categories();
self.pages.par_iter_mut()
.map(|(_, page)| {
let insert_anchor = pages_insert_anchors[&page.file.path];
page.render_markdown(permalinks, tera, config, insert_anchor)
})
.fold(|| Ok(()), Result::and)
.reduce(|| Ok(()), Result::and)?;


self.register_tera_global_fns();
self.sections.par_iter_mut()
.map(|(_, section)| section.render_markdown(permalinks, tera, config))
.fold(|| Ok(()), Result::and)
.reduce(|| Ok(()), Result::and)?;


Ok(()) Ok(())
} }


pub fn register_tera_global_fns(&mut self) { pub fn register_tera_global_fns(&mut self) {
self.tera.register_global_function("trans", global_fns::make_trans(self.config.clone()));
self.tera.register_global_function("get_page", global_fns::make_get_page(&self.pages)); self.tera.register_global_function("get_page", global_fns::make_get_page(&self.pages));
self.tera.register_global_function("get_section", global_fns::make_get_section(&self.sections)); self.tera.register_global_function("get_section", global_fns::make_get_section(&self.sections));
self.tera.register_global_function(
"get_taxonomy_url",
global_fns::make_get_taxonomy_url(self.tags.clone(), self.categories.clone())
);
self.tera.register_global_function( self.tera.register_global_function(
"get_url", "get_url",
global_fns::make_get_url(self.permalinks.clone(), self.config.clone()) global_fns::make_get_url(self.permalinks.clone(), self.config.clone())
@@ -318,6 +333,8 @@ impl Site {
section.ignored_pages = vec![]; section.ignored_pages = vec![];
} }


// TODO: use references instead of cloning to avoid having to call populate_section on
// content change
for page in self.pages.values() { for page in self.pages.values() {
let parent_section_path = page.file.parent.join("_index.md"); let parent_section_path = page.file.parent.join("_index.md");
if self.sections.contains_key(&parent_section_path) { if self.sections.contains_key(&parent_section_path) {
@@ -443,7 +460,7 @@ impl Site {
pub fn clean(&self) -> Result<()> { pub fn clean(&self) -> Result<()> {
if self.output_path.exists() { if self.output_path.exists() {
// Delete current `public` directory so we can start fresh // Delete current `public` directory so we can start fresh
remove_dir_all(&self.output_path).chain_err(|| "Couldn't delete `public` directory")?;
remove_dir_all(&self.output_path).chain_err(|| "Couldn't delete output directory")?;
} }


Ok(()) Ok(())
@@ -620,7 +637,13 @@ impl Site {
&self.pages &self.pages
.values() .values()
.filter(|p| !p.is_draft()) .filter(|p| !p.is_draft())
.map(|p| SitemapEntry::new(p.permalink.clone(), p.meta.date.clone()))
.map(|p| {
let date = match p.meta.date {
Some(ref d) => Some(d.to_string()),
None => None,
};
SitemapEntry::new(p.permalink.clone(), date)
})
.collect::<Vec<_>>() .collect::<Vec<_>>()
); );
context.add( context.add(
@@ -678,7 +701,7 @@ impl Site {
} }


let (sorted_pages, _) = sort_pages(pages, SortBy::Date); let (sorted_pages, _) = sort_pages(pages, SortBy::Date);
context.add("last_build_date", &sorted_pages[0].meta.date);
context.add("last_build_date", &sorted_pages[0].meta.date.clone().map(|d| d.to_string()));
// limit to the last n elements) // limit to the last n elements)
context.add("pages", &sorted_pages.iter().take(self.config.rss_limit.unwrap()).collect::<Vec<_>>()); context.add("pages", &sorted_pages.iter().take(self.config.rss_limit.unwrap()).collect::<Vec<_>>());
context.add("config", &self.config); context.add("config", &self.config);


+ 1
- 1
components/site/test_site/content/posts/draft.md View File

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



+ 1
- 1
components/site/test_site/content/posts/fixed-slug.md View File

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




+ 1
- 1
components/site/test_site/content/posts/fixed-url.md View File

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


A simple page with fixed url A simple page with fixed url

+ 1
- 1
components/site/test_site/content/posts/python.md View File

@@ -1,7 +1,7 @@
+++ +++
title = "Python in posts" title = "Python in posts"
description = "" description = ""
date = "2017-03-01"
date = 2017-03-01
+++ +++


Same filename but different path Same filename but different path


+ 1
- 1
components/site/test_site/content/posts/simple.md View File

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


A simple page A simple page


+ 1
- 1
components/site/test_site/content/posts/tutorials/devops/docker.md View File

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


A simple page A simple page

+ 1
- 1
components/site/test_site/content/posts/tutorials/devops/nix.md View File

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


A simple page A simple page

+ 1
- 1
components/site/test_site/content/posts/tutorials/programming/python.md View File

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


A simple page A simple page

+ 1
- 1
components/site/test_site/content/posts/tutorials/programming/rust.md View File

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


A simple page A simple page

+ 1
- 1
components/site/test_site/templates/index.html View File

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


+ 1
- 1
components/site/test_site/templates/index_paginated.html View File

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


+ 1
- 1
components/taxonomies/Cargo.toml View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
tera = "0.10"
tera = "0.11.0"
slug = "0.1" slug = "0.1"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"


+ 6
- 6
components/taxonomies/src/lib.rs View File

@@ -44,7 +44,7 @@ impl TaxonomyItem {
let (mut pages, ignored_pages) = sort_pages(pages, SortBy::Date); let (mut pages, ignored_pages) = sort_pages(pages, SortBy::Date);
let slug = slugify(name); let slug = slugify(name);
let permalink = { let permalink = {
let kind_path = if kind == TaxonomyKind::Tags { "tag" } else { "category" };
let kind_path = if kind == TaxonomyKind::Tags { "tags" } else { "categories" };
config.make_permalink(&format!("/{}/{}", kind_path, slug)) config.make_permalink(&format!("/{}/{}", kind_path, slug))
}; };


@@ -188,27 +188,27 @@ mod tests {


assert_eq!(tags.items[0].name, "db"); assert_eq!(tags.items[0].name, "db");
assert_eq!(tags.items[0].slug, "db"); assert_eq!(tags.items[0].slug, "db");
assert_eq!(tags.items[0].permalink, "http://a-website.com/tag/db/");
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/");
assert_eq!(tags.items[0].pages.len(), 1); assert_eq!(tags.items[0].pages.len(), 1);


assert_eq!(tags.items[1].name, "js"); assert_eq!(tags.items[1].name, "js");
assert_eq!(tags.items[1].slug, "js"); assert_eq!(tags.items[1].slug, "js");
assert_eq!(tags.items[1].permalink, "http://a-website.com/tag/js/");
assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/js/");
assert_eq!(tags.items[1].pages.len(), 2); assert_eq!(tags.items[1].pages.len(), 2);


assert_eq!(tags.items[2].name, "rust"); assert_eq!(tags.items[2].name, "rust");
assert_eq!(tags.items[2].slug, "rust"); assert_eq!(tags.items[2].slug, "rust");
assert_eq!(tags.items[2].permalink, "http://a-website.com/tag/rust/");
assert_eq!(tags.items[2].permalink, "http://a-website.com/tags/rust/");
assert_eq!(tags.items[2].pages.len(), 2); assert_eq!(tags.items[2].pages.len(), 2);


assert_eq!(categories.items[0].name, "Other"); assert_eq!(categories.items[0].name, "Other");
assert_eq!(categories.items[0].slug, "other"); assert_eq!(categories.items[0].slug, "other");
assert_eq!(categories.items[0].permalink, "http://a-website.com/category/other/");
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/other/");
assert_eq!(categories.items[0].pages.len(), 1); assert_eq!(categories.items[0].pages.len(), 1);


assert_eq!(categories.items[1].name, "Programming tutorials"); assert_eq!(categories.items[1].name, "Programming tutorials");
assert_eq!(categories.items[1].slug, "programming-tutorials"); assert_eq!(categories.items[1].slug, "programming-tutorials");
assert_eq!(categories.items[1].permalink, "http://a-website.com/category/programming-tutorials/");
assert_eq!(categories.items[1].permalink, "http://a-website.com/categories/programming-tutorials/");
assert_eq!(categories.items[1].pages.len(), 1); assert_eq!(categories.items[1].pages.len(), 1);
} }
} }

+ 4
- 3
components/templates/Cargo.toml View File

@@ -4,12 +4,13 @@ version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
tera = "0.10"
base64 = "0.7"
lazy_static = "0.2"
tera = "0.11.0"
base64 = "0.9"
lazy_static = "1"
pulldown-cmark = "0" pulldown-cmark = "0"


errors = { path = "../errors" } errors = { path = "../errors" }
utils = { path = "../utils" } utils = { path = "../utils" }
content = { path = "../content" } content = { path = "../content" }
config = { path = "../config" } config = { path = "../config" }
taxonomies = { path = "../taxonomies" }

+ 1
- 1
components/templates/src/builtins/rss.xml View File

@@ -4,7 +4,7 @@
<link>{{ config.base_url }}</link> <link>{{ config.base_url }}</link>
<description>{{ config.description }}</description> <description>{{ config.description }}</description>
<generator>Gutenberg</generator> <generator>Gutenberg</generator>
<language>{{ config.language_code }}</language>
<language>{{ config.default_language }}</language>
<atom:link href="{{ feed_url }}" rel="self" type="application/rss+xml"/> <atom:link href="{{ feed_url }}" rel="self" type="application/rss+xml"/>
<lastBuildDate>{{ last_build_date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate> <lastBuildDate>{{ last_build_date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate>
{% for page in pages %} {% for page in pages %}


+ 160
- 44
components/templates/src/global_fns.rs View File

@@ -6,6 +6,34 @@ use tera::{GlobalFn, Value, from_value, to_value, Result};
use content::{Page, Section}; use content::{Page, Section};
use config::Config; use config::Config;
use utils::site::resolve_internal_link; use utils::site::resolve_internal_link;
use taxonomies::Taxonomy;


macro_rules! required_string_arg {
($e: expr, $err: expr) => {
match $e {
Some(v) => match from_value::<String>(v.clone()) {
Ok(u) => u,
Err(_) => return Err($err.into())
},
None => return Err($err.into())
};
};
}


pub fn make_trans(config: Config) -> GlobalFn {
let translations_config = config.translations.unwrap();
let default_lang = to_value(config.default_language.unwrap()).unwrap();

Box::new(move |args| -> Result<Value> {
let key = required_string_arg!(args.get("key"), "`trans` requires a `key` argument.");
let lang_arg = args.get("lang").unwrap_or(&default_lang).clone();
let lang = from_value::<String>(lang_arg).unwrap();
let translations = &translations_config[lang.as_str()];
Ok(to_value(&translations[key.as_str()]).unwrap())
})
}




pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn { pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn {
@@ -15,17 +43,10 @@ pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn {
} }


Box::new(move |args| -> Result<Value> { Box::new(move |args| -> Result<Value> {
match args.get("path") {
Some(val) => match from_value::<String>(val.clone()) {
Ok(v) => {
match pages.get(&v) {
Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Page `{}` not found.", v).into())
}
},
Err(_) => Err(format!("`get_page` received path={:?} but it requires a string", val).into()),
},
None => Err("`get_page` requires a `path` argument.".into()),
let path = required_string_arg!(args.get("path"), "`get_page` requires a `path` argument with a string value");
match pages.get(&path) {
Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Page `{}` not found.", path).into())
} }
}) })
} }
@@ -37,17 +58,10 @@ pub fn make_get_section(all_sections: &HashMap<PathBuf, Section>) -> GlobalFn {
} }


Box::new(move |args| -> Result<Value> { Box::new(move |args| -> Result<Value> {
match args.get("path") {
Some(val) => match from_value::<String>(val.clone()) {
Ok(v) => {
match sections.get(&v) {
Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Section `{}` not found.", v).into())
}
},
Err(_) => Err(format!("`get_section` received path={:?} but it requires a string", val).into()),
},
None => Err("`get_section` requires a `path` argument.".into()),
let path = required_string_arg!(args.get("path"), "`get_section` requires a `path` argument with a string value");
match sections.get(&path) {
Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Section `{}` not found.", path).into())
} }
}) })
} }
@@ -60,40 +74,66 @@ pub fn make_get_url(permalinks: HashMap<String, String>, config: Config) -> Glob
from_value::<bool>(c.clone()).unwrap_or(false) from_value::<bool>(c.clone()).unwrap_or(false)
}); });


match args.get("path") {
Some(val) => match from_value::<String>(val.clone()) {
Ok(v) => {
// Internal link
if v.starts_with("./") {
match resolve_internal_link(&v, &permalinks) {
Ok(url) => Ok(to_value(url).unwrap()),
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", v).into())
}
} else {
// anything else
let mut permalink = config.make_permalink(&v);
if cachebust {
permalink = format!("{}?t={}", permalink, config.build_timestamp.unwrap());
}
Ok(to_value(permalink).unwrap())
}
},
Err(_) => Err(format!("`get_url` received path={:?} but it requires a string", val).into()),
},
None => Err("`get_url` requires a `path` argument.".into()),
let trailing_slash = args
.get("trailing_slash")
.map_or(true, |c| {
from_value::<bool>(c.clone()).unwrap_or(true)
});

let path = required_string_arg!(args.get("path"), "`get_url` requires a `path` argument with a string value");
if path.starts_with("./") {
match resolve_internal_link(&path, &permalinks) {
Ok(url) => Ok(to_value(url).unwrap()),
Err(_) => Err(format!("Could not resolve URL for link `{}` not found.", path).into())
}
} else {
// anything else
let mut permalink = config.make_permalink(&path);
if !trailing_slash && permalink.ends_with("/") {
permalink.pop(); // Removes the slash
}

if cachebust {
permalink = format!("{}?t={}", permalink, config.build_timestamp.unwrap());
}
Ok(to_value(permalink).unwrap())
}
})
}

pub fn make_get_taxonomy_url(tags: Option<Taxonomy>, categories: Option<Taxonomy>) -> GlobalFn {
Box::new(move |args| -> Result<Value> {
let kind = required_string_arg!(args.get("kind"), "`get_taxonomy_url` requires a `kind` argument with a string value");
let name = required_string_arg!(args.get("name"), "`get_taxonomy_url` requires a `name` argument with a string value");
let container = match kind.as_ref() {
"tag" => &tags,
"category" => &categories,
_ => return Err("`get_taxonomy_url` can only get `tag` or `category` for the `kind` argument".into()),
};

if let Some(ref c) = *container {
for item in &c.items {
if item.name == name {
return Ok(to_value(item.permalink.clone()).unwrap());
}
}
bail!("`get_taxonomy_url`: couldn't find `{}` in `{}` taxonomy", name, kind);
} else {
bail!("`get_taxonomy_url` tried to get a taxonomy of kind `{}` but there isn't any", kind);
} }
}) })
} }


#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::make_get_url;
use super::{make_get_url, make_get_taxonomy_url, make_trans};


use std::collections::HashMap; use std::collections::HashMap;


use tera::to_value; use tera::to_value;


use config::Config; use config::Config;
use taxonomies::{Taxonomy, TaxonomyKind, TaxonomyItem};




#[test] #[test]
@@ -106,6 +146,27 @@ mod tests {
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css/?t=1"); assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css/?t=1");
} }


#[test]
fn can_remove_trailing_slashes() {
let config = Config::default();
let static_fn = make_get_url(HashMap::new(), config);
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("app.css").unwrap());
args.insert("trailing_slash".to_string(), to_value(false).unwrap());
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css");
}

#[test]
fn can_remove_slashes_and_cachebust() {
let config = Config::default();
let static_fn = make_get_url(HashMap::new(), config);
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("app.css").unwrap());
args.insert("trailing_slash".to_string(), to_value(false).unwrap());
args.insert("cachebust".to_string(), to_value(true).unwrap());
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css?t=1");
}

#[test] #[test]
fn can_link_to_some_static_file() { fn can_link_to_some_static_file() {
let config = Config::default(); let config = Config::default();
@@ -114,4 +175,59 @@ mod tests {
args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("path".to_string(), to_value("app.css").unwrap());
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css/"); assert_eq!(static_fn(args).unwrap(), "http://a-website.com/app.css/");
} }

#[test]
fn can_get_tag_url() {
let tag = TaxonomyItem::new(
"Prog amming",
TaxonomyKind::Tags,
&Config::default(),
vec![],
);
let tags = Taxonomy {
kind: TaxonomyKind::Tags,
items: vec![tag],
};

let static_fn = make_get_taxonomy_url(Some(tags), None);
// can find it correctly
let mut args = HashMap::new();
args.insert("kind".to_string(), to_value("tag").unwrap());
args.insert("name".to_string(), to_value("Prog amming").unwrap());
assert_eq!(static_fn(args).unwrap(), "http://a-website.com/tags/prog-amming/");
// and errors if it can't find it
let mut args = HashMap::new();
args.insert("kind".to_string(), to_value("tag").unwrap());
args.insert("name".to_string(), to_value("random").unwrap());
assert!(static_fn(args).is_err());
}

#[test]
fn can_translate_a_string() {
let trans_config = r#"
base_url = "https://remplace-par-ton-url.fr"
default_language = "fr"

[translations]
[translations.fr]
title = "Un titre"

[translations.en]
title = "A title"

"#;

let config = Config::parse(trans_config).unwrap();
let static_fn = make_trans(config);
let mut args = HashMap::new();

args.insert("key".to_string(), to_value("title").unwrap());
assert_eq!(static_fn(args.clone()).unwrap(), "Un titre");

args.insert("lang".to_string(), to_value("en").unwrap());
assert_eq!(static_fn(args.clone()).unwrap(), "A title");

args.insert("lang".to_string(), to_value("fr").unwrap());
assert_eq!(static_fn(args.clone()).unwrap(), "Un titre");
}
} }

+ 2
- 0
components/templates/src/lib.rs View File

@@ -5,10 +5,12 @@ extern crate tera;
extern crate base64; extern crate base64;
extern crate pulldown_cmark; extern crate pulldown_cmark;


#[macro_use]
extern crate errors; extern crate errors;
extern crate utils; extern crate utils;
extern crate content; extern crate content;
extern crate config; extern crate config;
extern crate taxonomies;


pub mod filters; pub mod filters;
pub mod global_fns; pub mod global_fns;


+ 1
- 1
components/utils/Cargo.toml View File

@@ -5,7 +5,7 @@ authors = ["Vincent Prouillet <vincent@wearewizards.io>"]


[dependencies] [dependencies]
errors = { path = "../errors" } errors = { path = "../errors" }
tera = "0.10"
tera = "0.11.0"




[dev-dependencies] [dev-dependencies]


+ 43
- 0
components/utils/src/default_tpl.html View File

@@ -0,0 +1,43 @@
<html>
<head>
<title>Gutenberg</title>
</head>
<body>
<div class="container">
<h1>Welcome to Gutenberg!</h1>
<p>
You're seeing this page because we couldn't find a template to render.
</p>
<p>
To modify this page, create a <b>{{filename}}</b> file in the templates directory or
<a href="https://www.getgutenberg.io/documentation/themes/installing-and-using-themes/" target="_blank">install a theme</a>.
<br>
You can find what variables are available in this template in the <a href="{{url}}" target="_blank">documentation</a>.
</p>
</div>
<footer>
<a href="https://www.getgutenberg.io/documentation/getting-started/cli-usage/" target="_blank">Get started with Gutenberg</a>
</footer>
<style>
html {
line-height: 1.5;
}
h1 {
margin-bottom: 2rem;
}
.container {
font-family: "sans-serif";
text-align: center;
margin-top: 20vh;
padding: 2rem;
background: #e9e9e9;
}
footer {
position: fixed;
width: 100%;
bottom: 1rem;
text-align: center;
}
</style>
</body>
</html>

+ 27
- 5
components/utils/src/templates.rs View File

@@ -2,6 +2,20 @@ use tera::{Tera, Context};


use errors::Result; use errors::Result;


static DEFAULT_TPL: &str = include_str!("default_tpl.html");


macro_rules! render_default_tpl {
($filename: expr, $url: expr) => {
{
let mut context = Context::new();
context.add("filename", $filename);
context.add("url", $url);
Tera::one_off(DEFAULT_TPL, &context, true).map_err(|e| e.into())
}
};
}

/// Renders the given template with the given context, but also ensures that, if the default file /// Renders the given template with the given context, but also ensures that, if the default file
/// is not found, it will look up for the equivalent template for the current theme if there is one. /// is not found, it will look up for the equivalent template for the current theme if there is one.
/// Lastly, if it's a default template (index, section or page), it will just return an empty string /// Lastly, if it's a default template (index, section or page), it will just return an empty string
@@ -19,11 +33,19 @@ pub fn render_template(name: &str, tera: &Tera, context: &Context, theme: Option
.map_err(|e| e.into()); .map_err(|e| e.into());
} }


if name == "index.html" || name == "section.html" || name == "page.html" {
return Ok(String::new());
// maybe it's a default one?
match name {
"index.html" | "section.html" => {
render_default_tpl!(name, "https://www.getgutenberg.io/documentation/templates/pages-sections/#section-variables")
},
"page.html" => {
render_default_tpl!(name, "https://www.getgutenberg.io/documentation/templates/pages-sections/#page-variables")
},
"tag.html" | "tags.html" | "category.html" | "categories.html" => {
render_default_tpl!(name, "https://www.getgutenberg.io/documentation/templates/tags-categories/")
},
_ => bail!("Tried to render `{}` but the template wasn't found", name)
} }

bail!("Tried to render `{}` but the template wasn't found", name)
} }




@@ -55,7 +77,7 @@ mod tests {


#[test] #[test]
fn can_rewrite_all_paths_of_theme() { fn can_rewrite_all_paths_of_theme() {
let mut tera = Tera::parse("templates/*.html").unwrap();
let mut tera = Tera::parse("test-templates/*.html").unwrap();
rewrite_theme_paths(&mut tera, "hyde"); rewrite_theme_paths(&mut tera, "hyde");
// special case to make the test work: we also rename the files to // special case to make the test work: we also rename the files to
// match the imports // match the imports


components/utils/templates/child.html → components/utils/test-templates/child.html View File


components/utils/templates/included.html → components/utils/test-templates/included.html View File


components/utils/templates/index.html → components/utils/test-templates/index.html View File


components/utils/templates/macros.html → components/utils/test-templates/macros.html View File


components/utils/templates/using-macros.html → components/utils/test-templates/using-macros.html View File


+ 1
- 0
docs/content/documentation/content/page.md View File

@@ -22,6 +22,7 @@ description = ""


# The date of the post. # The date of the post.
# 2 formats are allowed: YYYY-MM-DD (2012-10-02) and RFC3339 (2002-10-02T15:00:00Z) # 2 formats are allowed: YYYY-MM-DD (2012-10-02) and RFC3339 (2002-10-02T15:00:00Z)
# Do not wrap dates in quotes, the line below only indicates that there is no default date
date = "" date = ""


# A draft page will not be present in prev/next pagination # A draft page will not be present in prev/next pagination


+ 7
- 5
docs/content/documentation/content/syntax-highlighting.md View File

@@ -32,7 +32,8 @@ Here is a full list of the supported languages and the short names you can use:
- Jinja2 -> ["j2", "jinja2"] - Jinja2 -> ["j2", "jinja2"]
- Julia -> ["jl"] - Julia -> ["jl"]
- Kotlin -> ["kt", "kts"] - Kotlin -> ["kt", "kts"]
- LESS -> ["less"]
- Less -> ["less", "css.less"]
- Nim -> ["nim", "nims"]
- ASP -> ["asa"] - ASP -> ["asa"]
- HTML (ASP) -> ["asp"] - HTML (ASP) -> ["asp"]
- ActionScript -> ["as"] - ActionScript -> ["as"]
@@ -57,12 +58,12 @@ Here is a full list of the supported languages and the short names you can use:
- Java Server Page (JSP) -> ["jsp"] - Java Server Page (JSP) -> ["jsp"]
- Java -> ["java", "bsh"] - Java -> ["java", "bsh"]
- Java Properties -> ["properties"] - Java Properties -> ["properties"]
- JSON -> ["json", "sublime-settings", "sublime-menu", "sublime-keymap", "sublime-mousemap", "sublime-theme", "sublime-build", "sublime-project", "sublime-completions", "sublime-commands", "sublime-macro"]
- JSON -> ["json", "sublime-settings", "sublime-menu", "sublime-keymap", "sublime-mousemap", "sublime-theme", "sublime-build", "sublime-project", "sublime-completions", "sublime-commands", "sublime-macro", "sublime-color-scheme"]
- JavaScript -> ["js", "htc"] - JavaScript -> ["js", "htc"]
- BibTeX -> ["bib"] - BibTeX -> ["bib"]
- LaTeX -> ["tex", "ltx"] - LaTeX -> ["tex", "ltx"]
- TeX -> ["sty", "cls"] - TeX -> ["sty", "cls"]
- Lisp -> ["lisp", "cl", "l", "mud", "el", "scm", "ss", "lsp", "fasl"]
- Lisp -> ["lisp", "cl", "clisp", "l", "mud", "el", "scm", "ss", "lsp", "fasl"]
- Lua -> ["lua"] - Lua -> ["lua"]
- Makefile -> ["make", "GNUmakefile", "makefile", "Makefile", "OCamlMakefile", "mak", "mk"] - Makefile -> ["make", "GNUmakefile", "makefile", "Makefile", "OCamlMakefile", "mak", "mk"]
- Markdown -> ["md", "mdown", "markdown", "markdn"] - Markdown -> ["md", "mdown", "markdown", "markdn"]
@@ -89,13 +90,14 @@ Here is a full list of the supported languages and the short names you can use:
- Rust -> ["rs"] - Rust -> ["rs"]
- SQL -> ["sql", "ddl", "dml"] - SQL -> ["sql", "ddl", "dml"]
- Scala -> ["scala", "sbt"] - Scala -> ["scala", "sbt"]
- Bourne Again Shell (bash) -> ["sh", "bash", "zsh", "fish", ".bash_aliases", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init"]
- Bourne Again Shell (bash) -> ["sh", "bash", "zsh", "fish", ".bash_aliases", ".bash_completions", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init"]
- HTML (Tcl) -> ["adp"] - HTML (Tcl) -> ["adp"]
- Tcl -> ["tcl"] - Tcl -> ["tcl"]
- Textile -> ["textile"] - Textile -> ["textile"]
- XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"] - XML -> ["xml", "xsd", "xslt", "tld", "dtml", "rss", "opml", "svg"]
- YAML -> ["yaml", "yml", "sublime-syntax"] - YAML -> ["yaml", "yml", "sublime-syntax"]
- Generic Config -> ["cfg", "conf", "config", "ini", "pro"]
- SWI-Prolog -> ["pro"]
- Generic Config -> ["cfg", "conf", "config", "ini", "pro", "mak", "mk", "Doxyfile", "inputrc", ".inputrc", "dircolors", ".dircolors", "gitmodules", ".gitmodules", "gitignore", ".gitignore", "gitattributes", ".gitattributes"]
- Linker Script -> ["ld"] - Linker Script -> ["ld"]
- TOML -> ["toml", "tml"] - TOML -> ["toml", "tml"]
- TypeScript -> ["ts"] - TypeScript -> ["ts"]


+ 2
- 2
docs/content/documentation/content/tags-categories.md View File

@@ -15,9 +15,9 @@ are available at the following paths:


```plain ```plain
$BASE_URL/tags/ $BASE_URL/tags/
$BASE_URL/tag/$TAG_SLUG
$BASE_URL/tags/$TAG_SLUG
$BASE_URL/categories/ $BASE_URL/categories/
$BASE_URL/category/$CATEGORY_SLUG
$BASE_URL/categories/$CATEGORY_SLUG
``` ```


It is currently not possible to change those paths or to create custom taxonomies. It is currently not possible to change those paths or to create custom taxonomies.

+ 6
- 0
docs/content/documentation/getting-started/cli-usage.md View File

@@ -36,6 +36,11 @@ $ gutenberg build --base-url $DEPLOY_URL
This is useful for example when you want to deploy previews of a site to a dynamic URL, such as Netlify This is useful for example when you want to deploy previews of a site to a dynamic URL, such as Netlify
deploy previews. deploy previews.


+You can override the default output directory 'public' by passing a other value to the `output-dir` flag.
```bash
$ gutenberg build --output-dir $DOCUMENT_ROOT
```

## serve ## serve


This will build and serve the site using a local server. You can also specify This will build and serve the site using a local server. You can also specify
@@ -46,6 +51,7 @@ $ gutenberg serve
$ gutenberg serve --port 2000 $ gutenberg serve --port 2000
$ gutenberg serve --interface 0.0.0.0 $ gutenberg serve --interface 0.0.0.0
$ gutenberg serve --interface 0.0.0.0 --port 2000 $ gutenberg serve --interface 0.0.0.0 --port 2000
$ gutenberg serve --interface 0.0.0.0 --port 2000 --output-dir www/public
``` ```


The serve command will watch all your content and will provide live reload, without The serve command will watch all your content and will provide live reload, without


+ 5
- 1
docs/content/documentation/getting-started/configuration.md View File

@@ -21,7 +21,8 @@ base_url = "mywebsite.com"
# Used in RSS by default # Used in RSS by default
title = "" title = ""
description = "" description = ""
language_code = "en"
# the default language, used in RSS and coming i18n
default_language = "en"


# Theme name to use # Theme name to use
theme = "" theme = ""
@@ -50,6 +51,9 @@ generate_categories_pages = false
# Whether to compile the Sass files found in the `sass` directory # Whether to compile the Sass files found in the `sass` directory
compile_sass = false compile_sass = false


# Optional translation object. The key if present should be a language code
[translations]

# You can put any kind of data in there and it # You can put any kind of data in there and it
# will be accessible in all templates # will be accessible in all templates
[extra] [extra]


+ 26
- 0
docs/content/documentation/templates/overview.md View File

@@ -70,5 +70,31 @@ we want to link to the file that is located at `static/css/app.css`:
{{ get_url(path="css/app.css") }} {{ get_url(path="css/app.css") }}
``` ```


For assets it is reccommended that you pass `trailing_slash=false` to the `get_url` function. This prevents errors
when dealing with certain hosting providers. An example is:

```jinja2
{{ get_url(path="css/app.css", trailing_slash=false) }}
```

In the case of non-internal links, you can also add a cachebust of the format `?t=1290192` at the end of a URL In the case of non-internal links, you can also add a cachebust of the format `?t=1290192` at the end of a URL
by passing `cachebust=true` to the `get_url` function. by passing `cachebust=true` to the `get_url` function.


### `get_taxonomy_url`
Gets the permalink for the tag or category given.

```jinja2
{% set url = get_taxonomy_url(kind="category", name=page.category) %}
```

The `name` will almost come from a variable but in case you want to do it manually,
the value should be the same as the one in the front-matter, not the slugified version.

### `trans`
Gets the translation of the given `key`, for the `default_language` or the `language given

```jinja2
{{ trans(key="title") }}
{{ trans(key="title", lang="fr") }}
```

+ 5
- 0
docs/content/documentation/themes/creating-a-theme.md View File

@@ -57,3 +57,8 @@ To be featured on this site, the theme will require two more things:
of importance of importance


A simple theme you can use as example is [Hyde](https://github.com/Keats/hyde). A simple theme you can use as example is [Hyde](https://github.com/Keats/hyde).

# Caveat

Please note that [include paths](https://tera.netlify.com/docs/templates/#include) can only be used in used in normal templates. Theme templates should use [macro's](https://tera.netlify.com/docs/templates/#macros) instead.


+ 2
- 2
docs/templates/index.html View File

@@ -7,8 +7,8 @@
<meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}"> <meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}">
<meta name="author" content="{{ config.extra.author }}"> <meta name="author" content="{{ config.extra.author }}">
<title>{% block title %}{{ config.title }}{% endblock title %}</title> <title>{% block title %}{{ config.title }}{% endblock title %}</title>
<link rel="stylesheet" href="{{ get_url(path="site.css") }}"/>
<link rel="icon" href="{{ get_url(path="favicon.ico") }}">
<link rel="stylesheet" href="{{ get_url(path="site.css", trailing_slash=false) }}"/>
<link rel="icon" href="{{ get_url(path="favicon.ico", trailing_slash=false) }}">
</head> </head>
<body> <body>




+ 12
- 0
src/cli.rs View File

@@ -28,6 +28,12 @@ pub fn build_cli() -> App<'static, 'static> {
.long("base-url") .long("base-url")
.takes_value(true) .takes_value(true)
.help("Force the base URL to be that value (default to the one in config.toml)"), .help("Force the base URL to be that value (default to the one in config.toml)"),
Arg::with_name("output_dir")
.short("o")
.long("output-dir")
.default_value("public")
.takes_value(true)
.help("Outputs the generated site in the given path"),
]), ]),
SubCommand::with_name("serve") SubCommand::with_name("serve")
.about("Serve the site. Rebuild and reload on change automatically") .about("Serve the site. Rebuild and reload on change automatically")
@@ -42,6 +48,12 @@ pub fn build_cli() -> App<'static, 'static> {
.long("port") .long("port")
.default_value("1111") .default_value("1111")
.help("Which port to use"), .help("Which port to use"),
Arg::with_name("output_dir")
.short("o")
.long("output-dir")
.default_value("public")
.takes_value(true)
.help("Outputs the generated site in the given path"),
]), ]),
]) ])
} }

+ 2
- 1
src/cmd/build.rs View File

@@ -5,8 +5,9 @@ use site::Site;


use console; use console;


pub fn build(config_file: &str, base_url: Option<&str>) -> Result<()> {
pub fn build(config_file: &str, base_url: Option<&str>, output_dir: &str) -> Result<()> {
let mut site = Site::new(env::current_dir().unwrap(), config_file)?; let mut site = Site::new(env::current_dir().unwrap(), config_file)?;
site.set_output_path(output_dir);
if let Some(b) = base_url { if let Some(b) = base_url {
site.config.base_url = b.to_string(); site.config.base_url = b.to_string();
} }


+ 2
- 3
src/cmd/init.rs View File

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

+ 35
- 8
src/cmd/serve.rs View File

@@ -22,6 +22,7 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


use std::env; use std::env;
use std::fs::remove_dir_all;
use std::path::Path; use std::path::Path;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
@@ -33,6 +34,8 @@ use mount::Mount;
use staticfile::Static; use staticfile::Static;
use notify::{Watcher, RecursiveMode, watcher}; use notify::{Watcher, RecursiveMode, watcher};
use ws::{WebSocket, Sender, Message}; use ws::{WebSocket, Sender, Message};
use ctrlc;

use site::Site; use site::Site;
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};


@@ -45,6 +48,7 @@ enum ChangeKind {
Templates, Templates,
StaticFiles, StaticFiles,
Sass, Sass,
Config,
} }


// Uglified using uglifyjs // Uglified using uglifyjs
@@ -77,8 +81,7 @@ fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &st
} }
} }


pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
let start = Instant::now();
fn create_new_site(interface: &str, port: &str, output_dir: &str, config_file: &str) -> Result<(Site, String)> {
let mut site = Site::new(env::current_dir().unwrap(), config_file)?; let mut site = Site::new(env::current_dir().unwrap(), config_file)?;


let address = format!("{}:{}", interface, port); let address = format!("{}:{}", interface, port);
@@ -88,22 +91,30 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
} else { } else {
format!("http://{}", address) format!("http://{}", address)
}; };
site.set_output_path(output_dir);
site.load()?; site.load()?;
site.enable_live_reload(); site.enable_live_reload();
console::notify_site_size(&site); console::notify_site_size(&site);
console::warn_about_ignored_pages(&site); console::warn_about_ignored_pages(&site);
site.build()?; site.build()?;
Ok((site, address))
}

pub fn serve(interface: &str, port: &str, output_dir: &str, config_file: &str) -> Result<()> {
let start = Instant::now();
let (mut site, address) = create_new_site(interface, port, output_dir, config_file)?;
console::report_elapsed_time(start); console::report_elapsed_time(start);
let mut watching_static = false;


// Setup watchers // Setup watchers
let mut watching_static = false;
let (tx, rx) = channel(); let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap(); let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
watcher.watch("content/", RecursiveMode::Recursive) watcher.watch("content/", RecursiveMode::Recursive)
.chain_err(|| "Can't watch the `content` folder. Does it exist?")?; .chain_err(|| "Can't watch the `content` folder. Does it exist?")?;
watcher.watch("templates/", RecursiveMode::Recursive) watcher.watch("templates/", RecursiveMode::Recursive)
.chain_err(|| "Can't watch the `templates` folder. Does it exist?")?; .chain_err(|| "Can't watch the `templates` folder. Does it exist?")?;
watcher.watch("config.toml", RecursiveMode::Recursive)
.chain_err(|| "Can't watch the `config.toml` file. Does it exist?")?;


if Path::new("static").exists() { if Path::new("static").exists() {
watching_static = true; watching_static = true;
@@ -116,9 +127,9 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {


let ws_address = format!("{}:{}", interface, "1112"); let ws_address = format!("{}:{}", interface, "1112");


// Start a webserver that serves the `public` directory
// Start a webserver that serves the `output_dir` directory
let mut mount = Mount::new(); let mut mount = Mount::new();
mount.mount("/", Static::new(Path::new("public/")));
mount.mount("/", Static::new(Path::new(output_dir)));
mount.mount("/livereload.js", livereload_handler); mount.mount("/livereload.js", livereload_handler);
// Starts with a _ to not trigger the unused lint // Starts with a _ to not trigger the unused lint
// we need to assign to a variable otherwise it will block // we need to assign to a variable otherwise it will block
@@ -147,7 +158,7 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {


let pwd = format!("{}", env::current_dir().unwrap().display()); let pwd = format!("{}", env::current_dir().unwrap().display());


let mut watchers = vec!["content", "templates"];
let mut watchers = vec!["content", "templates", "config.toml"];
if watching_static { if watching_static {
watchers.push("static"); watchers.push("static");
} }
@@ -158,6 +169,12 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
println!("Listening for changes in {}/{{{}}}", pwd, watchers.join(", ")); println!("Listening for changes in {}/{{{}}}", pwd, watchers.join(", "));
println!("Web server is available at http://{}", address); println!("Web server is available at http://{}", address);
println!("Press Ctrl+C to stop\n"); println!("Press Ctrl+C to stop\n");
// Delete the output folder on ctrl+C
let output_path = Path::new(output_dir).to_path_buf();
ctrlc::set_handler(move || {
remove_dir_all(&output_path).expect("Failed to delete output directory");
::std::process::exit(0);
}).expect("Error setting Ctrl-C handler");


use notify::DebouncedEvent::*; use notify::DebouncedEvent::*;


@@ -196,6 +213,10 @@ pub fn serve(interface: &str, port: &str, config_file: &str) -> Result<()> {
console::info(&format!("-> Sass file changed {}", path.display())); console::info(&format!("-> Sass file changed {}", path.display()));
rebuild_done_handling(&broadcaster, site.compile_sass(&site.base_path), &p); rebuild_done_handling(&broadcaster, site.compile_sass(&site.base_path), &p);
}, },
(ChangeKind::Config, _) => {
console::info(&format!("-> Config changed. The whole site will be reloaded. The browser needs to be refreshed to make the changes visible."));
site = create_new_site(interface, port, output_dir, config_file).unwrap().0;
}
}; };
console::report_elapsed_time(start); console::report_elapsed_time(start);
} }
@@ -249,6 +270,8 @@ fn detect_change_kind(pwd: &str, path: &Path) -> (ChangeKind, String) {
ChangeKind::StaticFiles ChangeKind::StaticFiles
} else if path_str.starts_with("/sass") { } else if path_str.starts_with("/sass") {
ChangeKind::Sass ChangeKind::Sass
} else if path_str == "/config.toml" {
ChangeKind::Config
} else { } else {
unreachable!("Got a change in an unexpected path: {}", path_str) unreachable!("Got a change in an unexpected path: {}", path_str)
}; };
@@ -299,7 +322,11 @@ mod tests {
( (
(ChangeKind::Sass, "/sass/print.scss".to_string()), (ChangeKind::Sass, "/sass/print.scss".to_string()),
"/home/vincent/site", Path::new("/home/vincent/site/sass/print.scss") "/home/vincent/site", Path::new("/home/vincent/site/sass/print.scss")
)
),
(
(ChangeKind::Config, "/config.toml".to_string()),
"/home/vincent/site", Path::new("/home/vincent/site/config.toml")
),
]; ];


for (expected, pwd, path) in test_cases { for (expected, pwd, path) in test_cases {


+ 5
- 2
src/main.rs View File

@@ -8,6 +8,7 @@ extern crate mount;
extern crate notify; extern crate notify;
extern crate url; extern crate url;
extern crate ws; extern crate ws;
extern crate ctrlc;


extern crate site; extern crate site;
#[macro_use] #[macro_use]
@@ -43,7 +44,8 @@ fn main() {
("build", Some(matches)) => { ("build", Some(matches)) => {
console::info("Building site..."); console::info("Building site...");
let start = Instant::now(); let start = Instant::now();
match cmd::build(config_file, matches.value_of("base_url")) {
let output_dir = matches.value_of("output_dir").unwrap();
match cmd::build(config_file, matches.value_of("base_url"), output_dir) {
Ok(()) => console::report_elapsed_time(start), Ok(()) => console::report_elapsed_time(start),
Err(e) => { Err(e) => {
console::unravel_errors("Failed to build the site", &e); console::unravel_errors("Failed to build the site", &e);
@@ -54,8 +56,9 @@ fn main() {
("serve", Some(matches)) => { ("serve", Some(matches)) => {
let interface = matches.value_of("interface").unwrap_or("127.0.0.1"); let interface = matches.value_of("interface").unwrap_or("127.0.0.1");
let port = matches.value_of("port").unwrap_or("1111"); let port = matches.value_of("port").unwrap_or("1111");
let output_dir = matches.value_of("output_dir").unwrap();
console::info("Building site..."); console::info("Building site...");
match cmd::serve(interface, port, config_file) {
match cmd::serve(interface, port, output_dir, config_file) {
Ok(()) => (), Ok(()) => (),
Err(e) => { Err(e) => {
console::unravel_errors("", &e); console::unravel_errors("", &e);


+ 11
- 2
src/rebuild.rs View File

@@ -1,4 +1,4 @@
use std::path::Path;
use std::path::{Path, Component};


use errors::Result; use errors::Result;
use site::Site; use site::Site;
@@ -231,8 +231,9 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
/// What happens when a template is changed /// What happens when a template is changed
pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> { pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
site.tera.full_reload()?; site.tera.full_reload()?;
let filename = path.file_name().unwrap().to_str().unwrap();


match path.file_name().unwrap().to_str().unwrap() {
match filename {
"sitemap.xml" => site.render_sitemap(), "sitemap.xml" => site.render_sitemap(),
"rss.xml" => site.render_rss_feed(), "rss.xml" => site.render_rss_feed(),
"robots.txt" => site.render_robots(), "robots.txt" => site.render_robots(),
@@ -247,6 +248,14 @@ pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
// We can't really know what this change affects so rebuild all // We can't really know what this change affects so rebuild all
// the things // the things
_ => { _ => {
// If we are updating a shortcode, re-render the markdown of all pages/site
// because we have no clue which one needs rebuilding
// TODO: look if there the shortcode is used in the markdown instead of re-rendering
// everything
if path.components().collect::<Vec<_>>().contains(&Component::Normal("shortcodes".as_ref())) {
site.render_markdown()?;
}
site.populate_sections();
site.render_sections()?; site.render_sections()?;
site.render_orphan_pages()?; site.render_orphan_pages()?;
site.render_categories()?; site.render_categories()?;


+ 12
- 16
sublime_syntaxes/Elixir.sublime-syntax View File

@@ -25,7 +25,7 @@ contexts:
captures: captures:
1: keyword.operator.other.elixir 1: keyword.operator.other.elixir
2: keyword.control.elixir 2: keyword.control.elixir
3: punctuation.definition.parameters.elixir
3: punctuation.section.function.elixir
pop: true pop: true
- include: core_syntax - include: core_syntax
- include: core_syntax - include: core_syntax
@@ -37,7 +37,7 @@ contexts:
captures: captures:
1: keyword.operator.other.elixir 1: keyword.operator.other.elixir
2: keyword.control.elixir 2: keyword.control.elixir
3: punctuation.definition.parameters.elixir
3: punctuation.section.function.elixir
pop: true pop: true
- include: core_syntax - include: core_syntax
core_syntax: core_syntax:
@@ -78,7 +78,7 @@ contexts:
captures: captures:
1: keyword.control.module.elixir 1: keyword.control.module.elixir
2: entity.name.function.public.elixir 2: entity.name.function.public.elixir
4: punctuation.definition.parameters.elixir
4: punctuation.section.function.elixir
push: push:
- meta_scope: meta.function.public.elixir - meta_scope: meta.function.public.elixir
- match: (\bdo:)|(\bdo\b)|(?=\s+(def|defmacro)\b) - match: (\bdo:)|(\bdo\b)|(?=\s+(def|defmacro)\b)
@@ -100,7 +100,7 @@ contexts:
captures: captures:
1: keyword.control.module.elixir 1: keyword.control.module.elixir
2: entity.name.function.private.elixir 2: entity.name.function.private.elixir
4: punctuation.definition.parameters.elixir
4: punctuation.section.function.elixir
push: push:
- meta_scope: meta.function.private.elixir - meta_scope: meta.function.private.elixir
- match: (\bdo:)|(\bdo\b)|(?=\s+(defp|defmacrop)\b) - match: (\bdo:)|(\bdo\b)|(?=\s+(defp|defmacrop)\b)
@@ -157,18 +157,6 @@ contexts:
pop: true pop: true
- include: interpolated_elixir - include: interpolated_elixir
- include: escaped_char - include: escaped_char
- match: (::)
captures:
0: punctuation.binary.elixir
push:
- match: (,|>>|$)
captures:
0: punctuation.binary.elixir
pop: true
- match: '\b[a-z]\w*\b'
scope: support.type.binary.elixir
- match: '\b(0x[0-9A-Fa-f](?>_?[0-9A-Fa-f])*|\d(?>_?\d)*(\.(?![^[:space:][:digit:]])(?>_?\d)*)?([eE][-+]?\d(?>_?\d)*)?|0b[01]+|0o[0-7]+)\b'
scope: constant.numeric.elixir
- match: '(?<!\.)\b(do|end|case|bc|lc|for|if|cond|unless|try|receive|fn|defmodule|defp?|defprotocol|defimpl|defrecord|defstruct|defmacrop?|defdelegate|defcallback|defmacrocallback|defexception|defoverridable|exit|after|rescue|catch|else|raise|throw|import|require|alias|use|quote|unquote|super|with)\b(?![?!:])' - match: '(?<!\.)\b(do|end|case|bc|lc|for|if|cond|unless|try|receive|fn|defmodule|defp?|defprotocol|defimpl|defrecord|defstruct|defmacrop?|defdelegate|defcallback|defmacrocallback|defexception|defoverridable|exit|after|rescue|catch|else|raise|throw|import|require|alias|use|quote|unquote|super|with)\b(?![?!:])'
scope: keyword.control.elixir scope: keyword.control.elixir
- match: (?<!\.)\b(and|not|or|when|xor|in)\b - match: (?<!\.)\b(and|not|or|when|xor|in)\b
@@ -433,6 +421,14 @@ contexts:
the negative lookbehind prevents against matching the negative lookbehind prevents against matching
p(42.tainted?) p(42.tainted?)
scope: constant.numeric.elixir scope: constant.numeric.elixir
- match: \+\+|\-\-|<\|>
scope: keyword.operator.concatenation.elixir
- match: \|\>|<~>|<>|<<<|>>>|~>>|<<~|~>|<~|<\|>
scope: keyword.operator.sigils_1.elixir
- match: "&&&|&&"
scope: keyword.operator.sigils_2.elixir
- match: <\-|\\\\
scope: keyword.operator.sigils_3.elixir
- match: "===?|!==?|<=?|>=?" - match: "===?|!==?|<=?|>=?"
scope: keyword.operator.comparison.elixir scope: keyword.operator.comparison.elixir
- match: (\|\|\||&&&|^^^|<<<|>>>|~~~) - match: (\|\|\||&&&|^^^|<<<|>>>|~~~)


+ 0
- 200
sublime_syntaxes/Elm.sublime-syntax View File

@@ -1,200 +0,0 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: Elm
file_extensions:
- elm
scope: source.elm
contexts:
main:
- match: "(`)[a-zA-Z_']*?(`)"
scope: keyword.operator.function.infix.elm
captures:
1: punctuation.definition.entity.elm
2: punctuation.definition.entity.elm
- match: \(\)
scope: constant.language.unit.elm
- match: ^\b((effect|port)\s+)?(module)\s+
captures:
1: keyword.other.elm
3: keyword.other.elm
push:
- meta_scope: meta.declaration.module.elm
- match: $|;
captures:
1: keyword.other.elm
pop: true
- include: module_name
- match: '(where)\s*\{'
captures:
1: keyword.other.elm
push:
- match: '\}'
pop: true
- include: type_signature
- match: (exposing)
scope: keyword.other.elm
- include: module_exports
- match: (where)
scope: keyword.other.elm
- match: "[a-z]+"
scope: invalid
- match: ^\b(import)\s+((open)\s+)?
captures:
1: keyword.other.elm
3: invalid
push:
- meta_scope: meta.import.elm
- match: ($|;)
pop: true
- match: (as|exposing)
scope: keyword.import.elm
- include: module_name
- include: module_exports
- match: '(\[)(glsl)(\|)'
captures:
1: keyword.other.elm
2: support.function.prelude.elm
3: keyword.other.elm
push:
- meta_scope: entity.glsl.elm
- match: '(\|\])'
captures:
1: keyword.other.elm
pop: true
- include: scope:source.glsl
- match: \b(type alias|type|case|of|let|in|as)\s+
scope: keyword.other.elm
- match: \b(if|then|else)\s+
scope: keyword.control.elm
- match: '\b([0-9]+\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)\b'
comment: Floats are always decimal
scope: constant.numeric.float.elm
- match: '\b([0-9]+)\b'
scope: constant.numeric.elm
- match: '"""'
captures:
0: punctuation.definition.string.begin.elm
push:
- meta_scope: string.quoted.double.elm
- match: '"""'
captures:
0: punctuation.definition.string.end.elm
pop: true
- match: '\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\''\&])'
scope: constant.character.escape.elm
- match: '\^[A-Z@\[\]\\\^_]'
scope: constant.character.escape.control.elm
- match: '"'
captures:
0: punctuation.definition.string.begin.elm
push:
- meta_scope: string.quoted.double.elm
- match: '"'
captures:
0: punctuation.definition.string.end.elm
pop: true
- match: '\\(NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL|[abfnrtv\\\"''\&])'
scope: constant.character.escape.elm
- match: '\^[A-Z@\[\]\\\^_]'
scope: constant.character.escape.control.elm
- match: |-
(?x)
(')
(?:
[\ -\[\]-~] # Basic Char
| (\\(?:NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE
|DC1|DC2|DC3|DC4|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS
|US|SP|DEL|[abfnrtv\\\"'\&])) # Escapes
| (\^[A-Z@\[\]\\\^_]) # Control Chars
)
(')
scope: string.quoted.single.elm
captures:
1: punctuation.definition.string.begin.elm
2: constant.character.escape.elm
3: punctuation.definition.string.end.elm
- match: '^(port\s+)?([a-z_][a-zA-Z0-9_'']*|\([|!%$+\-.,=</>]+\))\s*((:)([:]+)?)'
captures:
1: keyword.other.port.elm
2: entity.name.function.elm
4: keyword.other.colon.elm
5: invalid
push:
- meta_scope: meta.function.type-declaration.elm
- match: $\n?
pop: true
- include: type_signature
- match: \bport\s+
scope: keyword.other.port.elm
- match: '\b[A-Z]\w*\b'
scope: constant.other.elm
- include: comments
- match: '^[a-z][A-Za-z0-9_'']*\s+'
scope: entity.name.function.elm
- include: infix_op
- match: '[|!%$?~+:\-.=</>&\\*^]+'
scope: keyword.operator.elm
- match: '([\[\]\{\},])'
scope: constant.language.delimiter.elm
captures:
1: support.function.delimiter.elm
- match: '([\(\)])'
scope: keyword.other.parenthesis.elm
block_comment:
- match: '\{-(?!#)'
captures:
0: punctuation.definition.comment.elm
push:
- meta_scope: comment.block.elm
- include: block_comment
- match: '-\}'
captures:
0: punctuation.definition.comment.elm
pop: true
comments:
- match: (--).*$\n?
scope: comment.line.double-dash.elm
captures:
1: punctuation.definition.comment.elm
- include: block_comment
infix_op:
- match: '(\([|!%$+:\-.=</>]+\)|\(,+\))'
scope: entity.name.function.infix.elm
module_exports:
- match: \(
push:
- meta_scope: meta.declaration.exports.elm
- match: \)
pop: true
- match: '\b[a-z][a-zA-Z_''0-9]*'
scope: entity.name.function.elm
- match: '\b[A-Z][A-Za-z_''0-9]*'
scope: storage.type.elm
- match: ","
scope: punctuation.separator.comma.elm
- include: infix_op
- match: \(.*?\)
comment: So named because I don't know what to call this.
scope: meta.other.unknown.elm
module_name:
- match: "[A-Z][A-Za-z._']*"
scope: support.other.module.elm
type_signature:
- match: '\(\s*([A-Z][A-Za-z]*)\s+([a-z][A-Za-z_'']*)\)\s*(=>)'
scope: meta.class-constraint.elm
captures:
1: entity.other.inherited-class.elm
2: variable.other.generic-type.elm
3: keyword.other.big-arrow.elm
- match: "->"
scope: keyword.other.arrow.elm
- match: "=>"
scope: keyword.other.big-arrow.elm
- match: '\b[a-z][a-zA-Z0-9_'']*\b'
scope: variable.other.generic-type.elm
- match: '\b[A-Z][a-zA-Z0-9_'']*\b'
scope: storage.type.elm
- match: \(\)
scope: support.constant.unit.elm
- include: comments

+ 1
- 1
sublime_syntaxes/Elm.tmLanguage

@@ -1 +1 @@
Subproject commit 581b9e6f5bfdf55e506ac5a2b18422296c375ca2
Subproject commit 6f2e53603a62663463f95079b308a1c9adbabeec

+ 1
- 1
sublime_syntaxes/Julia-sublime

@@ -1 +1 @@
Subproject commit 3cf94d55b2eafd41461c45570d982eef1d65778c
Subproject commit 581805e47c7af5ab0a880aaef5b27f8c1ccc29aa

+ 1
- 1
sublime_syntaxes/LESS-sublime

@@ -1 +1 @@
Subproject commit ebd4a2f049fb61686664a68c8d0f34d83292e07e
Subproject commit df5a27523dd37ebe67ba4c7d36ea162dae95b2c3

+ 0
- 326
sublime_syntaxes/LESS.sublime-syntax View File

@@ -1,326 +0,0 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: LESS
comment: LESS
file_extensions:
- less
scope: source.less
contexts:
main:
- include: comment-block
- include: comment-line
- include: less-at-rules
- include: less-declarations
- include: less-variables
- include: less-functions
- include: string-double
- include: string-single
- include: selector
- include: rule-list
- include: less-operators
- include: less-parameters
- include: numeric-values
color-values:
- match: \b(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)\b
comment: http://www.w3.org/TR/CSS21/syndata.html#value-def-color
scope: support.constant.color.w3c-standard-color-name.css
- match: \b(aliceblue|antiquewhite|aquamarine|azure|beige|bisque|blanchedalmond|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|gainsboro|ghostwhite|gold|goldenrod|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|limegreen|linen|magenta|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|oldlace|olivedrab|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|thistle|tomato|turquoise|violet|wheat|whitesmoke|yellowgreen)\b
comment: "These colours are mostly recognised but will not validate. ref: http://www.w3schools.com/css/css_colornames.asp"
scope: support.constant.color.non-standard
- match: (hsla?|rgba?)\s*(\()
captures:
1: support.function.misc.css
2: punctuation.section.function.css
push:
- match: (\))
captures:
1: punctuation.section.function.css
pop: true
- match: '(?x)\b(0*((1?[0-9]{1,2})|(2([0-4][0-9]|5[0-5])))\s*,\s*){2}(0*((1?[0-9]{1,2})|(2([0-4][0-9]|5[0-5])))\b)(\s*,\s*((0?\.[0-9]+)|[0-1]))?'
scope: constant.other.color.rgb-value.css
- match: '\b([0-9]{1,2}|100)\s*%,\s*([0-9]{1,2}|100)\s*%,\s*([0-9]{1,2}|100)\s*%'
scope: constant.other.color.rgb-percentage.css
- include: numeric-values
comment-block:
- match: /\*
captures:
0: punctuation.definition.comment.css
push:
- meta_scope: comment.block.css
- match: \*/
captures:
0: punctuation.definition.comment.css
pop: true
comment-line:
- match: //
captures:
0: punctuation.definition.comment.css
push:
- meta_scope: comment.line.double-slash.less
- match: $\n?
captures:
0: punctuation.definition.comment.css
pop: true
less-at-rules:
- match: ^\s*((@)(?:-(?:webkit|moz|o)-)?(charset|import|namespace|media|page|font-face|keyframes|supports|document)\b)
scope: meta.at-rule.css
captures:
1: keyword.control.at-rule.css
2: punctuation.definition.keyword.css
less-data-uri:
- match: (url)(\()(data:)
captures:
1: support.function.misc.css
2: punctuation.section.function.css
3: parameter.less.data-uri comment markup.raw
push:
- meta_content_scope: parameter.less.data-uri comment markup.raw
- match: (\))
captures:
1: punctuation.section.function.css
pop: true
- match: (url)(\()("data:)
captures:
1: support.function.misc.css
2: punctuation.section.function.css
3: parameter.less.data-uri comment markup.raw
push:
- meta_content_scope: parameter.less.data-uri comment markup.raw
- match: (?<=")(\))
captures:
1: punctuation.section.function.css
pop: true
- match: (url)(\()('data:)
captures:
1: support.function.misc.css
2: punctuation.section.function.css
3: parameter.less.data-uri comment markup.raw
push:
- meta_content_scope: parameter.less.data-uri comment markup.raw
- match: (?<=\')(\))
captures:
1: punctuation.section.function.css
pop: true
less-declarations:
- match: '(?>@[a-zA-Z0-9_-][\w-]*+)(?!:)'
scope: variable.other.less
- match: '@[a-zA-Z0-9_-][\w-]*'
scope: variable.declaration.less
less-functions:
- match: '([%a-zA-Z\-\_\d]*)(?=\()'
scope: support.function.less
- match: '([\.#](?![0-9])[a-zA-Z0-9_-]+(?=\())'
captures:
1: entity.other.attribute-name.class.css entity.other.less.mixin
less-operators:
- match: /|!important|$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\|\||\?\:|(?<!\()/=|%=|\+=|\-=|&=|when\b|and\b|not\b
scope: keyword.operator.less
less-parameters:
- match: '\(|(}\s*,)'
push:
- meta_scope: parameter.less
- match: '\)|{'
pop: true
- include: color-values
- include: less-parameters
- include: less-functions
- include: numeric-values
- include: string-double
- include: string-single
- include: less-variables
- match: '(?:\:/)?[\/\.a-zA-Z0-9_\-]*'
scope: variable.parameter.misc.css
- include: less-operators
less-variables:
- match: '@[a-zA-Z0-9_-][\w-]*'
scope: variable.other.less
- match: '@{[a-zA-Z0-9_-][\w-]*}'
scope: variable.interpolation.less
- match: "`"
push:
- meta_scope: comment markup.raw
- match: "`"
pop: true
- match: (~)`
captures:
1: keyword.operator.less
push:
- meta_scope: comment markup.raw
- match: "`"
pop: true
- match: (~)"
captures:
1: keyword.operator.less
push:
- meta_scope: string.quoted.double.css comment markup.raw
- match: '"'
pop: true
- match: (~)'
captures:
1: keyword.operator.less
push:
- meta_scope: string.quoted.single.css comment markup.raw
- match: "'"
pop: true
numeric-values:
- match: '(#)([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b'
scope: constant.other.color.rgb-value.css
captures:
1: punctuation.definition.constant.css
- match: '(?x)(?:-|\+)?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+))((?:px|pt|ch|cm|mm|in|r?em|ex|pc|deg|g?rad|dpi|dpcm|ms|s)\b|%)?'
scope: constant.numeric.css
captures:
1: keyword.other.unit.css
property-names:
- match: "(?<![-a-z])(?=[-a-z])"
push:
- meta_scope: support.type.property-name.css
- match: "$|(?![-a-z])"
pop: true
- match: \b(word)\b
scope: support.type.property-name.css
property-values:
- include: less-variables
- include: less-data-uri
- include: less-functions
- include: less-operators
- include: color-values
- include: numeric-values
- include: less-parameters
- match: \b(absolute|all(-scroll)?|always|subpixel-antialiased|antialiased|armenian|auto|avoid|baseline|below|bidi-override|block|bold|bolder|border-box|both|bottom|break-all|break-word|capitalize|center|char|circle|cjk-ideographic|content-box|col-resize|collapse|crosshair|dashed|decimal-leading-zero|decimal|default|disabled|disc|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ellipsis|fixed|geometricPrecision|georgian|groove|hand|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|inactive|inherit|inline-block|inline|inset|inside|inter-ideograph|inter-word|italic|justify|katakana-iroha|katakana|keep-all|left|lighter|line-edge|line-through|line|list-item|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|medium|middle|move|n-resize|ne-resize|newspaper|no-drop|no-repeat|nw-resize|none|normal|not-allowed|nowrap|oblique|optimize(Legibility|Quality|Speed)|outset|outside|overline|pointer|pre(-(wrap|line))?|progress|relative|repeat-x|repeat-y|repeat|right|ridge|row-resize|rtl|s-resize|scroll|se-resize|separate|small-caps|solid|square|static|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table|tb-rl|text-bottom|text-top|textfield|text|thick|thin|top|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|vertical(-(ideographic|text))?|visible(Painted|Fill|Stroke)?|w-resize|wait|whitespace|zero|smaller|larger|((xx?-)?(small|large))|painted|fill|stroke)\b
scope: support.constant.property-value.css
- include: selector
- match: (\b(?i:arial|century|comic|courier|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace)\b)
scope: support.constant.font-name.css
- include: string-double
- include: string-single
- match: (rect)\s*(\()
captures:
1: support.function.misc.css
2: punctuation.section.function.css
push:
- match: (\))
captures:
1: punctuation.section.function.css
pop: true
- include: numeric-values
- match: (format|local|url|attr|counter|counters)\s*(\()
captures:
1: support.function.misc.css
2: punctuation.section.function.css
push:
- match: (\))
captures:
1: punctuation.section.function.css
pop: true
- include: string-single
- include: string-double
- match: '[^''") \t]+'
scope: variable.parameter.misc.css
- match: \!\s*important
scope: keyword.other.important.css
rule-list:
- include: comment-block
- include: comment-line
- include: selector
- include: property-names
- match: (:)\s*
captures:
1: punctuation.separator.key-value.css
push:
- meta_scope: meta.property-value.css
- match: '\s*(;|(?=[{}]))'
captures:
1: punctuation.terminator.rule.css
pop: true
- include: property-values
selector:
- match: '\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|eventsource|fieldset|figure|figcaption|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|samp|script|section|select|small|span|strike|strong|style|sub|summary|sup|svg|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\b'
scope: entity.name.tag.css, keyword.control.html.elements
- match: '(\.)-?[a-zA-Z_][a-zA-Z0-9_-]*'
scope: entity.other.attribute-name.class.css
captures:
1: punctuation.definition.entity.css
- match: "(#)-?[a-zA-Z_][a-zA-Z0-9_-]*"
scope: entity.other.attribute-name.id.css
captures:
1: punctuation.definition.entity.css
- match: \*
scope: entity.name.tag.wildcard.css
- match: (:+)((first|last|only)-child|(first|last|only)-of-type|empty|root|target|first-letter|first-line|first|left|right|lang)\b
scope: entity.other.attribute-name.pseudo-class.css
captures:
1: punctuation.definition.entity.css
- match: (:)(extend)\b
scope: entity.other.attribute-name.pseudo-class.less
captures:
1: punctuation.definition.entity.css
- match: (:)(checked|enabled|default|disabled|indeterminate|invalid|optional|required|valid)\b
scope: entity.other.attribute-name.pseudo-class.ui-state.css
captures:
1: punctuation.definition.entity.css
- match: ((:)not)(\()
captures:
1: entity.other.attribute-name.pseudo-class.css
2: punctuation.definition.entity.css
3: punctuation.section.function.css
push:
- match: \)
captures:
0: punctuation.section.function.css
pop: true
- include: selector
- match: ((:)nth-(?:(?:last-)?child|(?:last-)?of-type))(\()
captures:
1: entity.other.attribute-name.pseudo-class.css
2: punctuation.definition.entity.css
3: punctuation.section.function.css
push:
- meta_content_scope: constant.numeric.css
- match: (\))
captures:
1: punctuation.section.function.css
pop: true
- match: (:+)(?:-(?:webkit|moz|o)-)?(after|before|selection|scrollbar|placeholder|input-placeholder)\b
scope: entity.other.attribute-name.pseudo-element.css
captures:
1: punctuation.definition.entity.css
- match: (:+)(?:-(?:webkit|moz|o)-)?(active|hover|link|visited|focus-inner|focus)\b
scope: entity.other.attribute-name.pseudo-class.css
captures:
1: punctuation.definition.entity.css
- match: '(?i)(\[)\s*(-?[_a-z\\[[:^ascii:]]][_a-z0-9\-\\[[:^ascii:]]]*)(?:\s*([~|^$*]?=)\s*(?:(-?[_a-z\\[[:^ascii:]]][_a-z0-9\-\\[[:^ascii:]]]*)|((?>([''"])(?:[^\\]|\\.)*?(\6)))))?\s*(\])'
scope: meta.attribute-selector.css
captures:
1: punctuation.definition.entity.css
2: entity.other.attribute-name.attribute.css
3: punctuation.separator.operator.css
4: string.unquoted.attribute-value.css
5: string.quoted.double.attribute-value.css
6: punctuation.definition.string.begin.css
7: punctuation.definition.string.end.css
string-double:
- match: '"'
captures:
0: punctuation.definition.string.begin.css
push:
- meta_scope: string.quoted.double.css
- match: '"'
captures:
0: punctuation.definition.string.end.css
pop: true
- match: \\.
scope: constant.character.escape.css
string-single:
- match: "'"
captures:
0: punctuation.definition.string.begin.css
push:
- meta_scope: string.quoted.single.css
- match: "'"
captures:
0: punctuation.definition.string.end.css
pop: true
- match: \\.
scope: constant.character.escape.css

+ 1
- 1
sublime_syntaxes/Packages

@@ -1 +1 @@
Subproject commit 928a7a618d99631ea424c45e74fb01d1fb6f6853
Subproject commit ce7af4d6177d0340230893e49742070ae4143246

+ 135
- 0
sublime_syntaxes/Prolog.sublime-syntax View File

@@ -0,0 +1,135 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: SWI-Prolog
comment: This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
file_extensions:
- pro
scope: source.prolog
contexts:
main:
- include: comments
- match: (?<=:-)\s*
push:
- meta_scope: meta.clause.body.prolog
- match: (\.)
captures:
1: keyword.control.clause.bodyend.prolog
pop: true
- include: comments
- include: builtin
- include: controlandkeywords
- include: atom
- include: variable
- include: constants
- match: .
scope: meta.clause.body.prolog
- match: '^\s*([a-z][a-zA-Z0-9_]*)(\(?)(?=.*:-.*)'
captures:
1: entity.name.function.clause.prolog
2: punctuation.definition.parameters.begin
push:
- meta_scope: meta.clause.head.prolog
- match: ((\)?))\s*(:-)
captures:
1: punctuation.definition.parameters.end
3: keyword.control.clause.bodybegin.prolog
pop: true
- include: atom
- include: variable
- include: constants
- match: '^\s*([a-z][a-zA-Z0-9_]*)(\(?)(?=.*-->.*)'
captures:
1: entity.name.function.dcg.prolog
2: punctuation.definition.parameters.begin
push:
- meta_scope: meta.dcg.head.prolog
- match: ((\)?))\s*(-->)
captures:
1: punctuation.definition.parameters.end
3: keyword.control.dcg.bodybegin.prolog
pop: true
- include: atom
- include: variable
- include: constants
- match: (?<=-->)\s*
push:
- meta_scope: meta.dcg.body.prolog
- match: (\.)
captures:
1: keyword.control.dcg.bodyend.prolog
pop: true
- include: comments
- include: controlandkeywords
- include: atom
- include: variable
- include: constants
- match: .
scope: meta.dcg.body.prolog
- match: '^\s*([a-zA-Z][a-zA-Z0-9_]*)(\(?)(?!.*(:-|-->).*)'
captures:
1: entity.name.function.fact.prolog
2: punctuation.definition.parameters.begin
push:
- meta_scope: meta.fact.prolog
- match: ((\)?))\s*(\.)(?!\d+)
captures:
1: punctuation.definition.parameters.end
3: keyword.control.fact.end.prolog
pop: true
- include: atom
- include: variable
- include: constants
atom:
- match: '(?<![a-zA-Z0-9_])[a-z][a-zA-Z0-9_]*(?!\s*\(|[a-zA-Z0-9_])'
scope: constant.other.atom.simple.prolog
- match: "'.*?'"
scope: constant.other.atom.quoted.prolog
- match: '\[\]'
scope: constant.other.atom.emptylist.prolog
builtin:
- match: \b(op|findall|write|nl|writeln|fail|use_module|module)\b
scope: keyword.other
comments:
- match: "%.*"
scope: comment.line.percent-sign.prolog
- match: /\*
captures:
0: punctuation.definition.comment.prolog
push:
- meta_scope: comment.block.prolog
- match: \*/
captures:
0: punctuation.definition.comment.prolog
pop: true
constants:
- match: '(?<![a-zA-Z]|/)(\d+|(\d+\.\d+))'
scope: constant.numeric.integer.prolog
- match: '".*?"'
scope: string.quoted.double.prolog
controlandkeywords:
- match: (->)
captures:
1: keyword.control.if.prolog
push:
- meta_scope: meta.if.prolog
- match: (;)
captures:
1: keyword.control.else.prolog
pop: true
- include: main
- include: builtin
- include: comments
- include: atom
- include: variable
- match: .
scope: meta.if.body.prolog
- match: "!"
scope: keyword.control.cut.prolog
- match: (\s(is)\s)|=:=|=?\\?=|\\\+|@?>|@?=?<|\+|\*|\-
scope: keyword.operator.prolog
variable:
- match: "(?<![a-zA-Z0-9_])[A-Z][a-zA-Z0-9_]*"
scope: variable.parameter.uppercase.prolog
- match: (?<!\w)_
scope: variable.language.anonymous.prolog

+ 1
- 1
sublime_syntaxes/Sublime-GenericConfig

@@ -1 +1 @@
Subproject commit 5f12fdaae34303ab2550394a14599ad0885b26ec
Subproject commit 04ec6d7165e971db1fefd604942b04ccf86fb920

+ 0
- 1
sublime_syntaxes/TypeScript-Sublime-Plugin

@@ -1 +0,0 @@
Subproject commit 79bf8ddfb8a05a2b104f3937cd91b6f2afbbb943

+ 1
- 0
sublime_syntaxes/TypeScript-TmLanguage

@@ -0,0 +1 @@
Subproject commit c29d12d8aceb1a68af4cb6e466199846f41dd2ed

+ 942
- 716
sublime_syntaxes/TypeScript.sublime-syntax
File diff suppressed because it is too large
View File


+ 956
- 731
sublime_syntaxes/TypeScriptReact.sublime-syntax
File diff suppressed because it is too large
View File


BIN
sublime_syntaxes/newlines.packdump View File


BIN
sublime_syntaxes/nonewlines.packdump View File


Loading…
Cancel
Save