Merge pull request #976 from getzola/next

0.11.0
This commit is contained in:
Vincent Prouillet 2020-05-25 18:14:42 +02:00 committed by GitHub
commit a3a5b9ea06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
177 changed files with 10955 additions and 6235 deletions

98
.gitmodules vendored
View file

@ -1,39 +1,63 @@
[submodule "sublime_syntaxes/Packages"]
path = sublime_syntaxes/Packages
[submodule "sublime/syntaxes/Packages"]
path = sublime/syntaxes/Packages
url = https://github.com/sublimehq/Packages.git
[submodule "sublime_syntaxes/sublimeassembly"]
path = sublime_syntaxes/sublimeassembly
url = https://github.com/Nessphoro/sublimeassembly.git
[submodule "sublime_syntaxes/LESS-sublime"]
path = sublime_syntaxes/LESS-sublime
url = https://github.com/danro/LESS-sublime.git
[submodule "sublime_syntaxes/Handlebars"]
path = sublime_syntaxes/Handlebars
url = https://github.com/daaain/Handlebars.git
[submodule "sublime_syntaxes/Julia-sublime"]
path = sublime_syntaxes/Julia-sublime
url = https://github.com/JuliaEditorSupport/Julia-sublime.git
[submodule "sublime_syntaxes/sublime_toml_highlighting"]
path = sublime_syntaxes/sublime_toml_highlighting
url = https://github.com/Jayflux/sublime_toml_highlighting.git
[submodule "sublime_syntaxes/SublimeTextLinkerSyntax"]
path = sublime_syntaxes/SublimeTextLinkerSyntax
url = https://github.com/jbw3/SublimeTextLinkerSyntax.git
[submodule "sublime_syntaxes/Sublime-GenericConfig"]
path = sublime_syntaxes/Sublime-GenericConfig
url = https://github.com/skozlovf/Sublime-GenericConfig.git
[submodule "sublime_syntaxes/Sublime-VimL"]
path = sublime_syntaxes/Sublime-VimL
[submodule "sublime/syntaxes/awk-sublime"]
path = sublime/syntaxes/awk-sublime
url = https://github.com/JohnNilsson/awk-sublime.git
[submodule "sublime/syntaxes/AsciiDoc"]
path = sublime/syntaxes/AsciiDoc
url = https://github.com/SublimeText/AsciiDoc.git
[submodule "sublime/syntaxes/Sublime-CMakeLists"]
path = sublime/syntaxes/Sublime-CMakeLists
url = https://github.com/zyxar/Sublime-CMakeLists.git
[submodule "sublime/syntaxes/SublimeTextLinkerSyntax"]
path = sublime/syntaxes/SublimeTextLinkerSyntax
url = https://github.com/jbw3/SublimeTextLinkerSyntax
[submodule "sublime/syntaxes/Docker.tmbundle"]
path = sublime/syntaxes/Docker.tmbundle
url = https://github.com/asbjornenge/Docker.tmbundle.git
[submodule "sublime/syntaxes/Sublime-VimL"]
path = sublime/syntaxes/Sublime-VimL
url = https://github.com/SalGnt/Sublime-VimL.git
[submodule "sublime_syntaxes/TypeScript-TmLanguage"]
path = sublime_syntaxes/TypeScript-TmLanguage
url = https://github.com/Microsoft/TypeScript-TmLanguage
[submodule "sublime_syntaxes/SublimeElmLanguageSupport"]
path = sublime_syntaxes/SublimeElmLanguageSupport
url = https://github.com/elm-community/SublimeElmLanguageSupport
[submodule "sublime_syntaxes/Sublime-CMakeLists"]
path = sublime_syntaxes/Sublime-CMakeLists
url = https://github.com/zyxar/Sublime-CMakeLists
[submodule "sublime_syntaxes/Swift-for-f-ing-sublime"]
path = sublime_syntaxes/Swift-for-f-ing-sublime
url = https://github.com/colinta/Swift-for-f-ing-sublime.git
[submodule "sublime/syntaxes/elixir-sublime-syntax"]
path = sublime/syntaxes/elixir-sublime-syntax
url = https://github.com/princemaple/elixir-sublime-syntax.git
[submodule "sublime/syntaxes/SublimeElmLanguageSupport"]
path = sublime/syntaxes/SublimeElmLanguageSupport
url = https://github.com/elm-community/SublimeElmLanguageSupport.git
[submodule "sublime/syntaxes/sublimetext-fsharp"]
path = sublime/syntaxes/sublimetext-fsharp
url = https://github.com/hoest/sublimetext-fsharp.git
[submodule "sublime/syntaxes/sublime-fish"]
path = sublime/syntaxes/sublime-fish
url = https://github.com/Phidica/sublime-fish.git
[submodule "sublime/syntaxes/SublimeFortran"]
path = sublime/syntaxes/SublimeFortran
url = https://github.com/315234/SublimeFortran.git
[submodule "sublime/syntaxes/GraphQL-SublimeText3"]
path = sublime/syntaxes/GraphQL-SublimeText3
url = https://github.com/dncrews/GraphQL-SublimeText3.git
[submodule "sublime/syntaxes/Sublime-GenericConfig"]
path = sublime/syntaxes/Sublime-GenericConfig
url = https://github.com/skozlovf/Sublime-GenericConfig.git
[submodule "sublime/syntaxes/sublime-jinja2"]
path = sublime/syntaxes/sublime-jinja2
url = https://github.com/Martin819/sublime-jinja2.git
[submodule "sublime/syntaxes/Julia-sublime"]
path = sublime/syntaxes/Julia-sublime
url = https://github.com/JuliaEditorSupport/Julia-sublime.git
[submodule "sublime/syntaxes/LESS-sublime"]
path = sublime/syntaxes/LESS-sublime
url = https://github.com/danro/LESS-sublime.git
[submodule "sublime/syntaxes/sublime-purescript-syntax"]
path = sublime/syntaxes/sublime-purescript-syntax
url = https://github.com/tellnobody1/sublime-purescript-syntax.git
[submodule "sublime/syntaxes/SublimeSass"]
path = sublime/syntaxes/SublimeSass
url = https://github.com/braver/SublimeSass.git
[submodule "sublime/syntaxes/sublime_toml_highlighting"]
path = sublime/syntaxes/sublime_toml_highlighting
url = https://github.com/jasonwilliams/sublime_toml_highlighting.git
[submodule "sublime/syntaxes/vue-syntax-highlight"]
path = sublime/syntaxes/vue-syntax-highlight
url = https://github.com/vuejs/vue-syntax-highlight.git

View file

@ -1,5 +1,29 @@
# Changelog
## 0.11.0 (2020-05-25)
### Breaking
- RSS feed support has been altered to allow, *and default to*, Atom feeds, Atom being technically superior and just as widely-supported in normal use cases.
- New config value `feed_filename`, defaulting to `atom.xml` (change to `rss.xml` to reinstate the old behaviour)
- Config value `rss_limit` is renamed to `feed_limit`
- Config value `languages.*.rss` is renamed to `languages.*.feed`
- Config value `generate_rss` is renamed to `generate_feed`
Users with existing feeds should either set `feed_filename = "rss.xml"` in config.toml to keep things the same, or set up a 3xx redirect from rss.xml to atom.xml so that existing feed consumers arent broken.
- The feed template variable `last_build_date` is renamed to `last_updated` to more accurately reflect its semantics
- The sitemap templates `SitemapEntry` types `date` field has been renamed to `updated` to reflect that it will use the `updated` front-matter field if available, rather than `date`
### Other
- Add `updated` front-matter field for pages, which sitemap templates will use for the `SitemapEntry.date` field instead of the `date` front-matter field, and which the default Atom feed template will use
- Add `lang` to the feed template context
- Add `taxonomy` and `term` to the feed template context for taxonomy feeds
- Fix link checker not looking for anchor with capital id/name
- Pass missing `lang` template parameter to taxonomy list template
- Fix default index section not having its path set to '/'
- Change cachebust strategy to use SHA256 instead of timestamp
- Fix
## 0.10.1 (2020-03-12)
- Set user agent for HTTP requests
@ -12,8 +36,6 @@
### Breaking
- Remove `toc` variable in section/page context and pass it to `page.toc` and `section.toc` instead so they are
accessible everywhere
- [Slugification](https://en.wikipedia.org/wiki/Slug_(web_publishing)#Slug) of paths, taxonomies and anchors is now configurable. By default, everything will still be slugified like in previous versions.
See documentation for information on how to disable it.
### Other
- Add zenburn syntax highlighting theme

View file

@ -35,7 +35,7 @@ Tools > Developer > New Syntax from ... and put it at the root of `sublime_synta
You can also add a submodule to the repository of the wanted syntax:
```bash
$ cd sublime_syntaxes
$ cd sublime/syntaxes
$ git submodule add https://github.com/elm-community/SublimeElmLanguageSupport
```
@ -51,7 +51,7 @@ $ git submodule update --remote --merge
And finally from the root of the components/config crate run the following command:
```bash
$ cargo run --example generate_sublime synpack ../../sublime_syntaxes ../../sublime_syntaxes/newlines.packdump
$ cargo run --example generate_sublime synpack ../../sublime/syntaxes ../../sublime/syntaxes/newlines.packdump
```
### Adding a theme
@ -60,7 +60,7 @@ More themes can be easily added to Zola, just make a PR with the wanted theme ad
and run the following command from the root of the components/config:
```bash
$ cargo run --example generate_sublime themepack ../../sublime_themes ../../sublime_themes/all.themedump
$ cargo run --example generate_sublime themepack ../../sublime/themes ../../sublime/themes/all.themedump
```
You should see the list of themes being added.

2825
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "zola"
version = "0.10.1"
version = "0.11.0"
authors = ["Vincent Prouillet <hello@vincentprouillet.com>"]
edition = "2018"
license = "MIT"

View file

@ -19,9 +19,9 @@ stages:
linux-stable:
imageName: 'ubuntu-16.04'
rustup_toolchain: stable
linux-1.39:
linux-pinned:
imageName: 'ubuntu-16.04'
rustup_toolchain: 1.39.0
rustup_toolchain: 1.41.0
pool:
vmImage: $(imageName)
steps:

View file

@ -20,8 +20,8 @@ Register-ArgumentCompleter -Native -CommandName 'zola' -ScriptBlock {
$completions = @(switch ($command) {
'zola' {
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Path to a config file other than config.toml')
[CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'Path to a config file other than config.toml')
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Path to a config file other than config.toml in the root of project')
[CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'Path to a config file other than config.toml in the root of project')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')

View file

@ -1,4 +1,4 @@
complete -c zola -n "__fish_use_subcommand" -s c -l config -d 'Path to a config file other than config.toml'
complete -c zola -n "__fish_use_subcommand" -s c -l config -d 'Path to a config file other than config.toml in the root of project'
complete -c zola -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
complete -c zola -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'
complete -c zola -n "__fish_use_subcommand" -f -a "init" -d 'Create a new Zola project'

View file

@ -11,7 +11,7 @@ serde_derive = "1"
chrono = "0.4"
globset = "0.4"
lazy_static = "1"
syntect = "=3.2.0"
syntect = "4.1"
errors = { path = "../errors" }
utils = { path = "../utils" }

View file

@ -26,7 +26,10 @@ fn main() {
(Some(ref cmd), Some(ref package_dir), Some(ref packpath_newlines)) if cmd == "synpack" => {
let mut builder = SyntaxSetBuilder::new();
builder.add_plain_text_syntax();
builder.add_from_folder(package_dir, true).unwrap();
match builder.add_from_folder(package_dir, true) {
Ok(_) => (),
Err(e) => println!("Loading error: {:?}", e)
};
let ss = builder.build();
dump_to_file(&ss, packpath_newlines).unwrap();
let mut syntaxes: HashMap<String, HashSet<String>> = HashMap::new();

View file

@ -47,15 +47,15 @@ impl Default for Slugify {
pub struct Language {
/// The language code
pub code: String,
/// Whether to generate a RSS feed for that language, defaults to `false`
pub rss: bool,
/// Whether to generate a feed for that language, defaults to `false`
pub feed: bool,
/// Whether to generate search index for that language, defaults to `false`
pub search: bool,
}
impl Default for Language {
fn default() -> Self {
Language { code: String::new(), rss: false, search: false }
Language { code: String::new(), feed: false, search: false }
}
}
@ -68,8 +68,8 @@ pub struct Taxonomy {
/// by this much
pub paginate_by: Option<usize>,
pub paginate_path: Option<String>,
/// Whether to generate a RSS feed only for each taxonomy term, defaults to false
pub rss: bool,
/// Whether to generate a feed only for each taxonomy term, defaults to false
pub feed: bool,
/// The language for that taxonomy, only used in multilingual sites.
/// Defaults to the config `default_language` if not set
pub lang: String,
@ -99,7 +99,7 @@ impl Default for Taxonomy {
name: String::new(),
paginate_by: None,
paginate_path: None,
rss: false,
feed: false,
lang: String::new(),
}
}
@ -155,10 +155,13 @@ pub struct Config {
/// Defaults to "base16-ocean-dark"
pub highlight_theme: String,
/// Whether to generate RSS. Defaults to false
pub generate_rss: bool,
/// The number of articles to include in the RSS feed. Defaults to including all items.
pub rss_limit: Option<usize>,
/// Whether to generate a feed. Defaults to false.
pub generate_feed: bool,
/// The number of articles to include in the feed. Defaults to including all items.
pub feed_limit: Option<usize>,
/// The filename to use for feeds. Used to find the template, too.
/// Defaults to "atom.xml", with "rss.xml" also having a template provided out of the box.
pub feed_filename: String,
/// If set, files from static/ will be hardlinked instead of copied to the output dir.
pub hard_link_static: bool,
@ -215,6 +218,10 @@ impl Config {
bail!("Highlight theme {} not available", config.highlight_theme)
}
if config.languages.iter().any(|l| l.code == config.default_language) {
bail!("Default language `{}` should not appear both in `config.default_language` and `config.languages`", config.default_language)
}
config.build_timestamp = Some(Utc::now().timestamp());
if !config.ignored_content.is_empty() {
@ -272,11 +279,12 @@ impl Config {
/// Makes a url, taking into account that the base url might have a trailing slash
pub fn make_permalink(&self, path: &str) -> String {
let trailing_bit = if path.ends_with('/') || path.ends_with("rss.xml") || path.is_empty() {
""
} else {
"/"
};
let trailing_bit =
if path.ends_with('/') || path.ends_with(&self.feed_filename) || path.is_empty() {
""
} else {
"/"
};
// Index section with a base url that has a trailing slash
if self.base_url.ends_with('/') && path == "/" {
@ -380,8 +388,9 @@ impl Default for Config {
highlight_theme: "base16-ocean-dark".to_string(),
default_language: "en".to_string(),
languages: Vec::new(),
generate_rss: false,
rss_limit: None,
generate_feed: false,
feed_limit: None,
feed_filename: "atom.xml".to_string(),
hard_link_static: false,
taxonomies: Vec::new(),
compile_sass: false,
@ -489,10 +498,10 @@ hello = "world"
// https://github.com/Keats/gutenberg/issues/486
#[test]
fn doesnt_add_trailing_slash_to_rss() {
fn doesnt_add_trailing_slash_to_feed() {
let mut config = Config::default();
config.base_url = "http://vincent.is/".to_string();
assert_eq!(config.make_permalink("rss.xml"), "http://vincent.is/rss.xml");
assert_eq!(config.make_permalink("atom.xml"), "http://vincent.is/atom.xml");
}
#[test]
@ -657,4 +666,19 @@ anchors = "off"
assert_eq!(config.slugify.taxonomies, SlugifyStrategy::Safe);
assert_eq!(config.slugify.anchors, SlugifyStrategy::Off);
}
#[test]
fn error_on_language_set_twice() {
let config_str = r#"
base_url = "https://remplace-par-ton-url.fr"
default_language = "fr"
languages = [
{ code = "fr" },
{ code = "en" },
]
"#;
let config = Config::parse(config_str);
let err = config.unwrap_err();
assert_eq!("Default language `fr` should not appear both in `config.default_language` and `config.languages`", format!("{}", err));
}
}

View file

@ -9,11 +9,11 @@ use crate::config::Config;
lazy_static! {
pub static ref SYNTAX_SET: SyntaxSet = {
let ss: SyntaxSet =
from_binary(include_bytes!("../../../sublime_syntaxes/newlines.packdump"));
from_binary(include_bytes!("../../../sublime/syntaxes/newlines.packdump"));
ss
};
pub static ref THEME_SET: ThemeSet =
from_binary(include_bytes!("../../../sublime_themes/all.themedump"));
from_binary(include_bytes!("../../../sublime/themes/all.themedump"));
}
/// Returns the highlighter and whether it was found in the extra or not

View file

@ -7,11 +7,11 @@ use std::path::Path;
/// Get and parse the config.
/// If it doesn't succeed, exit
pub fn get_config(path: &Path, filename: &str) -> Config {
match Config::from_file(path.join(filename)) {
pub fn get_config(filename: &Path) -> Config {
match Config::from_file(filename) {
Ok(c) => c,
Err(e) => {
println!("Failed to load {}", filename);
println!("Failed to load {}", filename.display());
println!("Error: {}", e);
::std::process::exit(1);
}

View file

@ -43,7 +43,7 @@ impl Theme {
let content = read_file_with_error(
path,
"No `theme.toml` file found. \
Is the `theme` defined in your `config.toml present in the `themes` directory \
Is the `theme` defined in your `config.toml` present in the `themes` directory \
and does it have a `theme.toml` inside?",
)?;
Theme::parse(&content)

View file

@ -8,4 +8,4 @@ edition = "2018"
tera = "1"
toml = "0.5"
image = "0.23"
syntect = "=3.2.0"
syntect = "4.1"

0
components/errors/src/lib.rs Executable file → Normal file
View file

View file

@ -16,6 +16,9 @@ pub struct PageFrontMatter {
pub title: Option<String>,
/// Description in <meta> that appears when linked, e.g. on twitter
pub description: Option<String>,
/// Updated date
#[serde(default, deserialize_with = "from_toml_datetime")]
pub updated: Option<String>,
/// Date if we want to order pages (ie blog post)
#[serde(default, deserialize_with = "from_toml_datetime")]
pub date: Option<String>,
@ -117,6 +120,7 @@ impl Default for PageFrontMatter {
PageFrontMatter {
title: None,
description: None,
updated: None,
date: None,
datetime: None,
datetime_tuple: None,

View file

@ -195,7 +195,7 @@ mod tests {
#[test]
fn can_find_valid_language_in_page() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let mut file = FileInfo::new_page(
&Path::new("/home/vincent/code/site/content/posts/tutorials/python.fr.md"),
&PathBuf::new(),
@ -208,7 +208,7 @@ mod tests {
#[test]
fn can_find_valid_language_in_page_with_assets() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let mut file = FileInfo::new_page(
&Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.fr.md"),
&PathBuf::new(),
@ -234,7 +234,7 @@ mod tests {
#[test]
fn errors_on_unknown_language_in_page_with_i18n_on() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("it"), rss: false, search: false });
config.languages.push(Language { code: String::from("it"), feed: false, search: false });
let mut file = FileInfo::new_page(
&Path::new("/home/vincent/code/site/content/posts/tutorials/python.fr.md"),
&PathBuf::new(),
@ -246,7 +246,7 @@ mod tests {
#[test]
fn can_find_valid_language_in_section() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let mut file = FileInfo::new_section(
&Path::new("/home/vincent/code/site/content/posts/tutorials/_index.fr.md"),
&PathBuf::new(),
@ -273,7 +273,7 @@ mod tests {
#[test]
fn correct_canonical_after_find_language() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let mut file = FileInfo::new_page(
&Path::new("/home/vincent/code/site/content/posts/tutorials/python/index.fr.md"),
&PathBuf::new(),

View file

@ -770,7 +770,7 @@ Hello world
#[test]
fn can_specify_language_in_filename() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#"
+++
+++
@ -787,7 +787,7 @@ Bonjour le monde"#
#[test]
fn can_specify_language_in_filename_with_date() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#"
+++
+++
@ -806,7 +806,7 @@ Bonjour le monde"#
#[test]
fn i18n_frontmatter_path_overrides_default_permalink() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#"
+++
path = "bonjour"

View file

@ -350,7 +350,7 @@ mod tests {
#[test]
fn can_specify_language_in_filename() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#"
+++
+++
@ -372,7 +372,7 @@ Bonjour le monde"#
#[test]
fn can_make_links_to_translated_sections_without_double_trailing_slash() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#"
+++
+++
@ -389,7 +389,7 @@ Bonjour le monde"#
#[test]
fn can_make_links_to_translated_subsections_with_trailing_slash() {
let mut config = Config::default();
config.languages.push(Language { code: String::from("fr"), rss: false, search: false });
config.languages.push(Language { code: String::from("fr"), feed: false, search: false });
let content = r#"
+++
+++

View file

@ -63,6 +63,7 @@ pub struct SerializingPage<'a> {
ancestors: Vec<String>,
title: &'a Option<String>,
description: &'a Option<String>,
updated: &'a Option<String>,
date: &'a Option<String>,
year: Option<i32>,
month: Option<u32>,
@ -126,6 +127,7 @@ impl<'a> SerializingPage<'a> {
title: &page.meta.title,
description: &page.meta.description,
extra: &page.meta.extra,
updated: &page.meta.updated,
date: &page.meta.date,
year,
month,
@ -182,6 +184,7 @@ impl<'a> SerializingPage<'a> {
title: &page.meta.title,
description: &page.meta.description,
extra: &page.meta.extra,
updated: &page.meta.updated,
date: &page.meta.date,
year,
month,

View file

@ -6,7 +6,7 @@ use slotmap::DefaultKey;
use crate::content::Page;
/// Used by the RSS feed
/// Used by the feed
/// There to not have to import sorting stuff in the site crate
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn sort_actual_pages_by_date(a: &&Page, b: &&Page) -> Ordering {

View file

@ -169,6 +169,7 @@ impl Taxonomy {
let terms: Vec<SerializedTaxonomyItem> =
self.items.iter().map(|i| SerializedTaxonomyItem::from_item(i, library)).collect();
context.insert("terms", &terms);
context.insert("lang", &self.kind.lang);
context.insert("taxonomy", &self.kind);
context.insert("current_url", &config.make_permalink(&self.kind.name));
context.insert("current_path", &self.kind.name);
@ -457,7 +458,7 @@ mod tests {
#[test]
fn can_make_taxonomies_in_multiple_languages() {
let mut config = Config::default();
config.languages.push(Language { rss: false, code: "fr".to_string(), search: false });
config.languages.push(Language { feed: false, code: "fr".to_string(), search: false });
let mut library = Library::new(2, 0, true);
config.taxonomies = vec![
@ -568,7 +569,7 @@ mod tests {
let mut config = Config::default();
config.slugify.taxonomies = SlugifyStrategy::Safe;
config.languages.push(Language {
rss: false,
feed: false,
code: "fr".to_string(),
..Language::default()
});
@ -601,7 +602,7 @@ mod tests {
let mut config = Config::default();
config.slugify.taxonomies = SlugifyStrategy::On;
config.languages.push(Language {
rss: false,
feed: false,
code: "fr".to_string(),
..Language::default()
});

View file

@ -5,11 +5,15 @@ authors = ["Vincent Prouillet <prouillet.vincent@gmail.com>"]
edition = "2018"
[dependencies]
reqwest = { version = "0.10", features = ["blocking", "rustls-tls"] }
lazy_static = "1"
config = { path = "../config" }
errors = { path = "../errors" }
[dependencies.reqwest]
version = "0.10"
default-features = false
features = ["blocking", "rustls-tls"]
[dev-dependencies]
mockito = "0.23"
mockito = "0.25"

View file

@ -3,50 +3,33 @@ use reqwest::header::{HeaderMap, ACCEPT};
use reqwest::{blocking::Client, StatusCode};
use config::LinkChecker;
use errors::Result;
use std::collections::HashMap;
use std::result;
use std::sync::{Arc, RwLock};
#[derive(Clone, Debug, PartialEq)]
pub struct LinkResult {
pub code: Option<StatusCode>,
/// Whether the HTTP request didn't make it to getting a HTTP code
pub error: Option<String>,
pub type Result = result::Result<StatusCode, String>;
pub fn is_valid(res: &Result) -> bool {
match res {
Ok(ref code) => code.is_success() || *code == StatusCode::NOT_MODIFIED,
Err(_) => false,
}
}
impl LinkResult {
pub fn is_valid(&self) -> bool {
if self.error.is_some() {
return false;
}
if let Some(c) = self.code {
return c.is_success() || c == StatusCode::NOT_MODIFIED;
}
true
}
pub fn message(&self) -> String {
if let Some(ref e) = self.error {
return e.clone();
}
if let Some(c) = self.code {
return format!("{}", c);
}
"Unknown error".to_string()
pub fn message(res: &Result) -> String {
match res {
Ok(ref code) => format!("{}", code),
Err(ref error) => error.clone(),
}
}
lazy_static! {
// Keep history of link checks so a rebuild doesn't have to check again
static ref LINKS: Arc<RwLock<HashMap<String, LinkResult>>> = Arc::new(RwLock::new(HashMap::new()));
static ref LINKS: Arc<RwLock<HashMap<String, Result>>> = Arc::new(RwLock::new(HashMap::new()));
}
pub fn check_url(url: &str, config: &LinkChecker) -> LinkResult {
pub fn check_url(url: &str, config: &LinkChecker) -> Result {
{
let guard = LINKS.read().unwrap();
if let Some(res) = guard.get(url) {
@ -75,13 +58,13 @@ pub fn check_url(url: &str, config: &LinkChecker) -> LinkResult {
};
match check_page_for_anchor(url, body) {
Ok(_) => LinkResult { code: Some(response.status()), error: None },
Err(e) => LinkResult { code: None, error: Some(e.to_string()) },
Ok(_) => Ok(response.status()),
Err(e) => Err(e.to_string()),
}
}
Ok(response) => {
if response.status().is_success() || response.status() == StatusCode::NOT_MODIFIED {
LinkResult { code: Some(response.status()), error: None }
Ok(response.status())
} else {
let error_string = if response.status().is_informational() {
format!("Informational status code ({}) received", response.status())
@ -95,10 +78,10 @@ pub fn check_url(url: &str, config: &LinkChecker) -> LinkResult {
format!("Non-success status code ({}) received", response.status())
};
LinkResult { code: None, error: Some(error_string) }
Err(error_string)
}
}
Err(e) => LinkResult { code: None, error: Some(e.to_string()) },
Err(e) => Err(e.to_string()),
};
LINKS.write().unwrap().insert(url.to_string(), res.clone());
@ -115,14 +98,18 @@ fn has_anchor(url: &str) -> bool {
}
}
fn check_page_for_anchor(url: &str, body: String) -> Result<()> {
fn check_page_for_anchor(url: &str, body: String) -> errors::Result<()> {
let index = url.find('#').unwrap();
let anchor = url.get(index + 1..).unwrap();
let checks: [String; 4] = [
let checks: [String; 8] = [
format!(" id='{}'", anchor),
format!(" ID='{}'", anchor),
format!(r#" id="{}""#, anchor),
format!(r#" ID="{}""#, anchor),
format!(" name='{}'", anchor),
format!(" NAME='{}'", anchor),
format!(r#" name="{}""#, anchor),
format!(r#" NAME="{}""#, anchor),
];
if checks.iter().any(|check| body[..].contains(&check[..])) {
@ -134,8 +121,11 @@ fn check_page_for_anchor(url: &str, body: String) -> Result<()> {
#[cfg(test)]
mod tests {
use super::{check_page_for_anchor, check_url, has_anchor, LinkChecker, LINKS};
use super::{
check_page_for_anchor, check_url, has_anchor, is_valid, message, LinkChecker, LINKS,
};
use mockito::mock;
use reqwest::StatusCode;
// NOTE: HTTP mock paths below are randomly generated to avoid name
// collisions. Mocks with the same path can sometimes bleed between tests
@ -163,7 +153,8 @@ mod tests {
.create();
let res = check_url(&url, &LinkChecker::default());
assert!(res.is_valid());
assert!(is_valid(&res));
assert_eq!(message(&res), "200 OK");
assert!(LINKS.read().unwrap().get(&url).is_some());
}
@ -183,9 +174,9 @@ mod tests {
let url = format!("{}{}", mockito::server_url(), "/c7qrtrv3zz");
let res = check_url(&url, &LinkChecker::default());
assert!(res.is_valid());
assert!(res.code.is_some());
assert!(res.error.is_none());
assert!(is_valid(&res));
assert!(res.is_ok());
assert_eq!(message(&res), "200 OK");
}
#[test]
@ -199,9 +190,8 @@ mod tests {
let url = format!("{}{}", mockito::server_url(), "/C4Szbfnvj6M0LoPk");
let res = check_url(&url, &LinkChecker::default());
assert!(res.is_valid());
assert!(res.code.is_some());
assert!(res.error.is_none());
assert!(is_valid(&res));
assert_eq!(res.unwrap(), StatusCode::OK);
}
#[test]
@ -221,9 +211,9 @@ mod tests {
let url = format!("{}{}", mockito::server_url(), "/cav9vibhsc");
let res = check_url(&url, &LinkChecker::default());
assert_eq!(res.is_valid(), false);
assert!(res.code.is_none());
assert!(res.error.is_some());
assert!(!is_valid(&res));
assert!(res.is_err());
assert_eq!(message(&res), "Client error status code (404 Not Found) received");
}
#[test]
@ -236,9 +226,9 @@ mod tests {
let url = format!("{}{}", mockito::server_url(), "/nlhab9c1vc");
let res = check_url(&url, &LinkChecker::default());
assert_eq!(res.is_valid(), false);
assert!(res.code.is_none());
assert!(res.error.is_some());
assert!(!is_valid(&res));
assert!(res.is_err());
assert_eq!(message(&res), "Client error status code (404 Not Found) received");
}
#[test]
@ -251,17 +241,18 @@ mod tests {
let url = format!("{}{}", mockito::server_url(), "/qdbrssazes");
let res = check_url(&url, &LinkChecker::default());
assert_eq!(res.is_valid(), false);
assert!(res.code.is_none());
assert!(res.error.is_some());
assert!(!is_valid(&res));
assert!(res.is_err());
assert_eq!(message(&res), "Server error status code (500 Internal Server Error) received");
}
#[test]
fn can_fail_unresolved_links() {
let res = check_url("https://t6l5cn9lpm.lxizfnzckd", &LinkChecker::default());
assert_eq!(res.is_valid(), false);
assert!(res.code.is_none());
assert!(res.error.is_some());
assert!(!is_valid(&res));
assert!(res.is_err());
assert!(message(&res)
.starts_with("error sending request for url (https://t6l5cn9lpm.lxizfnzckd/)"));
}
#[test]
@ -272,6 +263,15 @@ mod tests {
assert!(res.is_ok());
}
// https://github.com/getzola/zola/issues/948
#[test]
fn can_validate_anchors_in_capital() {
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect";
let body = r#"<body><h3 ID="method.collect">collect</h3></body>"#.to_string();
let res = check_page_for_anchor(url, body);
assert!(res.is_ok());
}
#[test]
fn can_validate_anchors_with_other_quotes() {
let url = "https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect";
@ -299,29 +299,25 @@ mod tests {
#[test]
fn can_check_url_for_anchor() {
let url = "https://doc.rust-lang.org/std/index.html#the-rust-standard-library";
let res = has_anchor(url);
assert_eq!(res, true);
assert!(has_anchor(url));
}
#[test]
fn will_return_false_when_no_anchor() {
let url = "https://doc.rust-lang.org/std/index.html";
let res = has_anchor(url);
assert_eq!(res, false);
assert!(!has_anchor(url));
}
#[test]
fn will_return_false_when_has_router_url() {
let url = "https://doc.rust-lang.org/#/std";
let res = has_anchor(url);
assert_eq!(res, false);
assert!(!has_anchor(url));
}
#[test]
fn will_return_false_when_has_router_url_alt() {
let url = "https://doc.rust-lang.org/#!/std";
let res = has_anchor(url);
assert_eq!(res, false);
assert!(!has_anchor(url));
}
#[test]
@ -347,7 +343,7 @@ mod tests {
// anchor check is ignored because the url matches the prefix
let ignore = format!("{}{}", mockito::server_url(), "/ignore/i30hobj1cy#nonexistent");
assert!(check_url(&ignore, &config).is_valid());
assert!(is_valid(&check_url(&ignore, &config)));
let _m2 = mock("GET", "/guvqcqwmth")
.with_header("Content-Type", "text/html")
@ -367,9 +363,9 @@ mod tests {
// other anchors are checked
let existent = format!("{}{}", mockito::server_url(), "/guvqcqwmth#existent");
assert!(check_url(&existent, &config).is_valid());
assert!(is_valid(&check_url(&existent, &config)));
let nonexistent = format!("{}{}", mockito::server_url(), "/guvqcqwmth#nonexistent");
assert_eq!(check_url(&nonexistent, &config).is_valid(), false);
assert!(!is_valid(&check_url(&nonexistent, &config)));
}
}

View file

@ -395,7 +395,17 @@ pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
match filename {
"sitemap.xml" => site.render_sitemap(),
"rss.xml" => site.render_rss_feed(site.library.read().unwrap().pages_values(), None),
filename if filename == site.config.feed_filename => {
// FIXME: this is insufficient; for multilingual sites, its rendering the wrong
// content into the root feed, and its not regenerating any of the other feeds (other
// languages or taxonomies with feed enabled).
site.render_feed(
site.library.read().unwrap().pages_values(),
None,
&site.config.default_language,
None,
)
}
"split_sitemap_index.xml" => site.render_sitemap(),
"robots.txt" => site.render_robots(),
"single.html" | "list.html" => site.render_taxonomies(),

View file

@ -20,7 +20,8 @@ macro_rules! load_and_build_site {
dir::copy(&path, &$tmp_dir, &options).unwrap();
let site_path = $tmp_dir.path().join($site);
let mut site = Site::new(&site_path, "config.toml").unwrap();
let config_file = site_path.join("config.toml");
let mut site = Site::new(&site_path, &config_file).unwrap();
site.load().unwrap();
let public = &site_path.join("public");
site.set_output_path(&public);

View file

@ -6,7 +6,7 @@ edition = "2018"
[dependencies]
tera = { version = "1", features = ["preserve_order"] }
syntect = "=3.2.0"
syntect = "4.1"
pulldown-cmark = "0.7"
serde = "1"
serde_derive = "1"

View file

@ -231,7 +231,9 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
.unwrap_or(::syntect::highlighting::Color::WHITE);
background = IncludeBackground::IfDifferent(color);
let snippet = start_highlighted_html_snippet(theme);
Event::Html(snippet.0.into())
let mut html = snippet.0;
html.push_str("<code>");
Event::Html(html.into())
}
Event::End(Tag::CodeBlock(_)) => {
if !context.config.highlight_code {
@ -239,7 +241,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
}
// reset highlight and close the code block
highlighter = None;
Event::Html("</pre>".into())
Event::Html("</code></pre>".into())
}
Event::Start(Tag::Image(link_type, src, title)) => {
if is_colocated_asset_link(&src) {
@ -329,6 +331,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
};
let mut c = tera::Context::new();
c.insert("id", &id);
c.insert("level", &heading_ref.level);
let anchor_link = utils::templates::render_template(
&ANCHOR_LINK_TEMPLATE,

View file

@ -39,7 +39,7 @@ fn can_highlight_code_block_no_lang() {
let res = render_content("```\n$ gutenberg server\n$ ping\n```", &context).unwrap();
assert_eq!(
res.body,
"<pre style=\"background-color:#2b303b;\">\n<span style=\"color:#c0c5ce;\">$ gutenberg server\n$ ping\n</span></pre>"
"<pre style=\"background-color:#2b303b;\">\n<code><span style=\"color:#c0c5ce;\">$ gutenberg server\n$ ping\n</span></code></pre>"
);
}
@ -53,7 +53,7 @@ fn can_highlight_code_block_with_lang() {
let res = render_content("```python\nlist.append(1)\n```", &context).unwrap();
assert_eq!(
res.body,
"<pre style=\"background-color:#2b303b;\">\n<span style=\"color:#c0c5ce;\">list.</span><span style=\"color:#bf616a;\">append</span><span style=\"color:#c0c5ce;\">(</span><span style=\"color:#d08770;\">1</span><span style=\"color:#c0c5ce;\">)\n</span></pre>"
"<pre style=\"background-color:#2b303b;\">\n<code><span style=\"color:#c0c5ce;\">list.</span><span style=\"color:#bf616a;\">append</span><span style=\"color:#c0c5ce;\">(</span><span style=\"color:#d08770;\">1</span><span style=\"color:#c0c5ce;\">)\n</span></code></pre>"
);
}
@ -68,7 +68,7 @@ fn can_higlight_code_block_with_unknown_lang() {
// defaults to plain text
assert_eq!(
res.body,
"<pre style=\"background-color:#2b303b;\">\n<span style=\"color:#c0c5ce;\">list.append(1)\n</span></pre>"
"<pre style=\"background-color:#2b303b;\">\n<code><span style=\"color:#c0c5ce;\">list.append(1)\n</span></code></pre>"
);
}

View file

@ -108,7 +108,7 @@ base_url = "https://replace-this-with-your-url.com"
theme = "sample"
taxonomies = [
{name = "tags", rss = true},
{name = "tags", feed = true},
{name = "categories"}
]

View file

@ -11,7 +11,8 @@ fn bench_loading_small_blog(b: &mut test::Bencher) {
let mut path = env::current_dir().unwrap().to_path_buf();
path.push("benches");
path.push("small-blog");
let mut site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let mut site = Site::new(&path, &config_file).unwrap();
b.iter(|| site.load().unwrap());
}
@ -21,7 +22,8 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) {
let mut path = env::current_dir().unwrap().to_path_buf();
path.push("benches");
path.push("small-blog");
let mut site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let mut site = Site::new(&path, &config_file).unwrap();
site.config.highlight_code = true;
b.iter(|| site.load().unwrap());
@ -32,7 +34,8 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("medium-blog");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
//
// b.iter(|| site.load().unwrap());
//}
@ -42,7 +45,8 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("medium-blog");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
// site.config.highlight_code = true;
//
// b.iter(|| site.load().unwrap());
@ -53,7 +57,8 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("big-blog");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
//
// b.iter(|| site.load().unwrap());
//}
@ -63,7 +68,8 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("big-blog");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
// site.config.highlight_code = true;
//
// b.iter(|| site.load().unwrap());
@ -74,7 +80,8 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("huge-blog");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
//
// b.iter(|| site.load().unwrap());
//}
@ -84,7 +91,8 @@ fn bench_loading_small_blog_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("huge-blog");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
// site.config.highlight_code = true;
//
// b.iter(|| site.load().unwrap());
@ -95,7 +103,8 @@ fn bench_loading_small_kb(b: &mut test::Bencher) {
let mut path = env::current_dir().unwrap().to_path_buf();
path.push("benches");
path.push("small-kb");
let mut site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let mut site = Site::new(&path, &config_file).unwrap();
b.iter(|| site.load().unwrap());
}
@ -105,7 +114,8 @@ fn bench_loading_small_kb_with_syntax_highlighting(b: &mut test::Bencher) {
let mut path = env::current_dir().unwrap().to_path_buf();
path.push("benches");
path.push("small-kb");
let mut site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let mut site = Site::new(&path, &config_file).unwrap();
site.config.highlight_code = true;
b.iter(|| site.load().unwrap());
@ -116,7 +126,8 @@ fn bench_loading_small_kb_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("medium-kb");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
//
// b.iter(|| site.load().unwrap());
//}
@ -126,7 +137,8 @@ fn bench_loading_small_kb_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("medium-kb");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
// site.config.highlight_code = Some(true);
//
// b.iter(|| site.load().unwrap());
@ -137,7 +149,8 @@ fn bench_loading_small_kb_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("huge-kb");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
//
// b.iter(|| site.load().unwrap());
//}
@ -147,7 +160,8 @@ fn bench_loading_small_kb_with_syntax_highlighting(b: &mut test::Bencher) {
// let mut path = env::current_dir().unwrap().to_path_buf();
// path.push("benches");
// path.push("huge-kb");
// let mut site = Site::new(&path, "config.toml").unwrap();
// let config_file = path.join("config.toml");
// let mut site = Site::new(&path, &config_file).unwrap();
// site.config.highlight_code = Some(true);
//
// b.iter(|| site.load().unwrap());

View file

@ -11,7 +11,8 @@ fn setup_site(name: &str) -> Site {
let mut path = env::current_dir().unwrap().to_path_buf();
path.push("benches");
path.push(name);
let mut site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let mut site = Site::new(&path, &config_file).unwrap();
site.load().unwrap();
site
}
@ -35,12 +36,20 @@ fn bench_render_sitemap(b: &mut test::Bencher) {
}
#[bench]
fn bench_render_rss_feed(b: &mut test::Bencher) {
fn bench_render_feed(b: &mut test::Bencher) {
let mut site = setup_site("big-blog");
let tmp_dir = tempdir().expect("create temp dir");
let public = &tmp_dir.path().join("public");
site.set_output_path(&public);
b.iter(|| site.render_rss_feed(site.library.read().unwrap().pages_values(), None).unwrap());
b.iter(|| {
site.render_feed(
site.library.read().unwrap().pages_values(),
None,
&site.config.default_language,
None,
)
.unwrap();
});
}
#[bench]

View file

@ -8,15 +8,16 @@ use std::sync::{Arc, Mutex, RwLock};
use glob::glob;
use rayon::prelude::*;
use sass_rs::{compile_file, Options as SassOptions, OutputStyle};
use serde_derive::Serialize;
use tera::{Context, Tera};
use config::{get_config, Config};
use config::{get_config, Config, Taxonomy as TaxonomyConfig};
use errors::{bail, Error, ErrorKind, Result};
use front_matter::InsertAnchor;
use library::{
find_taxonomies, sort_actual_pages_by_date, Library, Page, Paginator, Section, Taxonomy,
TaxonomyItem,
};
use link_checker::check_url;
use templates::{global_fns, render_redirect_template, ZOLA_TERA};
use utils::fs::{copy_directory, create_directory, create_file, ensure_directory_exists};
use utils::net::get_available_port;
@ -45,12 +46,26 @@ pub struct Site {
include_drafts: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
struct SerializedTaxonomyItem<'a> {
name: &'a str,
slug: &'a str,
permalink: &'a str,
}
impl<'a> SerializedTaxonomyItem<'a> {
pub fn from_item(item: &'a TaxonomyItem) -> Self {
SerializedTaxonomyItem { name: &item.name, slug: &item.slug, permalink: &item.permalink }
}
}
impl Site {
/// Parse a site at the given path. Defaults to the current dir
/// Passing in a path is used in tests and when --root argument is passed
pub fn new<P: AsRef<Path>>(path: P, config_file: &str) -> Result<Site> {
pub fn new<P: AsRef<Path>, P2: AsRef<Path>>(path: P, config_file: P2) -> Result<Site> {
let path = path.as_ref();
let mut config = get_config(path, config_file);
let config_file = config_file.as_ref();
let mut config = get_config(config_file);
config.load_extra_syntaxes(path)?;
let tpl_glob =
@ -396,8 +411,8 @@ impl Site {
{
return None;
}
let res = check_url(&link, &self.config.link_checker);
if res.is_valid() {
let res = link_checker::check_url(&link, &self.config.link_checker);
if link_checker::is_valid(&res) {
None
} else {
Some((page_path, link, res))
@ -423,7 +438,7 @@ impl Site {
"Dead link in {} to {}: {}",
page_path.to_string_lossy(),
link,
check_res.message()
link_checker::message(&check_res)
)
})
.collect::<Vec<_>>()
@ -458,13 +473,14 @@ impl Site {
let filename = format!("_index.{}.md", l);
index_section.file.path = self.content_path.join(&filename);
index_section.file.relative = filename;
index_section.lang = index_section.file.find_language(&self.config)?;
} else {
index_section.file.name = "_index".to_string();
index_section.permalink = self.config.make_permalink("");
index_section.file.path = self.content_path.join("_index.md");
index_section.file.relative = "_index.md".to_string();
index_section.path = "/".to_string();
}
index_section.lang = index_section.file.find_language(&self.config)?;
library.insert_section(index_section);
}
}
@ -518,7 +534,7 @@ impl Site {
pub fn register_early_global_fns(&mut self) {
self.tera.register_function(
"get_url",
global_fns::GetUrl::new(self.config.clone(), self.permalinks.clone()),
global_fns::GetUrl::new(self.config.clone(), self.permalinks.clone(), self.content_path.clone()),
);
self.tera.register_function(
"resize_image",
@ -626,15 +642,15 @@ impl Site {
}
/// Inject live reload script tag if in live reload mode
fn inject_livereload(&self, html: String) -> String {
fn inject_livereload(&self, mut html: String) -> String {
if let Some(port) = self.live_reload {
return html.replace(
"</body>",
&format!(
r#"<script src="/livereload.js?port={}&amp;mindelay=10"></script></body>"#,
port
),
);
let script =
format!(r#"<script src="/livereload.js?port={}&amp;mindelay=10"></script>"#, port,);
if let Some(index) = html.rfind("</body>") {
html.insert_str(index, &script);
} else {
html.push_str(&script);
}
}
html
@ -743,8 +759,9 @@ impl Site {
self.render_sitemap()?;
let library = self.library.read().unwrap();
if self.config.generate_rss {
let pages = if self.config.is_multilingual() {
if self.config.generate_feed {
let is_multilingual = self.config.is_multilingual();
let pages = if is_multilingual {
library
.pages_values()
.iter()
@ -754,16 +771,16 @@ impl Site {
} else {
library.pages_values()
};
self.render_rss_feed(pages, None)?;
self.render_feed(pages, None, &self.config.default_language, None)?;
}
for lang in &self.config.languages {
if !lang.rss {
if !lang.feed {
continue;
}
let pages =
library.pages_values().iter().filter(|p| p.lang == lang.code).cloned().collect();
self.render_rss_feed(pages, Some(&PathBuf::from(lang.code.clone())))?;
self.render_feed(pages, Some(&PathBuf::from(lang.code.clone())), &lang.code, None)?;
}
self.render_404()?;
@ -981,10 +998,16 @@ impl Site {
create_file(&path.join("index.html"), &self.inject_livereload(single_output))?;
}
if taxonomy.kind.rss {
self.render_rss_feed(
if taxonomy.kind.feed {
self.render_feed(
item.pages.iter().map(|p| library.get_page_by_key(*p)).collect(),
Some(&PathBuf::from(format!("{}/{}", taxonomy.kind.name, item.slug))),
if self.config.is_multilingual() && !taxonomy.kind.lang.is_empty() {
&taxonomy.kind.lang
} else {
&self.config.default_language
},
Some((&taxonomy.kind, &item)),
)
} else {
Ok(())
@ -1043,30 +1066,40 @@ impl Site {
Ok(())
}
/// Renders a RSS feed for the given path and at the given path
/// If both arguments are `None`, it will render only the RSS feed for the whole
/// Renders a feed for the given path and at the given path
/// If both arguments are `None`, it will render only the feed for the whole
/// site at the root folder.
pub fn render_rss_feed(
pub fn render_feed(
&self,
all_pages: Vec<&Page>,
base_path: Option<&PathBuf>,
lang: &str,
taxonomy_and_item: Option<(&TaxonomyConfig, &TaxonomyItem)>,
) -> Result<()> {
ensure_directory_exists(&self.output_path)?;
let mut context = Context::new();
let mut pages = all_pages.into_iter().filter(|p| p.meta.date.is_some()).collect::<Vec<_>>();
// Don't generate a RSS feed if none of the pages has a date
// Don't generate a feed if none of the pages has a date
if pages.is_empty() {
return Ok(());
}
pages.par_sort_unstable_by(sort_actual_pages_by_date);
context.insert("last_build_date", &pages[0].meta.date.clone());
context.insert(
"last_updated",
pages
.iter()
.filter_map(|page| page.meta.updated.as_ref())
.chain(pages[0].meta.date.as_ref())
.max() // I love lexicographically sorted date strings
.unwrap(), // Guaranteed because of pages[0].meta.date
);
let library = self.library.read().unwrap();
// limit to the last n elements if the limit is set; otherwise use all.
let num_entries = self.config.rss_limit.unwrap_or_else(|| pages.len());
let num_entries = self.config.feed_limit.unwrap_or_else(|| pages.len());
let p = pages
.iter()
.take(num_entries)
@ -1075,16 +1108,24 @@ impl Site {
context.insert("pages", &p);
context.insert("config", &self.config);
context.insert("lang", lang);
let rss_feed_url = if let Some(ref base) = base_path {
self.config.make_permalink(&base.join("rss.xml").to_string_lossy().replace('\\', "/"))
let feed_filename = &self.config.feed_filename;
let feed_url = if let Some(ref base) = base_path {
self.config
.make_permalink(&base.join(feed_filename).to_string_lossy().replace('\\', "/"))
} else {
self.config.make_permalink("rss.xml")
self.config.make_permalink(feed_filename)
};
context.insert("feed_url", &rss_feed_url);
context.insert("feed_url", &feed_url);
let feed = &render_template("rss.xml", &self.tera, context, &self.config.theme)?;
if let Some((taxonomy, item)) = taxonomy_and_item {
context.insert("taxonomy", taxonomy);
context.insert("term", &SerializedTaxonomyItem::from_item(item));
}
let feed = &render_template(feed_filename, &self.tera, context, &self.config.theme)?;
if let Some(ref base) = base_path {
let mut output_path = self.output_path.clone();
@ -1094,9 +1135,9 @@ impl Site {
create_directory(&output_path)?;
}
}
create_file(&output_path.join("rss.xml"), feed)?;
create_file(&output_path.join(feed_filename), feed)?;
} else {
create_file(&self.output_path.join("rss.xml"), feed)?;
create_file(&self.output_path.join(feed_filename), feed)?;
}
Ok(())
}

View file

@ -14,7 +14,7 @@ use tera::{Map, Value};
#[derive(Debug, Serialize)]
pub struct SitemapEntry<'a> {
pub permalink: Cow<'a, str>,
pub date: Option<String>,
pub updated: Option<String>,
pub extra: Option<&'a Map<String, Value>>,
}
@ -33,8 +33,8 @@ impl<'a> PartialEq for SitemapEntry<'a> {
impl<'a> Eq for SitemapEntry<'a> {}
impl<'a> SitemapEntry<'a> {
pub fn new(permalink: Cow<'a, str>, date: Option<String>) -> Self {
SitemapEntry { permalink, date, extra: None }
pub fn new(permalink: Cow<'a, str>, updated: Option<String>) -> Self {
SitemapEntry { permalink, updated, extra: None }
}
pub fn add_extra(&mut self, extra: &'a Map<String, Value>) {
@ -65,11 +65,10 @@ pub fn find_entries<'a>(
.pages_values()
.iter()
.map(|p| {
let date = match p.meta.date {
Some(ref d) => Some(d.to_string()),
None => None,
};
let mut entry = SitemapEntry::new(Cow::Borrowed(&p.permalink), date);
let mut entry = SitemapEntry::new(
Cow::Borrowed(&p.permalink),
p.meta.updated.clone().or_else(|| p.meta.date.clone()),
);
entry.add_extra(&p.meta.extra);
entry
})

View file

@ -38,7 +38,8 @@ macro_rules! file_contains {
pub fn build_site(name: &str) -> (Site, TempDir, PathBuf) {
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
path.push(name);
let mut site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let mut site = Site::new(&path, &config_file).unwrap();
site.load().unwrap();
let tmp_dir = tempdir().expect("create temp dir");
let public = &tmp_dir.path().join("public");
@ -54,7 +55,8 @@ where
{
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
path.push(name);
let site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let site = Site::new(&path, &config_file).unwrap();
let (mut site, needs_loading) = setup_cb(site);
if needs_loading {
site.load().unwrap();

View file

@ -13,7 +13,8 @@ use site::Site;
fn can_parse_site() {
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
path.push("test_site");
let mut site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let mut site = Site::new(&path, &config_file).unwrap();
site.load().unwrap();
let library = site.library.read().unwrap();
@ -152,7 +153,7 @@ fn can_build_site_without_live_reload() {
// We do have categories
assert_eq!(file_exists!(public, "categories/index.html"), true);
assert_eq!(file_exists!(public, "categories/a-category/index.html"), true);
assert_eq!(file_exists!(public, "categories/a-category/rss.xml"), true);
assert_eq!(file_exists!(public, "categories/a-category/atom.xml"), true);
// But no tags
assert_eq!(file_exists!(public, "tags/index.html"), false);
@ -232,7 +233,7 @@ fn can_build_site_with_live_reload_and_drafts() {
// We do have categories
assert_eq!(file_exists!(public, "categories/index.html"), true);
assert_eq!(file_exists!(public, "categories/a-category/index.html"), true);
assert_eq!(file_exists!(public, "categories/a-category/rss.xml"), true);
assert_eq!(file_exists!(public, "categories/a-category/atom.xml"), true);
// But no tags
assert_eq!(file_exists!(public, "tags/index.html"), false);
@ -294,11 +295,11 @@ fn can_build_site_with_taxonomies() {
assert!(file_exists!(public, "categories/index.html"));
assert!(file_exists!(public, "categories/a/index.html"));
assert!(file_exists!(public, "categories/b/index.html"));
assert!(file_exists!(public, "categories/a/rss.xml"));
assert!(file_exists!(public, "categories/a/atom.xml"));
assert!(file_contains!(
public,
"categories/a/rss.xml",
"https://replace-this-with-your-url.com/categories/a/rss.xml"
"categories/a/atom.xml",
"https://replace-this-with-your-url.com/categories/a/atom.xml"
));
// Extending from a theme works
assert!(file_contains!(public, "categories/a/index.html", "EXTENDED"));
@ -513,7 +514,7 @@ fn can_build_site_with_pagination_for_taxonomy() {
name: "tags".to_string(),
paginate_by: Some(2),
paginate_path: None,
rss: true,
feed: true,
lang: site.config.default_language.clone(),
});
site.load().unwrap();
@ -547,9 +548,9 @@ fn can_build_site_with_pagination_for_taxonomy() {
// Tags
assert!(file_exists!(public, "tags/index.html"));
// With RSS
assert!(file_exists!(public, "tags/a/rss.xml"));
assert!(file_exists!(public, "tags/b/rss.xml"));
// With Atom
assert!(file_exists!(public, "tags/a/atom.xml"));
assert!(file_exists!(public, "tags/b/atom.xml"));
// And pagination!
assert!(file_exists!(public, "tags/a/page/1/index.html"));
assert!(file_exists!(public, "tags/b/page/1/index.html"));
@ -588,15 +589,15 @@ fn can_build_site_with_pagination_for_taxonomy() {
}
#[test]
fn can_build_rss_feed() {
fn can_build_feed() {
let (_, _tmp_dir, public) = build_site("test_site");
assert!(&public.exists());
assert!(file_exists!(public, "rss.xml"));
assert!(file_exists!(public, "atom.xml"));
// latest article is posts/extra-syntax.md
assert!(file_contains!(public, "rss.xml", "Extra Syntax"));
assert!(file_contains!(public, "atom.xml", "Extra Syntax"));
// Next is posts/simple.md
assert!(file_contains!(public, "rss.xml", "Simple article with shortcodes"));
assert!(file_contains!(public, "atom.xml", "Simple article with shortcodes"));
}
#[test]
@ -628,7 +629,8 @@ fn can_build_with_extra_syntaxes() {
fn can_apply_page_templates() {
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
path.push("test_site");
let mut site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let mut site = Site::new(&path, &config_file).unwrap();
site.load().unwrap();
let template_path = path.join("content").join("applying_page_template");

View file

@ -9,7 +9,8 @@ use site::Site;
fn can_parse_multilingual_site() {
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf();
path.push("test_site_i18n");
let mut site = Site::new(&path, "config.toml").unwrap();
let config_file = path.join("config.toml");
let mut site = Site::new(&path, &config_file).unwrap();
site.load().unwrap();
let library = site.library.read().unwrap();
@ -115,15 +116,25 @@ fn can_build_multilingual_site() {
assert!(file_contains!(public, "sitemap.xml", "https://example.com/fr/blog/something-else/"));
assert!(file_contains!(public, "sitemap.xml", "https://example.com/it/blog/something-else/"));
// one rss per language
assert!(file_exists!(public, "rss.xml"));
assert!(file_contains!(public, "rss.xml", "https://example.com/blog/something-else/"));
assert!(!file_contains!(public, "rss.xml", "https://example.com/fr/blog/something-else/"));
assert!(file_exists!(public, "fr/rss.xml"));
assert!(!file_contains!(public, "fr/rss.xml", "https://example.com/blog/something-else/"));
assert!(file_contains!(public, "fr/rss.xml", "https://example.com/fr/blog/something-else/"));
// Italian doesn't have RSS enabled
assert!(!file_exists!(public, "it/rss.xml"));
// one feed per language
assert!(file_exists!(public, "atom.xml"));
assert!(file_contains!(public, "atom.xml", "https://example.com/blog/something-else/"));
assert!(!file_contains!(public, "atom.xml", "https://example.com/fr/blog/something-else/"));
assert!(file_contains!(
public,
"atom.xml",
r#"<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">"#
));
assert!(file_exists!(public, "fr/atom.xml"));
assert!(!file_contains!(public, "fr/atom.xml", "https://example.com/blog/something-else/"));
assert!(file_contains!(public, "fr/atom.xml", "https://example.com/fr/blog/something-else/"));
assert!(file_contains!(
public,
"fr/atom.xml",
r#"<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">"#
));
// Italian doesn't have feed enabled
assert!(!file_exists!(public, "it/atom.xml"));
// Taxonomies are per-language
// English
@ -131,7 +142,17 @@ fn can_build_multilingual_site() {
assert!(file_contains!(public, "authors/index.html", "Queen"));
assert!(!file_contains!(public, "authors/index.html", "Vincent"));
assert!(!file_exists!(public, "auteurs/index.html"));
assert!(file_exists!(public, "authors/queen-elizabeth/rss.xml"));
assert!(file_exists!(public, "authors/queen-elizabeth/atom.xml"));
assert!(file_contains!(
public,
"authors/queen-elizabeth/atom.xml",
r#"<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">"#
));
assert!(file_contains!(
public,
"authors/queen-elizabeth/atom.xml",
r#"<title> - Queen Elizabeth</title>"#
));
assert!(file_exists!(public, "tags/index.html"));
assert!(file_contains!(public, "tags/index.html", "hello"));
@ -142,7 +163,7 @@ fn can_build_multilingual_site() {
assert!(file_exists!(public, "fr/auteurs/index.html"));
assert!(!file_contains!(public, "fr/auteurs/index.html", "Queen"));
assert!(file_contains!(public, "fr/auteurs/index.html", "Vincent"));
assert!(!file_exists!(public, "fr/auteurs/vincent-prouillet/rss.xml"));
assert!(!file_exists!(public, "fr/auteurs/vincent-prouillet/atom.xml"));
assert!(file_exists!(public, "fr/tags/index.html"));
assert!(file_contains!(public, "fr/tags/index.html", "bonjour"));

View file

@ -13,7 +13,7 @@ toml = "0.5"
csv = "1"
image = "0.23"
serde_json = "1.0"
reqwest = { version = "0.10", features = ["blocking"] }
sha2 = "0.8"
url = "2"
errors = { path = "../errors" }
@ -22,5 +22,10 @@ library = { path = "../library" }
config = { path = "../config" }
imageproc = { path = "../imageproc" }
[dependencies.reqwest]
version = "0.10"
default-features = false
features = ["blocking", "rustls-tls"]
[dev-dependencies]
mockito = "0.23"
mockito = "0.25"

View file

@ -1,10 +1,3 @@
<!doctype html>
<html>
<head>
<title>File Not Found: 404.</title>
</head>
<body>
<h1>Oops!</h1>
<h2>File Not Found: 404.</h2>
</body>
</html>
<title>404 Not Found</title>
<h1>404 Not Found</h1>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ lang }}">
<title>{{ config.title }}
{%- if term %} - {{ term.name }}
{%- endif -%}
</title>
{%- if config.description %}
<subtitle>{{ config.description }}</subtitle>
{%- endif %}
<link href="{{ feed_url | safe }}" rel="self" type="application/atom+xml"/>
<link href="{{ config.base_url | safe }}"/>
<generator uri="https://www.getzola.org/">Zola</generator>
<updated>{{ last_updated | date(format="%+") }}</updated>
<id>{{ feed_url | safe }}</id>
{%- for page in pages %}
<entry xml:lang="{{ page.lang }}">
<title>{{ page.title }}</title>
<published>{{ page.date | date(format="%+") }}</published>
<updated>{{ page.updated | default(value=page.date) | date(format="%+") }}</updated>
<link href="{{ page.permalink | safe }}" type="text/html"/>
<id>{{ page.permalink | safe }}</id>
<content type="html">{{ page.content }}</content>
</entry>
{%- endfor %}
</feed>

View file

@ -1,12 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<title>Redirect</title>
<link rel="canonical" href="{{ url | safe }}" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="refresh" content="0;url={{ url | safe }}" />
</head>
<body>
<p><a href="{{ url | safe }}">Click here</a> to be redirected.</p>
</body>
</html>
<!doctype html>
<meta charset="utf-8">
<link rel="canonical" href="{{ url | safe }}">
<meta http-equiv="refresh" content="0;url={{ url | safe }}">
<title>Redirect</title>
<p><a href="{{ url | safe }}">Click here</a> to be redirected.</p>

View file

@ -7,15 +7,15 @@
<generator>Zola</generator>
<language>{{ config.default_language }}</language>
<atom:link href="{{ feed_url | safe }}" rel="self" type="application/rss+xml"/>
<lastBuildDate>{{ last_build_date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate>
{% for page in pages %}
<item>
<title>{{ page.title }}</title>
<pubDate>{{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</pubDate>
<link>{{ page.permalink | escape_xml | safe }}</link>
<guid>{{ page.permalink | escape_xml | safe }}</guid>
<description>{% if page.summary %}{{ page.summary }}{% else %}{{ page.content }}{% endif %}</description>
</item>
{% endfor %}
<lastBuildDate>{{ last_updated | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate>
{%- for page in pages %}
<item>
<title>{{ page.title }}</title>
<pubDate>{{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</pubDate>
<link>{{ page.permalink | escape_xml | safe }}</link>
<guid>{{ page.permalink | escape_xml | safe }}</guid>
<description>{% if page.summary %}{{ page.summary }}{% else %}{{ page.content }}{% endif %}</description>
</item>
{%- endfor %}
</channel>
</rss>

View file

@ -1,7 +1,3 @@
<div {% if class %}class="{{class}}"{% endif %}>
<iframe src="https://www.streamable.com/e/{{id}}"
scrolling="no"
frameborder="0"
allowfullscreen mozallowfullscreen webkitallowfullscreen>
</iframe>
<iframe src="https://www.streamable.com/e/{{id}}" scrolling="no" frameborder="0" allowfullscreen mozallowfullscreen webkitallowfullscreen></iframe>
</div>

View file

@ -1,4 +1,3 @@
<div {% if class %}class="{{class}}"{% endif %}>
<iframe src="//player.vimeo.com/video/{{id}}" webkitallowfullscreen mozallowfullscreen allowfullscreen>
</iframe>
<iframe src="//player.vimeo.com/video/{{id}}" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
</div>

View file

@ -1,4 +1,3 @@
<div {% if class %}class="{{class}}"{% endif %}>
<iframe src="https://www.youtube.com/embed/{{id}}{% if autoplay %}?autoplay=1{% endif %}" webkitallowfullscreen mozallowfullscreen allowfullscreen>
</iframe>
<iframe src="https://www.youtube.com/embed/{{id}}{% if autoplay %}?autoplay=1{% endif %}" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
</div>

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for sitemap_entry in entries %}
<url>
<loc>{{ sitemap_entry.permalink | escape_xml | safe }}</loc>
{% if sitemap_entry.date %}
<lastmod>{{ sitemap_entry.date }}</lastmod>
{% endif %}
</url>
{% endfor %}
{%- for sitemap_entry in entries %}
<url>
<loc>{{ sitemap_entry.permalink | escape_xml | safe }}</loc>
{%- if sitemap_entry.updated %}
<lastmod>{{ sitemap_entry.updated }}</lastmod>
{%- endif %}
</url>
{%- endfor %}
</urlset>

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for sitemap in sitemaps %}
<sitemap>
<loc>{{ sitemap }}</loc>
</sitemap>
{% endfor %}
</sitemapindex>
{%- for sitemap in sitemaps %}
<sitemap>
<loc>{{ sitemap }}</loc>
</sitemap>
{%- endfor %}
</sitemapindex>

View file

@ -210,11 +210,10 @@ impl TeraFn for LoadData {
.send()
.and_then(|res| res.error_for_status())
.map_err(|e| {
format!(
"Failed to request {}: {}",
url,
e.status().expect("response status")
)
match e.status() {
Some(status) => format!("Failed to request {}: {}", url, status),
None => format!("Could not get response status for url: {}", url),
}
})?;
response
.text()

View file

@ -1,7 +1,9 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::{fs, io, result};
use sha2::{Digest, Sha256};
use tera::{from_value, to_value, Error, Function as TeraFn, Result, Value};
use config::Config;
@ -47,12 +49,38 @@ impl TeraFn for Trans {
pub struct GetUrl {
config: Config,
permalinks: HashMap<String, String>,
content_path: PathBuf,
}
impl GetUrl {
pub fn new(config: Config, permalinks: HashMap<String, String>) -> Self {
Self { config, permalinks }
pub fn new(config: Config, permalinks: HashMap<String, String>, content_path: PathBuf) -> Self {
Self { config, permalinks, content_path }
}
}
fn make_path_with_lang(path: String, lang: &str, config: &Config) -> Result<String> {
if lang == &config.default_language {
return Ok(path);
}
if !config.languages.iter().any(|x| x.code == lang) {
return Err(
format!("`{}` is not an authorized language (check config.languages).", lang).into()
);
}
let mut splitted_path: Vec<String> = path.split(".").map(String::from).collect();
let ilast = splitted_path.len() - 1;
splitted_path[ilast] = format!("{}.{}", lang, splitted_path[ilast]);
Ok(splitted_path.join("."))
}
fn compute_file_sha256(path: &PathBuf) -> result::Result<String, io::Error> {
let mut file = fs::File::open(path)?;
let mut hasher = Sha256::new();
io::copy(&mut file, &mut hasher)?;
Ok(format!("{:x}", hasher.result()))
}
impl TeraFn for GetUrl {
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
let cachebust =
@ -67,11 +95,21 @@ impl TeraFn for GetUrl {
args.get("path"),
"`get_url` requires a `path` argument with a string value"
);
let lang = optional_arg!(String, args.get("lang"), "`get_url`: `lang` must be a string.")
.unwrap_or_else(|| self.config.default_language.clone());
if path.starts_with("@/") {
match resolve_internal_link(&path, &self.permalinks) {
let path_with_lang = match make_path_with_lang(path, &lang, &self.config) {
Ok(x) => x,
Err(e) => return Err(e),
};
match resolve_internal_link(&path_with_lang, &self.permalinks) {
Ok(resolved) => Ok(to_value(resolved.permalink).unwrap()),
Err(_) => {
Err(format!("Could not resolve URL for link `{}` not found.", path).into())
Err(format!("Could not resolve URL for link `{}` not found.", path_with_lang)
.into())
}
}
} else {
@ -82,7 +120,11 @@ impl TeraFn for GetUrl {
}
if cachebust {
permalink = format!("{}?t={}", permalink, self.config.build_timestamp.unwrap());
let full_path = self.content_path.join(&path);
permalink = match compute_file_sha256(&full_path) {
Ok(digest) => format!("{}?h={}", permalink, digest),
Err(_) => return Err(format!("Could not read file `{}`. Expected location: {}", path, full_path.to_str().unwrap()).into()),
};
}
Ok(to_value(permalink).unwrap())
}
@ -340,28 +382,56 @@ mod tests {
use super::{GetTaxonomy, GetTaxonomyUrl, GetUrl, Trans};
use std::collections::HashMap;
use std::env::temp_dir;
use std::fs::remove_dir_all;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use lazy_static::lazy_static;
use tera::{to_value, Function, Value};
use config::{Config, Taxonomy as TaxonomyConfig};
use library::{Library, Taxonomy, TaxonomyItem};
use utils::fs::{create_directory, create_file};
use utils::slugs::SlugifyStrategy;
struct TestContext {
content_path: PathBuf,
}
impl TestContext {
fn setup() -> Self {
let dir = temp_dir().join("test_global_fns");
create_directory(&dir).expect("Could not create test directory");
create_file(&dir.join("app.css"), "// Hello world!")
.expect("Could not create test content (app.css)");
Self { content_path: dir }
}
}
impl Drop for TestContext {
fn drop(&mut self) {
remove_dir_all(&self.content_path).expect("Could not free test directory");
}
}
lazy_static! {
static ref TEST_CONTEXT: TestContext = TestContext::setup();
}
#[test]
fn can_add_cachebust_to_url() {
let config = Config::default();
let static_fn = GetUrl::new(config, HashMap::new());
let static_fn = GetUrl::new(config, HashMap::new(), TEST_CONTEXT.content_path.clone());
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("app.css").unwrap());
args.insert("cachebust".to_string(), to_value(true).unwrap());
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css?t=1");
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css?h=572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840");
}
#[test]
fn can_add_trailing_slashes() {
let config = Config::default();
let static_fn = GetUrl::new(config, HashMap::new());
let static_fn = GetUrl::new(config, HashMap::new(), TEST_CONTEXT.content_path.clone());
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("app.css").unwrap());
args.insert("trailing_slash".to_string(), to_value(true).unwrap());
@ -371,18 +441,18 @@ mod tests {
#[test]
fn can_add_slashes_and_cachebust() {
let config = Config::default();
let static_fn = GetUrl::new(config, HashMap::new());
let static_fn = GetUrl::new(config, HashMap::new(), TEST_CONTEXT.content_path.clone());
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("app.css").unwrap());
args.insert("trailing_slash".to_string(), to_value(true).unwrap());
args.insert("cachebust".to_string(), to_value(true).unwrap());
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css/?t=1");
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css/?h=572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840");
}
#[test]
fn can_link_to_some_static_file() {
let config = Config::default();
let static_fn = GetUrl::new(config, HashMap::new());
let static_fn = GetUrl::new(config, HashMap::new(), TEST_CONTEXT.content_path.clone());
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("app.css").unwrap());
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css");
@ -516,6 +586,9 @@ mod tests {
const TRANS_CONFIG: &str = r#"
base_url = "https://remplace-par-ton-url.fr"
default_language = "fr"
languages = [
{ code = "en" },
]
[translations]
[translations.fr]
@ -562,4 +635,62 @@ title = "A title"
let error = Trans::new(config).call(&args).unwrap_err();
assert_eq!("Failed to retreive term translation", format!("{}", error));
}
#[test]
fn error_when_language_not_available() {
let config = Config::parse(TRANS_CONFIG).unwrap();
let static_fn = GetUrl::new(config, HashMap::new(), TEST_CONTEXT.content_path.clone());
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap());
args.insert("lang".to_string(), to_value("it").unwrap());
let err = static_fn.call(&args).unwrap_err();
assert_eq!(
"`it` is not an authorized language (check config.languages).",
format!("{}", err)
);
}
#[test]
fn can_get_url_with_default_language() {
let config = Config::parse(TRANS_CONFIG).unwrap();
let mut permalinks = HashMap::new();
permalinks.insert(
"a_section/a_page.md".to_string(),
"https://remplace-par-ton-url.fr/a_section/a_page/".to_string(),
);
permalinks.insert(
"a_section/a_page.en.md".to_string(),
"https://remplace-par-ton-url.fr/en/a_section/a_page/".to_string(),
);
let static_fn = GetUrl::new(config, permalinks, TEST_CONTEXT.content_path.clone());
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap());
args.insert("lang".to_string(), to_value("fr").unwrap());
assert_eq!(
static_fn.call(&args).unwrap(),
"https://remplace-par-ton-url.fr/a_section/a_page/"
);
}
#[test]
fn can_get_url_with_other_language() {
let config = Config::parse(TRANS_CONFIG).unwrap();
let mut permalinks = HashMap::new();
permalinks.insert(
"a_section/a_page.md".to_string(),
"https://remplace-par-ton-url.fr/a_section/a_page/".to_string(),
);
permalinks.insert(
"a_section/a_page.en.md".to_string(),
"https://remplace-par-ton-url.fr/en/a_section/a_page/".to_string(),
);
let static_fn = GetUrl::new(config, permalinks, TEST_CONTEXT.content_path.clone());
let mut args = HashMap::new();
args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap());
args.insert("lang".to_string(), to_value("en").unwrap());
assert_eq!(
static_fn.call(&args).unwrap(),
"https://remplace-par-ton-url.fr/en/a_section/a_page/"
);
}
}

View file

@ -11,6 +11,7 @@ lazy_static! {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("__zola_builtins/404.html", include_str!("builtins/404.html")),
("__zola_builtins/atom.xml", include_str!("builtins/atom.xml")),
("__zola_builtins/rss.xml", include_str!("builtins/rss.xml")),
("__zola_builtins/sitemap.xml", include_str!("builtins/sitemap.xml")),
("__zola_builtins/robots.txt", include_str!("builtins/robots.txt")),

View file

@ -13,6 +13,7 @@ serde = "1"
serde_derive = "1"
slug = "0.1"
percent-encoding = "2"
filetime = "0.2.8"
errors = { path = "../errors" }

View file

@ -1,4 +1,5 @@
use std::fs::{copy, create_dir_all, read_dir, File};
use filetime::{set_file_mtime, FileTime};
use std::fs::{copy, create_dir_all, metadata, read_dir, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
@ -94,7 +95,11 @@ pub fn find_related_assets(path: &Path) -> Vec<PathBuf> {
}
/// Copy a file but takes into account where to start the copy as
/// there might be folders we need to create on the way
/// there might be folders we need to create on the way.
/// No copy occurs if all of the following conditions are satisfied:
/// 1. A file with the same name already exists in the dest path.
/// 2. Its modification timestamp is identical to that of the src file.
/// 3. Its filesize is identical to that of the src file.
pub fn copy_file(src: &Path, dest: &PathBuf, base_path: &PathBuf, hard_link: bool) -> Result<()> {
let relative_path = src.strip_prefix(base_path).unwrap();
let target_path = dest.join(relative_path);
@ -106,7 +111,19 @@ pub fn copy_file(src: &Path, dest: &PathBuf, base_path: &PathBuf, hard_link: boo
if hard_link {
std::fs::hard_link(src, target_path)?
} else {
copy(src, target_path)?;
let src_metadata = metadata(src)?;
let src_mtime = FileTime::from_last_modification_time(&src_metadata);
if Path::new(&target_path).is_file() {
let target_metadata = metadata(&target_path)?;
let target_mtime = FileTime::from_last_modification_time(&target_metadata);
if !(src_mtime == target_mtime && src_metadata.len() == target_metadata.len()) {
copy(src, &target_path)?;
set_file_mtime(&target_path, src_mtime)?;
}
} else {
copy(src, &target_path)?;
set_file_mtime(&target_path, src_mtime)?;
}
}
Ok(())
}
@ -160,11 +177,14 @@ where
#[cfg(test)]
mod tests {
use std::fs::File;
use std::fs::{metadata, read_to_string, File};
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
use tempfile::tempdir;
use tempfile::{tempdir, tempdir_in};
use super::find_related_assets;
use super::{copy_file, find_related_assets};
#[test]
fn can_find_related_assets() {
@ -181,4 +201,68 @@ mod tests {
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "graph.jpg").count(), 1);
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "fail.png").count(), 1);
}
#[test]
fn test_copy_file_timestamp_preserved() {
let base_path = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap();
let src_dir =
tempdir_in(&base_path).expect("failed to create a temporary source directory.");
let dest_dir =
tempdir_in(&base_path).expect("failed to create a temporary destination directory.");
let src_file_path = src_dir.path().join("test.txt");
let dest_file_path = dest_dir.path().join(src_file_path.strip_prefix(&base_path).unwrap());
File::create(&src_file_path).unwrap();
copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
assert_eq!(
metadata(&src_file_path).and_then(|m| m.modified()).unwrap(),
metadata(&dest_file_path).and_then(|m| m.modified()).unwrap()
);
}
#[test]
fn test_copy_file_already_exists() {
let base_path = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap();
let src_dir =
tempdir_in(&base_path).expect("failed to create a temporary source directory.");
let dest_dir =
tempdir_in(&base_path).expect("failed to create a temporary destination directory.");
let src_file_path = src_dir.path().join("test.txt");
let dest_file_path = dest_dir.path().join(src_file_path.strip_prefix(&base_path).unwrap());
{
let mut src_file = File::create(&src_file_path).unwrap();
src_file.write_all(b"file1").unwrap();
}
copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
{
let mut dest_file = File::create(&dest_file_path).unwrap();
dest_file.write_all(b"file2").unwrap();
}
// Check copy does not occur when moditication timestamps and filesizes are same.
filetime::set_file_mtime(&src_file_path, filetime::FileTime::from_unix_time(0, 0)).unwrap();
filetime::set_file_mtime(&dest_file_path, filetime::FileTime::from_unix_time(0, 0))
.unwrap();
copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
assert_eq!(read_to_string(&src_file_path).unwrap(), "file1");
assert_eq!(read_to_string(&dest_file_path).unwrap(), "file2");
// Copy occurs if the timestamps are different while the filesizes are same.
filetime::set_file_mtime(&dest_file_path, filetime::FileTime::from_unix_time(42, 42))
.unwrap();
copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
assert_eq!(read_to_string(&src_file_path).unwrap(), "file1");
assert_eq!(read_to_string(&dest_file_path).unwrap(), "file1");
// Copy occurs if the timestamps are same while the filesizes are different.
{
let mut dest_file = File::create(&dest_file_path).unwrap();
dest_file.write_all(b"This file has different file size to the source file!").unwrap();
}
filetime::set_file_mtime(&dest_file_path, filetime::FileTime::from_unix_time(0, 0))
.unwrap();
copy_file(&src_file_path, &dest_dir.path().to_path_buf(), &base_path, false).unwrap();
assert_eq!(read_to_string(&src_file_path).unwrap(), "file1");
assert_eq!(read_to_string(&dest_file_path).unwrap(), "file1");
}
}

View file

@ -5,8 +5,9 @@ weight = 50
## Heading id and anchor insertion
While rendering the Markdown content, a unique id will automatically be assigned to each heading.
This id is created by converting the heading text to a [slug](https://en.wikipedia.org/wiki/Semantic_URL#Slug) if `slugify_paths` is enabled.
if `slugify_paths` is disabled, whitespaces are replaced by `_` and the following characters are stripped: `#`, `%`, `<`, `>`, `[`, `]`, `(`, `)`, \`, `^`, `{`, `|`, `}`.
This id is created by converting the heading text to a [slug](https://en.wikipedia.org/wiki/Semantic_URL#Slug) if `slugify.anchors` is set to `"on"` (the default).
If `slugify.paths` is set to `"safe"`, whitespaces are replaced by `_` and the following characters are stripped: `#`, `%`, `<`, `>`, `[`, `]`, `(`, `)`, \`, `^`, `{`, `|`, `}`.
If `slugify.paths` is set to `"off"`, no modifications are made, and you may be left with nominally illegal ids.
A number is appended at the end if the slug already exists for that article
For example:
@ -38,7 +39,12 @@ This option is set at the section level: the `insert_anchor_links` variable on t
The default template is very basic and will need CSS tweaks in your project to look decent.
If you want to change the anchor template, it can be easily overwritten by
creating an `anchor-link.html` file in the `templates` directory, which gets an `id` variable.
creating an `anchor-link.html` file in the `templates` directory.
The anchor link template has the following variables:
- `id`: the heading's id after applying the rules defined by `slugify.anchors`
- `level`: the heading level (between 1 and 6)
## Internal links
Linking to other pages and their headings is so common that Zola adds a

View file

@ -11,9 +11,9 @@ to your `config.toml`. For example:
```toml
languages = [
{code = "fr", rss = true}, # there will be a RSS feed for French content
{code = "fr", feed = true}, # there will be a feed for French content
{code = "fr", search = true}, # there will be a Search Index for French content
{code = "it"}, # there won't be a RSS feed for Italian content
{code = "it"}, # there won't be a feed for Italian content
]
```

View file

@ -35,12 +35,13 @@ For any page within your content folder, its output path will be defined by eith
- its filename
Either way, these proposed path will be sanitized before being used.
If `slugify_paths` is enabled in the site's config - the default - paths are [slugified](https://en.wikipedia.org/wiki/Clean_URL#Slug).
Otherwise, a simpler sanitation is performed, outputting only valid NTFS paths.
The following characters are removed: `<`, `>`, `:`, `/`, `|`, `?`, `*`, `#`, `\\`, `(`, `)`, `[`, `]` as well as newlines and tabulations.
If `slugify.paths` is set to `"on"` in the site's config - the default - paths are [slugified](https://en.wikipedia.org/wiki/Clean_URL#Slug).
If it is set to `"safe"`, only sanitation is performed, with the following characters being removed: `<`, `>`, `:`, `/`, `|`, `?`, `*`, `#`, `\\`, `(`, `)`, `[`, `]` as well as newlines and tabulations. This ensures that the path can be represented on all operating systems.
Additionally, trailing whitespace and dots are removed and whitespaces are replaced by `_`.
**NOTE:** To produce URLs containing non-English characters (UTF8), `slugify_paths` needs to be set to `false`.
If `slugify.paths` is set to `"off"`, no modifications are made.
If you want URLs containing non-ASCII characters, `slugify.paths` needs to be set to `"safe"` or `"off"`.
### Path from frontmatter
@ -56,7 +57,7 @@ slug = "femmes-libres-libération-kurde"
This is my article.
```
This frontmatter will output the article to `[base_url]/zines/femmes-libres-libération-kurde` with `slugify_paths` disabled, and to `[base_url]/zines/femmes-libres-liberation-kurde` with `slugify_enabled` enabled.
This frontmatter will output the article to `[base_url]/zines/femmes-libres-libération-kurde` with `slugify.paths` set to `"safe"` or `"off"`, and to `[base_url]/zines/femmes-libres-liberation-kurde` with the default value for `slugify.paths` of `"on"`.
### Path from filename
@ -66,7 +67,7 @@ When the article's output path is not specified in the frontmatter, it is extrac
If the path found starts with a datetime string (`YYYY-mm-dd` or [a RFC3339 datetime](https://www.ietf.org/rfc/rfc3339.txt)) followed by an underscore (`_`) or a dash (`-`), this date is removed from the output path and will be used as the page date (unless already set in the front-matter). Note that the full RFC3339 datetime contains colons, which is not a valid character in a filename on Windows.
The output path extracted from the file path is then slugified or not depending on the `slugify_paths` config, as explained previously.
The output path extracted from the file path is then slugified or not, depending on the `slugify.paths` config, as explained previously.
**Example:**
The file `content/blog/2018-10-10-hello-world.md` will yield a page at `[base_url]/blog/hello-world`.
@ -93,6 +94,10 @@ description = ""
# Setting this overrides a date set in the filename.
date =
# The last updated date of the post, if different from the date.
# Same format as `date`.
updated =
# The weight as defined on the Section page of the documentation.
# If the section variable `sort_by` is set to `weight`, then any page that lacks a `weight`
# will not be rendered.

View file

@ -82,7 +82,7 @@ render = true
# Useful for the same reason as `render` but when you don't want a 404 when
# landing on the root section page.
# Example: redirect_to = "documentation/content/overview"
redirect_to = ""
redirect_to =
# If set to "true", the section will pass its pages on to the parent section. Defaults to `false`.
# Useful when the section shouldn't split up the parent section, like

View file

@ -24,16 +24,18 @@ Here is a full list of supported languages and their short names:
```
- ActionScript -> ["as"]
- Advanced CSV -> ["csv", "tsv"]
- AppleScript -> ["applescript", "script editor"]
- ASP -> ["asa"]
- Assembly x86 (NASM) -> ["asm", "inc", "nasm"]
- AWK -> ["awk"]
- Batch File -> ["bat", "cmd"]
- BibTeX -> ["bib"]
- Bourne Again Shell (bash) -> [".bash_aliases", ".bash_completions", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".profile", ".textmate_init", ".zshrc", "bash", "fish", "sh", "zsh"]
- Bourne Again Shell (bash) -> [".bash_aliases", ".bash_completions", ".bash_functions", ".bash_login", ".bash_logout", ".bash_profile", ".bash_variables", ".bashrc", ".ebuild", ".eclass", ".profile", ".textmate_init", ".zlogin", ".zlogout", ".zprofile", ".zshenv", ".zshrc", "PKGBUILD", "ash", "bash", "sh", "zsh"]
- C -> ["c", "h"]
- C# -> ["cs", "csx"]
- C++ -> ["C", "c++", "cc", "cp", "cpp", "cxx", "h", "h++", "hh", "hpp", "hxx", "inl", "ipp"]
- Clojure -> ["clj"]
- Clojure -> ["clj", "cljc", "cljs", "edn"]
- CMake -> ["CMakeLists.txt", "cmake"]
- CMake C Header -> ["h.in"]
- CMake C++ Header -> ["h++.in", "hh.in", "hpp.in", "hxx.in"]
@ -43,10 +45,15 @@ Here is a full list of supported languages and their short names:
- D -> ["d", "di"]
- Dart -> ["dart"]
- Diff -> ["diff", "patch"]
- Dockerfile -> ["Dockerfile", "dockerfile"]
- Elixir -> ["ex", "exs"]
- Elm -> ["elm"]
- Erlang -> ["Emakefile", "emakefile", "erl", "hrl"]
- fsharp -> ["fs"]
- Erlang -> ["Emakefile", "emakefile", "erl", "escript", "hrl"]
- F# -> ["fs", "fsi", "fsx"]
- Fortran (Fixed Form) -> ["F", "F77", "FOR", "FPP", "f", "f77", "for", "fpp"]
- Fortran (Modern) -> ["F03", "F08", "F90", "F95", "f03", "f08", "f90", "f95"]
- Fortran Namelist -> ["namelist"]
- Friendly Interactive Shell (fish) -> ["fish"]
- Generic Config -> [".dircolors", ".gitattributes", ".gitignore", ".gitmodules", ".inputrc", "Doxyfile", "cfg", "conf", "config", "dircolors", "gitattributes", "gitignore", "gitmodules", "ini", "inputrc", "mak", "mk", "pro"]
- Git Attributes -> [".gitattributes", "attributes", "gitattributes"]
- Git Commit -> ["COMMIT_EDITMSG", "MERGE_MSG", "TAG_EDITMSG"]
@ -54,15 +61,19 @@ Here is a full list of supported languages and their short names:
- Git Ignore -> [".gitignore", "exclude", "gitignore"]
- Git Link -> [".git"]
- Git Log -> ["gitlog"]
- Git Mailmap -> [".mailmap", "mailmap"]
- Git Rebase Todo -> ["git-rebase-todo"]
- Go -> ["go"]
- GraphQL -> ["gql", "graphql"]
- Graphviz (DOT) -> ["DOT", "dot", "gv"]
- Groovy -> ["Jenkinsfile", "gradle", "groovy", "gvy"]
- Handlebars -> ["handlebars", "handlebars.html", "hbr", "hbrs", "hbs", "hdbs", "hjs", "mu", "mustache", "rac", "stache", "template", "tmpl"]
- Haskell -> ["hs"]
- HTML -> ["htm", "html", "shtml", "xhtml"]
- HTML (ASP) -> ["asp"]
- HTML (EEx) -> ["html.eex", "html.leex"]
- HTML (Erlang) -> ["yaws"]
- HTML (Jinja2) -> ["htm.j2", "html.j2", "xhtml.j2", "xml.j2"]
- HTML (Rails) -> ["erb", "html.erb", "rails", "rhtml"]
- HTML (Tcl) -> ["adp"]
- Java -> ["bsh", "java"]
@ -70,8 +81,8 @@ Here is a full list of supported languages and their short names:
- Java Server Page (JSP) -> ["jsp"]
- JavaScript -> ["htc", "js"]
- JavaScript (Rails) -> ["js.erb"]
- Jinja2 -> ["j2", "jinja2"]
- JSON -> ["json", "sublime-build", "sublime-color-scheme", "sublime-commands", "sublime-completions", "sublime-keymap", "sublime-macro", "sublime-menu", "sublime-mousemap", "sublime-project", "sublime-settings", "sublime-theme"]
- Jinja2 -> ["j2", "jinja", "jinja2"]
- JSON -> ["Pipfile.lock", "ipynb", "json", "sublime-build", "sublime-color-scheme", "sublime-commands", "sublime-completions", "sublime-keymap", "sublime-macro", "sublime-menu", "sublime-mousemap", "sublime-project", "sublime-settings", "sublime-theme"]
- Julia -> ["jl"]
- Kotlin -> ["kt", "kts"]
- LaTeX -> ["ltx", "tex"]
@ -94,24 +105,30 @@ Here is a full list of supported languages and their short names:
- OCamllex -> ["mll"]
- OCamlyacc -> ["mly"]
- Pascal -> ["dpr", "p", "pas"]
- Perl -> ["PL", "pl", "pm", "pod", "t"]
- Perl -> ["pc", "pl", "pm", "pmc", "pod", "t"]
- PHP -> ["php", "php3", "php4", "php5", "php7", "phps", "phpt", "phtml"]
- Plain Text -> ["txt"]
- PowerShell -> ["ps1", "psd1", "psm1"]
- Python -> ["SConscript", "SConstruct", "Sconstruct", "Snakefile", "cpy", "gyp", "gypi", "pxd", "pxd.in", "pxi", "pxi.in", "py", "py3", "pyi", "pyw", "pyx", "pyx.in", "rpy", "sconstruct", "wscript"]
- R -> ["R", "Rprofile", "S", "r", "s"]
- PureScript -> ["purs"]
- Python -> ["SConscript", "SConstruct", "Sconstruct", "Snakefile", "bazel", "bzl", "cpy", "gyp", "gypi", "pxd", "pxd.in", "pxi", "pxi.in", "py", "py3", "pyi", "pyw", "pyx", "pyx.in", "rpy", "sconstruct", "vpy", "wscript"]
- R -> ["R", "Rprofile", "r"]
- Racket -> ["rkt"]
- Rd (R Documentation) -> ["rd"]
- Reason -> ["re", "rei"]
- Regular Expression -> ["re"]
- Regular Expressions (Elixir) -> ["ex.re"]
- reStructuredText -> ["rest", "rst"]
- Ruby -> ["Appfile", "Appraisals", "Berksfile", "Brewfile", "Cheffile", "Deliverfile", "Fastfile", "Gemfile", "Guardfile", "Rakefile", "Rantfile", "Scanfile", "Snapfile", "Thorfile", "Vagrantfile", "capfile", "cgi", "config.ru", "fcgi", "gemspec", "irbrc", "jbuilder", "podspec", "prawn", "rabl", "rake", "rb", "rbx", "rjs", "ruby.rail", "simplecov", "thor"]
- Ruby -> ["Appfile", "Appraisals", "Berksfile", "Brewfile", "Cheffile", "Deliverfile", "Fastfile", "Gemfile", "Guardfile", "Podfile", "Rakefile", "Rantfile", "Scanfile", "Snapfile", "Thorfile", "Vagrantfile", "capfile", "cgi", "config.ru", "fcgi", "gemspec", "irbrc", "jbuilder", "podspec", "prawn", "rabl", "rake", "rb", "rbx", "rjs", "ruby.rail", "simplecov", "thor"]
- Ruby Haml -> ["haml", "sass"]
- Ruby on Rails -> ["builder", "rxml"]
- Rust -> ["rs"]
- Scala -> ["sbt", "scala"]
- Sass -> ["sass"]
- Scala -> ["sbt", "sc", "scala"]
- SCSS -> ["scss"]
- SQL -> ["ddl", "dml", "sql"]
- SQL (Rails) -> ["erbsql", "sql.erb"]
- srt -> ["srt", "subrip"]
- Stylus -> ["styl", "stylus"]
- SWI-Prolog -> ["pro"]
- Swift -> ["swift"]
- Tcl -> ["tcl"]
@ -121,7 +138,7 @@ Here is a full list of supported languages and their short names:
- TypeScript -> ["ts"]
- TypeScriptReact -> ["tsx"]
- VimL -> ["vim"]
- XML -> ["dtml", "opml", "rss", "svg", "tld", "xml", "xsd", "xslt"]
- XML -> ["dtml", "opml", "rng", "rss", "svg", "tld", "xml", "xsd", "xslt"]
- YAML -> ["sublime-syntax", "yaml", "yml"]
```

View file

@ -13,7 +13,7 @@ A taxonomy has five variables:
- `paginate_by`: if this is set to a number, each term page will be paginated by this much.
- `paginate_path`: if set, this path will be used by the paginated page and the page number will be appended after it.
For example the default would be page/1.
- `rss`: if set to `true`, an RSS feed will be generated for each term.
- `feed`: if set to `true`, a feed (atom by default) will be generated for each term.
- `lang`: only set this if you are making a multilingual site and want to indicate which language this taxonomy is for
**Example 1:** (one language)
@ -52,7 +52,7 @@ categories = ["programming"]
In a similar manner to how section and pages calculate their output path:
- the taxonomy name is never slugified
- the taxonomy entry (eg. as specific tag) is slugified when `slugify_paths` is enabled in the configuration
- the taxonomy term (eg. as specific tag) is slugified when `slugify.taxonomies` is enabled (`"on"`, the default) in the configuration
The taxonomy pages are then available at the following paths:

View file

@ -17,11 +17,11 @@ used by Zola as well as their default values are listed below:
# The base URL of the site; the only required configuration variable.
base_url = "mywebsite.com"
# The site title and description; used in RSS by default.
# The site title and description; used in feeds by default.
title = ""
description = ""
# The default language; used in RSS.
# The default language; used in feeds.
default_language = "en"
# The site theme to use.
@ -34,12 +34,17 @@ highlight_code = false
# See below for list of allowed values.
highlight_theme = "base16-ocean-dark"
# When set to "true", an RSS feed is automatically generated.
generate_rss = false
# When set to "true", a feed is automatically generated.
generate_feed = false
# The number of articles to include in the RSS feed. All items are included if
# The filename to use for the feed. Used as the template filename, too.
# Defaults to "atom.xml", which has a builtin template that renders an Atom 1.0 feed.
# There is also a builtin template "rss.xml" that renders an RSS 2.0 feed.
# feed_filename = "atom.xml"
# The number of articles to include in the feed. All items are included if
# this limit is not set (the default).
# rss_limit = 20
# feed_limit = 20
# When set to "true", files in the `static` directory are hard-linked. Useful for large
# static files. Note that for this to work, both `static` and the
@ -50,10 +55,10 @@ generate_rss = false
# The taxonomies to be rendered for the site and their configuration.
# Example:
# taxonomies = [
# {name = "tags", rss = true}, # each tag will have its own RSS feed
# {name = "tags", feed = true}, # each tag will have its own feed
# {name = "tags", lang = "fr"}, # you can have taxonomies with the same name in multiple languages
# {name = "categories", paginate_by = 5}, # 5 items per page for a term
# {name = "authors"}, # Basic definition: no RSS or pagination
# {name = "authors"}, # Basic definition: no feed or pagination
# ]
#
taxonomies = []
@ -61,9 +66,9 @@ taxonomies = []
# The additional languages for the site.
# Example:
# languages = [
# {code = "fr", rss = true}, # there will be a RSS feed for French content
# {code = "fr", feed = true}, # there will be a feed for French content
# {code = "fr", search = true}, # there will be a Search Index for French content
# {code = "it"}, # there won't be a RSS feed for Italian content
# {code = "it"}, # there won't be a feed for Italian content
# ]
#
languages = []
@ -155,6 +160,7 @@ Zola currently has the following highlight themes available:
- [material-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Material%20Dark)
- [material-light](https://github.com/morhetz/gruvbox)
- [monokai](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Monokai)
- [nord](https://github.com/crabique/Nord-plist/tree/0d655b23d6b300e691676d9b90a68d92b267f7ec)
- [nyx-bold](https://github.com/GalAster/vscode-theme-nyx)
- [one-dark](https://github.com/andresmichel/one-dark-theme)
- [solarized-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Solarized%20(dark))
@ -162,6 +168,7 @@ Zola currently has the following highlight themes available:
- [subway-madrid](https://github.com/idleberg/Subway.tmTheme)
- [subway-moscow](https://github.com/idleberg/Subway.tmTheme)
- [Tomorrow](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Tomorrow)
- [TwoDark](https://github.com/erremauro/TwoDark)
- [visual-studio-dark](https://tmtheme-editor.herokuapp.com/#!/editor/theme/Visual%20Studio%20Dark)
- [zenburn](https://github.com/colinta/zenburn)

View file

@ -0,0 +1,34 @@
+++
title = "Feeds"
weight = 50
aliases = ["/documentation/templates/rss/"]
+++
If the site `config.toml` file sets `generate_feed = true`, then Zola will
generate a feed file for the site, named according to the `feed_filename`
setting in `config.toml`, which defaults to `atom.xml`. Given the feed filename
`atom.xml`, the generated file will live at `base_url/atom.xml`, based upon the
`atom.xml` file in the `templates` directory, or the built-in Atom template.
`feed_filename` can be set to any value, but built-in templates are provided
for `atom.xml` (in the preferred Atom 1.0 format), and `rss.xml` (in the RSS
2.0 format). If you choose a different filename (e.g. `feed.xml`), you will
need to provide a template yourself.
**Only pages with a date will be available.**
The feed template gets five variables:
- `config`: the site config
- `feed_url`: the full url to that specific feed
- `last_updated`: the most recent `updated` or `date` field of any post
- `pages`: see [page variables](@/documentation/templates/pages-sections.md#page-variables)
for a detailed description of what this contains
- `lang`: the language code that applies to all of the pages in the feed,
if the site is multilingual, or `config.default_language` if it is not
Feeds for taxonomy terms get two more variables, using types from the
[taxonomies templates](@/documentation/templates/taxonomies.md):
- `taxonomy`: of type `TaxonomyConfig`
- `term`: of type `TaxonomyTerm`, but without `term.pages` (use `pages` instead)

View file

@ -13,13 +13,14 @@ to learn more about it first.
All templates live in the `templates` directory. If you are not sure what variables are available in a template,
you can place `{{ __tera_context }}` in the template to print the whole context.
A few variables are available on all templates except RSS and the sitemap:
A few variables are available on all templates except feeds and the sitemap:
- `config`: the [configuration](@/documentation/getting-started/configuration.md) without any modifications
- `current_path`: the path (full URL without `base_url`) of the current page, never starting with a `/`
- `current_url`: the full URL for the current page
- `lang`: the language for the current page; `null` if the page/section doesn't have a language set
- `lang`: the language for the current page
Config variables can be accessed like `config.variable`, in HTML for example with `{{ config.base_url }}`.
The 404 template does not get `current_path` and `current_url` (this information cannot be determined).
## Standard templates
@ -35,12 +36,13 @@ section variables. The `page.html` template has access to the page variables.
The page and section variables are described in more detail in the next section.
## Built-in templates
Zola comes with three built-in templates: `rss.xml`, `sitemap.xml` and
`robots.txt` (each is described in its own section of this documentation).
Zola comes with four built-in templates: `atom.xml` and `rss.xml` (described in
[Feeds](@/documentation/templates/feeds.md)), `sitemap.xml` (described in [Sitemap](@/documentation/templates/sitemap.md)),
and `robots.txt` (described in [Robots.txt](@/documentation/templates/robots.md)).
Additionally, themes can add their own templates, which will be applied if not
overridden. You can override built-in or theme templates by creating a template with
the same name in the correct path. For example, you can override the RSS template by
creating a `templates/rss.xml` file.
the same name in the correct path. For example, you can override the Atom template by
creating a `templates/atom.xml` file.
## Custom templates
In addition to the standard `index.html`, `section.html` and `page.html` templates,
@ -72,6 +74,8 @@ pass `true` to the inline argument:
{{ some_text | markdown(inline=true) }}
```
You do not need to use this filter with `page.content` or `section.content`, the content is already rendered.
### base64_encode
Encode the variable to base64.
@ -114,6 +118,16 @@ link like the ones used in Markdown, starting from the root `content` directory.
{% set url = get_url(path="@/blog/_index.md") %}
```
It accepts an optionnal parameter `lang` in order to compute a *language-aware URL* in multilingual websites. Assuming `config.base_url` is `"http://example.com"`, the following snippet will:
- return `"http://example.com/blog/"` if `config.default_language` is `"en"`
- return `"http://example.com/en/blog/"` if `config.default_language` is **not** `"en"` and `"en"` appears in `config.languages`
- fail otherwise, with the error message `"'en' is not an authorized language (check config.languages)."`
```jinja2
{% set url = get_url(path="@/blog/_index.md", lang="en") %}
```
This can also be used to get the permalinks for static assets, for example if
we want to link to the file that is located at `static/css/app.css`:
@ -128,7 +142,7 @@ An example is:
{{/* get_url(path="css/app.css", trailing_slash=true) */}}
```
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 `?h=<sha256>` at the end of a URL
by passing `cachebust=true` to the `get_url` function.

View file

@ -14,10 +14,13 @@ with the following fields:
```ts
// The HTML output of the Markdown content
content: String;
title: String?;
description: String?;
date: String?;
// `updated` will be the same as `date` if `date` is specified but `updated` is not in front-matter
updated: String?;
slug: String;
path: String;
draft: Bool;
@ -68,6 +71,7 @@ with the following fields:
```ts
// The HTML output of the Markdown content
content: String;
title: String?;
description: String?;

View file

@ -1,18 +0,0 @@
+++
title = "RSS"
weight = 50
+++
If the site `config.toml` file sets `generate_rss = true`, then Zola will
generate an `rss.xml` page for the site, which will live at `base_url/rss.xml`. To
generate the `rss.xml` page, Zola will look for an `rss.xml` file in the `templates`
directory or, if one does not exist, it will use the use the built-in rss template.
**Only pages with a date will be available.**
The RSS template gets three variables in addition to `config`:
- `feed_url`: the full url to that specific feed
- `last_build_date`: the date of the latest post
- `pages`: see [page variables](@/documentation/templates/pages-sections.md#page-variables) for
a detailed description of what this contains

View file

@ -25,7 +25,7 @@ A `SitemapEntry` has the following fields:
```ts
permalink: String;
date: String?;
updated: String?;
extra: Hashmap<String, Any>?;
```

View file

@ -21,10 +21,10 @@ and `TaxonomyConfig` has the following fields:
```ts
name: String,
slug: String,
paginate_by: Number?;
paginate_path: String?;
rss: Bool;
feed: Bool;
lang: String;
```

View file

@ -100,8 +100,8 @@ Zulma has 3 taxonomies already set internally: `tags`, `cateogories` and `author
```toml
taxonomies = [
{name = "categories"},
{name = "tags", paginate_by = 5, rss = true},
{name = "authors", rss = true},
{name = "tags", paginate_by = 5, feed = true},
{name = "authors", feed = true},
]
```

View file

@ -55,9 +55,9 @@ The theme requires tags and categories taxonomies to be enabled in your `config.
```toml
taxonomies = [
# You can enable/disable RSS
{name = "categories", rss = true},
{name = "tags", rss = true},
# You can enable/disable feeds
{name = "categories", feed = true},
{name = "tags", feed = true},
]
```
If you want to paginate taxonomies pages, you will need to overwrite the templates

View file

@ -48,9 +48,9 @@ The theme requires tags and categories taxonomies to be enabled in your `config.
```toml
taxonomies = [
# You can enable/disable RSS
{name = "categories", rss = true},
{name = "tags", rss = true},
# You can enable/disable feeds
{name = "categories", feed = true},
{name = "tags", feed = true},
]
```
If you want to paginate taxonomies pages, you will need to overwrite the templates

View file

@ -18,9 +18,8 @@ pub fn build_cli() -> App<'static, 'static> {
Arg::with_name("config")
.short("c")
.long("config")
.default_value("config.toml")
.takes_value(true)
.help("Path to a config file other than config.toml")
.help("Path to a config file other than config.toml in the root of project")
)
.subcommands(vec![
SubCommand::with_name("init")

View file

@ -7,9 +7,9 @@ use crate::console;
pub fn build(
root_dir: &Path,
config_file: &str,
config_file: &Path,
base_url: Option<&str>,
output_dir: &str,
output_dir: &Path,
include_drafts: bool,
) -> Result<()> {
let mut site = Site::new(root_dir, config_file)?;

View file

@ -7,7 +7,7 @@ use crate::console;
pub fn check(
root_dir: &Path,
config_file: &str,
config_file: &Path,
base_path: Option<&str>,
base_url: Option<&str>,
include_drafts: bool,

View file

@ -161,9 +161,9 @@ fn create_new_site(
root_dir: &Path,
interface: &str,
port: u16,
output_dir: &str,
output_dir: &Path,
base_url: &str,
config_file: &str,
config_file: &Path,
include_drafts: bool,
) -> Result<(Site, String)> {
let mut site = Site::new(root_dir, config_file)?;
@ -194,9 +194,9 @@ pub fn serve(
root_dir: &Path,
interface: &str,
port: u16,
output_dir: &str,
output_dir: &Path,
base_url: &str,
config_file: &str,
config_file: &Path,
watch_only: bool,
open: bool,
include_drafts: bool,

View file

@ -16,7 +16,10 @@ fn main() {
"." => env::current_dir().unwrap(),
path => PathBuf::from(path),
};
let config_file = matches.value_of("config").unwrap();
let config_file = match matches.value_of("config") {
Some(path) => PathBuf::from(path),
None => root_dir.join("config.toml"),
};
match matches.subcommand() {
("init", Some(matches)) => {
@ -31,12 +34,12 @@ fn main() {
("build", Some(matches)) => {
console::info("Building site...");
let start = Instant::now();
let output_dir = matches.value_of("output_dir").unwrap();
let output_dir = PathBuf::from(matches.value_of("output_dir").unwrap());
match cmd::build(
&root_dir,
config_file,
&config_file,
matches.value_of("base_url"),
output_dir,
&output_dir,
matches.is_present("drafts"),
) {
Ok(()) => console::report_elapsed_time(start),
@ -73,16 +76,16 @@ fn main() {
::std::process::exit(1);
}
}
let output_dir = matches.value_of("output_dir").unwrap();
let output_dir = PathBuf::from(matches.value_of("output_dir").unwrap());
let base_url = matches.value_of("base_url").unwrap();
console::info("Building site...");
match cmd::serve(
&root_dir,
interface,
port,
output_dir,
&output_dir,
base_url,
config_file,
&config_file,
watch_only,
open,
include_drafts,
@ -99,7 +102,7 @@ fn main() {
let start = Instant::now();
match cmd::check(
&root_dir,
config_file,
&config_file,
matches.value_of("base_path"),
matches.value_of("base_url"),
matches.is_present("drafts"),

View file

@ -0,0 +1,46 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: Advanced CSV
file_extensions:
- csv
- tsv
scope: text.advanced_csv
contexts:
main:
- match: (\")
captures:
1: string.quoted.double.advanced_csv
push:
- meta_scope: meta.quoted.advanced_csv
- match: (\")
captures:
1: string.quoted.double.advanced_csv
pop: true
- include: main
- match: '(\[([+-]?\d*)(\:)?([+-]?\d*)(\,)?([+-]?\d*)(\:)?([+-]?\d*)\])?\s*([<>v^])?\s*(=)'
captures:
1: keyword.operator.advanced_csv
2: constant.numeric.formula.advanced_csv
4: constant.numeric.formula.advanced_csv
6: constant.numeric.formula.advanced_csv
8: constant.numeric.formula.advanced_csv
9: keyword.operator.advanced_csv
10: keyword.operator.advanced_csv
push:
- meta_scope: meta.range.advanced_csv
- match: (?=(\")|$)
pop: true
- include: scope:source.python
- match: '(?<=^|,|\s|\")([0-9.eE+-]+)(?=$|,|\s|\")'
scope: meta.number.advanced_csv
captures:
1: constant.numeric.advanced_csv
- match: '(?<=^|,|\s|\")([^, \t\"]+)(?=$|,|\s|\")'
scope: meta.nonnumber.advanced_csv
captures:
1: storage.type.advanced_csv
- match: (\,)
scope: meta.delimiter.advanced_csv
captures:
1: keyword.operator.advanced_csv

View file

@ -131,9 +131,9 @@ contexts:
scope: keyword.control.pseudo-method.crystal
- match: '\b(nil|true|false)\b(?![?!])'
scope: constant.language.crystal
- match: '\b(__(DIR|FILE|LINE)__|self)\b(?![?!])'
- match: '\b(__(DIR|FILE|LINE|END_LINE)__|self)\b(?![?!])'
scope: variable.language.crystal
- match: '\b(initialize|new|loop|include|extend|raise|getter|setter|property|class_getter|class_setter|class_property|describe|it|with|delegate|def_hash|def_equals|def_equals_and_hash|forward_missing_to|record|assert_responds_to|spawn)\b[!?]?'
- match: '\b(initialize|new|loop|include|extend|raise|getter|setter|property|class_getter|class_setter|class_property|describe|context|it|with|delegate|def_hash|def_equals|def_equals_and_hash|forward_missing_to|record|assert_responds_to|spawn|annotation|verbatim)\b[!?]?'
comment: everything being a method but having a special function is a..
scope: keyword.control.special-method.crystal
- match: \b(require)\b
@ -180,7 +180,7 @@ contexts:
(?<=^|\s)(def)\s+ # the def keyword
( (?>[a-zA-Z_\x{80}-\x{10FFFF}][\x{80}-\x{10FFFF}\w]*(?>\.|::))? # a method name prefix
(?>[a-zA-Z_\x{80}-\x{10FFFF}][\x{80}-\x{10FFFF}\w]*(?>[?!]|=(?!>))? # the method name
|===?|>[>=]?|<=>|<[<=]?|[%&`/\|]|\*\*?|=?~|[-+]@?|\[\]=?) ) # …or an operator method
|===?|>[>=]?|<=>|<[<=]?|[%&`/\|]|\*\*?|=?~|[-+]@?|\[\](?:=|\?)?) ) # …or an operator method
\s*(\() # the openning parenthesis for arguments
comment: the method pattern comes from the symbol pattern, see there for a explaination
captures:
@ -201,7 +201,7 @@ contexts:
(?<=^|\s)(def)\s+ # the def keyword
( (?>[a-zA-Z_\x{80}-\x{10FFFF}][\w\x{80}-\x{10FFFF}]*(?>\.|::))? # a method name prefix
(?>[a-zA-Z_\x{80}-\x{10FFFF}][\w\x{80}-\x{10FFFF}]*(?>[?!]|=(?!>))? # the method name
|===?|>[>=]?|<=>|<[<=]?|[%&`/\|]|\*\*?|=?~|[-+]@?|\[\]=?) ) # …or an operator method
|===?|>[>=]?|<=>|<[<=]?|[%&`/\|]|\*\*?|=?~|[-+]@?|\[\](?:=|\?)?) ) # …or an operator method
[ \t] # the space separating the arguments
(?=[ \t]*[^\s#;]) # make sure arguments and not a comment follow
comment: same as the previous rule, but without parentheses around the arguments
@ -221,7 +221,7 @@ contexts:
( \s+ # an optional group of whitespace followed by…
( (?>[a-zA-Z_\x{80}-\x{10FFFF}][\w\x{80}-\x{10FFFF}]*(?>\.|::))? # a method name prefix
(?>[a-zA-Z_\x{80}-\x{10FFFF}][\w\x{80}-\x{10FFFF}]*(?>[?!]|=(?!>))? # the method name
|===?|>[>=]?|<=>|<[<=]?|[%&`/\|]|\*\*?|=?~|[-+]@?|\[\]=?) ) )? # …or an operator method
|===?|>[>=]?|<=>|<[<=]?|[%&`/\|]|\*\*?|=?~|[-+]@?|\[\](?:=|\?)?) ) )? # …or an operator method
comment: the optional name is just to catch the def also without a method-name
scope: meta.function.method.without-arguments.crystal
captures:
@ -588,7 +588,7 @@ contexts:
pop: true
- match: \\.
comment: Cant be named because its not neccesarily an escape.
- match: '(?<!:)(:)(?>[a-zA-Z_\x{80}-\x{10FFFF}][\w\x{80}-\x{10FFFF}]*(?>[?!]|=(?![>=]))?|===?|>[>=]?|<[<=]?|<=>|[%&`/\|]|\*\*?|=?~|[-+]@?|\[\]=?|@@?[a-zA-Z_\x{80}-\x{10FFFF}][\w\x{80}-\x{10FFFF}]*)'
- match: '(?<!:)(:)(?>[a-zA-Z_\x{80}-\x{10FFFF}][\w\x{80}-\x{10FFFF}]*(?>[?!]|=(?![>=]))?|===?|>[>=]?|<[<=]?|<=>|[%&`/\|]|\*\*?|=?~|[-+]@?|\[\](?:=|\?)?|@@?[a-zA-Z_\x{80}-\x{10FFFF}][\w\x{80}-\x{10FFFF}]*|!=?(?![?!]))'
comment: symbols
scope: constant.other.symbol.crystal
captures:
@ -713,14 +713,14 @@ contexts:
captures:
0: punctuation.definition.string.begin.crystal
push:
- meta_scope: string.unquoted.embedded.js.jquery.crystal
- meta_content_scope: text.js.jquery.embedded.crystal
- meta_scope: string.unquoted.embedded.js.crystal
- meta_content_scope: text.js.embedded.crystal
- match: \s*\2$
captures:
0: punctuation.definition.string.end.crystal
pop: true
- include: heredoc
- include: scope:source.js.jquery
- include: scope:source.js
- include: interpolated_crystal
- include: escaped_char
- match: '(?><<-("?)((?:[_\w]+_|)(?:SH|SHELL))\b\1)'

View file

@ -39,22 +39,31 @@ contexts:
- include: comments-doc-oldschool
- include: comments-doc
- include: comments-inline
comments-block:
- match: /\*
push:
- meta_scope: comment.block.dart
- match: \*/
pop: true
- include: comments-block
comments-doc:
- match: ///
scope: comment.block.documentation.dart
push:
- meta_scope: comment.block.documentation.dart
- match: .*
pop: true
- include: dartdoc
comments-doc-oldschool:
- match: /\*\*
push:
- meta_scope: comment.block.documentation.dart
- match: \*/
pop: true
- include: comments-doc-oldschool
- include: comments-block
- include: dartdoc
comments-inline:
- match: /\*
push:
- meta_scope: comment.block.dart
- match: \*/
pop: true
- include: comments-block
- match: ((//).*)$
captures:
1: comment.line.double-slash.dart
@ -65,16 +74,16 @@ contexts:
scope: variable.language.dart
- match: '(?<!\$)\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)\b(?!\$)'
scope: constant.numeric.dart
- match: "(?<![a-zA-Z0-9_$])[_$]*[A-Z][a-zA-Z0-9_$]*"
- match: '(?<![a-zA-Z0-9_$])([_$]*[A-Z][a-zA-Z0-9_$]*|bool\b|num\b|int\b|double\b|dynamic\b)'
scope: support.class.dart
- match: '([_$]*[a-z][a-zA-Z0-9_$]*)(\(|\s+=>)'
- match: '([_$]*[a-z][a-zA-Z0-9_$]*)(<|\(|\s+=>)'
captures:
1: entity.name.function.dart
dartdoc:
- match: '(\[.*?\])'
captures:
0: variable.name.source.dart
- match: " .*"
- match: '^ {4,}(?![ \*]).*'
captures:
0: variable.name.source.dart
- match: "```.*?$"
@ -105,7 +114,7 @@ contexts:
scope: keyword.control.dart
- match: (?<!\$)\b(new)\b(?!\$)
scope: keyword.control.new.dart
- match: (?<!\$)\b(abstract|class|enum|extends|external|factory|implements|get|mixin|native|operator|set|typedef|with)\b(?!\$)
- match: (?<!\$)\b(abstract|class|enum|extends|external|factory|implements|get|mixin|native|operator|set|typedef|with|covariant)\b(?!\$)
scope: keyword.declaration.dart
- match: (?<!\$)\b(is\!?)\b(?!\$)
scope: keyword.operator.dart
@ -131,7 +140,7 @@ contexts:
scope: keyword.operator.logical.dart
- match: (?<!\$)\b(static|final|const)\b(?!\$)
scope: storage.modifier.dart
- match: (?<!\$)\b(?:void|bool|num|int|double|dynamic|var)\b(?!\$)
- match: (?<!\$)\b(?:void|var)\b(?!\$)
scope: storage.type.primitive.dart
punctuation:
- match: ","

@ -0,0 +1 @@
Subproject commit 9e9a518aed93031042c54710f8f02c839301de26

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

@ -0,0 +1 @@
Subproject commit 6c0d770fc74e6bc037c70cae1f94fe113b60fd95

View file

@ -16,18 +16,19 @@ contexts:
- include: imports
- include: statements
classes:
- match: (?=\s*(?:companion|class|object|interface))
- match: (?<!::)(?=\b(?:companion|class|object|interface)\b)
push:
- match: "}|(?=$)"
- match: '(?=$|\})'
pop: true
- include: comments
- match: \b(companion\s*)?(class|object|interface)\b
captures:
1: keyword.other.kotlin
1: storage.modifier.kotlin
2: storage.modifier.kotlin
push:
- match: '(?=<|{|\(|:)'
- match: '(?=<|\{|\(|:|$)'
pop: true
- match: \b(object)\b
scope: keyword.other.kotlin
- include: comments
- match: \w+
scope: entity.name.type.class.kotlin
- match: <
@ -75,7 +76,7 @@ contexts:
constants:
- match: \b(true|false|null|this|super)\b
scope: constant.language.kotlin
- match: '\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFfUuDd]|UL|ul)?\b'
- match: '\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFf])?\b'
scope: constant.numeric.kotlin
- match: '\b([A-Z][A-Z0-9_]+)\b'
scope: constant.other.kotlin
@ -91,9 +92,9 @@ contexts:
- include: comments
- include: keywords
functions:
- match: (?=\s*(?:fun))
- match: (?=\s*\b(?:fun)\b)
push:
- match: "}|(?=$)"
- match: '(?=$|\})'
pop: true
- match: \b(fun)\b
captures:
@ -192,32 +193,40 @@ contexts:
1: keyword.other.kotlin
2: keyword.other.kotlin
keywords:
- match: \b(var|val|public|private|protected|abstract|final|enum|open|attribute|annotation|override|inline|var|val|vararg|lazy|in|out|internal|data|tailrec|operator|infix|const|yield|typealias|typeof)\b
- match: \b(var|val|public|private|protected|abstract|final|sealed|enum|open|attribute|annotation|override|inline|vararg|in|out|internal|data|tailrec|operator|infix|const|yield|typealias|typeof|reified|suspend)\b
scope: storage.modifier.kotlin
- match: \b(try|catch|finally|throw)\b
scope: keyword.control.catch-exception.kotlin
- match: \b(if|else|while|for|do|return|when|where|break|continue)\b
scope: keyword.control.kotlin
- match: \b(in|is|as|assert)\b
- match: \b(in|is|!in|!is|as|as\?|assert)\b
scope: keyword.operator.kotlin
- match: (==|!=|===|!==|<=|>=|<|>)
scope: keyword.operator.comparison.kotlin
- match: (=)
scope: keyword.operator.assignment.kotlin
- match: (::)
scope: keyword.operator.kotlin
- match: (:)
scope: keyword.operator.declaration.kotlin
- match: \b(by)\b
scope: keyword.other.by.kotlin
- match: (\?\.)
scope: keyword.operator.safenav.kotlin
- match: (\.)
scope: keyword.operator.dot.kotlin
- match: (\?:)
scope: keyword.operator.elvis.kotlin
- match: (\-\-|\+\+)
scope: keyword.operator.increment-decrement.kotlin
- match: (\-|\+|\*|\/|%)
scope: keyword.operator.arithmetic.kotlin
- match: (\+=|\-=|\*=|\/=)
scope: keyword.operator.arithmetic.assign.kotlin
- match: (!|&&|\|\|)
scope: keyword.operator.logical.kotlin
- match: (\.\.)
scope: keyword.operator.range.kotlin
- match: (\-|\+|\*|\/|%)
scope: keyword.operator.arithmetic.kotlin
- match: (!|&&|\|\|)
scope: keyword.operator.logical.kotlin
- match: (;)
scope: punctuation.terminator.kotlin
namespaces:
@ -315,11 +324,11 @@ contexts:
- include: generics
- include: expressions
types:
- match: \b(Any|Unit|String|Int|Boolean|Char|Long|Double|Float|Short|Byte|dynamic)\b
- match: \b(Nothing|Any|Unit|String|CharSequence|Int|Boolean|Char|Long|Double|Float|Short|Byte|dynamic)\b
scope: storage.type.buildin.kotlin
- match: \b(IntArray|BooleanArray|CharArray|LongArray|DoubleArray|FloatArray|ShortArray|ByteArray)\b
scope: storage.type.buildin.array.kotlin
- match: \b(Array|List|Map)<\b
- match: \b(Array|Collection|List|Map|Set|MutableList|MutableMap|MutableSet|Sequence)<\b
captures:
1: storage.type.buildin.collection.kotlin
push:
@ -333,13 +342,6 @@ contexts:
pop: true
- include: types
- include: keywords
- match: (#)\(
captures:
1: keyword.operator.tuple.kotlin
push:
- match: \)
pop: true
- include: expressions
- match: '\{'
push:
- match: '\}'
@ -353,15 +355,15 @@ contexts:
- match: (->)
scope: keyword.operator.declaration.kotlin
variables:
- match: (?=\s*(?:var|val))
- match: (?=\s*\b(?:var|val)\b)
push:
- match: (?=:|=|$)
- match: (?=:|=|(\b(by)\b)|$)
pop: true
- match: \b(var|val)\b
captures:
1: keyword.other.kotlin
push:
- match: (?=:|=|$)
- match: (?=:|=|(\b(by)\b)|$)
pop: true
- match: <
push:
@ -379,6 +381,13 @@ contexts:
pop: true
- include: types
- include: getters-and-setters
- match: \b(by)\b
captures:
1: keyword.other.kotlin
push:
- match: (?=$)
pop: true
- include: expressions
- match: (=)
captures:
1: keyword.operator.assignment.kotlin

@ -0,0 +1 @@
Subproject commit 3dd952ea771e5bc087a41146941ed36f2051c3c4

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

View file

@ -147,7 +147,7 @@ contexts:
2: keyword.other.powershell
3: variable.parameter.powershell
attribute:
- match: '(\[)\s*\b(?i)(cmdletbinding|alias|outputtype|parameter|validatenotnull|validatenotnullorempty|validatecount|validateset|allownull|allowemptycollection|allowemptystring|validatescript|validaterange|validatepattern|validatelength)\b'
- match: '(\[)\s*\b(?i)(cmdletbinding|alias|outputtype|parameter|validatenotnull|validatenotnullorempty|validatecount|validateset|allownull|allowemptycollection|allowemptystring|validatescript|validaterange|validatepattern|validatelength|supportswildcards)\b'
captures:
1: punctuation.section.bracket.begin.powershell
2: support.function.attribute.powershell
@ -165,31 +165,11 @@ contexts:
captures:
0: punctuation.section.group.end.powershell
pop: true
- include: variable
- include: variableNoProperty
- include: hashtable
- include: scriptblock
- include: doubleQuotedStringEscapes
- include: doubleQuotedString
- include: type
- include: numericConstant
- include: doubleQuotedString
- include: main
- match: (?i)\b(mandatory|valuefrompipeline|valuefrompipelinebypropertyname|valuefromremainingarguments|position|parametersetname|defaultparametersetname|supportsshouldprocess|supportspaging|positionalbinding|helpuri|confirmimpact|helpmessage)\b(?:\s+)?(=)?
captures:
1: variable.parameter.attribute.powershell
2: keyword.operator.assignment.powershell
- match: (?<!')'
captures:
0: punctuation.definition.string.begin.powershell
push:
- meta_scope: string.quoted.single.powershell
- match: "'(?!')"
captures:
0: punctuation.definition.string.end.powershell
pop: true
- match: "''"
scope: constant.character.escape.powershell
commands:
- match: '(?:(\p{L}|\d|_|-|\\|\:)*\\)?\b(?i:Add|Approve|Assert|Backup|Block|Build|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy|Debug|Deny|Deploy|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|Format|Get|Grant|Group|Hide|Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Mount|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push|Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke|Save|Search|Select|Send|Set|Show|Skip|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Test|Trace|Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Write)\-.+?(?:\.(?i:exe|cmd|bat|ps1))?\b'
comment: "Verb-Noun pattern:"
@ -242,14 +222,14 @@ contexts:
pop: true
- match: '(?i)\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,64}\b'
- include: variableNoProperty
- include: variable
- include: doubleQuotedStringEscapes
- include: interpolation
- match: '`\s*$'
scope: keyword.other.powershell
doubleQuotedStringEscapes:
- match: '`[0abnfrvt"''$`]'
- match: '`[`0abefnrtv"''$]'
scope: constant.character.escape.powershell
- include: unicodeEscape
- match: '""'
scope: constant.character.escape.powershell
function:
@ -362,6 +342,11 @@ contexts:
- match: '(?!\d+|\.)(?:\p{L}|\p{N}|\.)+'
scope: storage.type.powershell
- include: main
unicodeEscape:
- match: '`u\{(?:(?:10)?([0-9a-fA-F]){1,4}|0?{{1}}{1,5})}'
scope: constant.character.escape.powershell
- match: '`u(?:\{[0-9a-fA-F]{,6}.)?'
scope: invalid.character.escape.powershell
variable:
- match: (\$)(?i:(False|Null|True))\b
comment: These are special constants.
@ -374,13 +359,13 @@ contexts:
0: support.constant.variable.powershell
1: punctuation.definition.variable.powershell
3: variable.other.member.powershell
- match: '(\$)(?i:(\$|\^|\?|_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This))((?:\.(?:\p{L}|\d|_)+)*\b)?\b'
- match: '(\$)((?:[$^?])|(?i:_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This)\b)((?:\.(?:\p{L}|\d|_)+)*\b)?'
comment: Automatic variables are not constants, but they are read-only. In monokai (default) color schema support.variable doesn't have color, so we use constant.
captures:
0: support.constant.automatic.powershell
0: support.variable.automatic.powershell
1: punctuation.definition.variable.powershell
3: variable.other.member.powershell
- match: '(\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|ProgressPreference|PsCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|VerbosePreference|WarningPreference|WhatIfPreference))((?:\.(?:\p{L}|\d|_)+)*\b)?\b'
- match: '(\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|InformationPreference|LogCommandHealthEvent|LogCommandLifecycleEvent|LogEngineHealthEvent|LogEngineLifecycleEvent|LogProviderHealthEvent|LogProviderLifecycleEvent|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|PSCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoLoadingPreference|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|ProgressPreference|VerbosePreference|WarningPreference|WhatIfPreference))((?:\.(?:\p{L}|\d|_)+)*\b)?\b'
comment: Style preference variables as language variables so that they stand out.
captures:
0: variable.language.powershell
@ -426,19 +411,19 @@ contexts:
0: support.constant.variable.powershell
1: punctuation.definition.variable.powershell
3: variable.other.member.powershell
- match: (\$)(?i:(\$|\^|\?|_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This))\b
- match: '(\$)((?:[$^?])|(?i:_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This)\b)'
comment: Automatic variables are not constants, but they are read-only...
captures:
0: support.variable.automatic.powershell
1: punctuation.definition.variable.powershell
3: variable.other.member.powershell
- match: (\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|ProgressPreference|PsCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|VerbosePreference|WarningPreference|WhatIfPreference))\b
- match: (\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|InformationPreference|LogCommandHealthEvent|LogCommandLifecycleEvent|LogEngineHealthEvent|LogEngineLifecycleEvent|LogProviderHealthEvent|LogProviderLifecycleEvent|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|PSCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoLoadingPreference|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|ProgressPreference|VerbosePreference|WarningPreference|WhatIfPreference))\b
comment: Style preference variables as language variables so that they stand out.
captures:
0: variable.language.powershell
1: punctuation.definition.variable.powershell
3: variable.other.member.powershell
- match: '(?i:(\$|@)(global|local|private|script|using|workflow):((?:\p{L}|\d|_)+))'
- match: '(?i:(\$)(global|local|private|script|using|workflow):((?:\p{L}|\d|_)+))'
captures:
0: variable.other.readwrite.powershell
1: punctuation.definition.variable.powershell

View file

@ -0,0 +1,18 @@
# Sublime syntaxes
All the submodules are offering .sublime-syntax files. The ones that are not in a submodule
are converted from the following repos at the following commit:
- Assembly x86: https://github.com/Nessphoro/sublimeassembly/tree/150352f4d89d7fde48b18cc4b046385a6e926c96
- CSV: https://github.com/wadetb/Sublime-Text-Advanced-CSV/tree/4786d037a761bc45e516f6b0624a839919ec6d05
- Crystal: https://github.com/crystal-lang-tools/sublime-crystal/tree/2ee9d667aeb9e90846244b5a74e78826014676de
- Dart: https://github.com/LiteCatDev/Dartlight/tree/4a9abb5d480eec6b4b95fe6ff4667162a33d41d6
- Kotlin: https://github.com/vkostyukov/kotlin-sublime-package/tree/aeeed2780b04aea3d293c547c24cae27cafef0c5
- Nix: https://github.com/wmertens/sublime-nix/tree/9032bd613746b9c135223fd6f26a5fa555f18946
- PowerShell: https://github.com/PowerShell/EditorSyntax/tree/4a0a076661d26473cac71b9a17e6a759e9b1c643
- Swift: https://github.com/quiqueg/Swift-Sublime-Package/tree/fef17119ceef28a3c4b8cf72268d6192b595365d
- Reason: https://github.com/reasonml-editor/sublime-reason/tree/69925396ec6e73581357c0b1c487055fa6c065e8
- Stylus: https://github.com/billymoon/Stylus/tree/30908e3b5757d6cab4bf2ce660ef89b0c614cf62
- Racket: https://gist.githubusercontent.com/sliminality/3f1527f8e910c36b3303346422b03409/raw/9ce1ddd463c9bc97439726fd5f63b1fb3a36419b/Racket.tmLanguage
The others, I haven't kept track of the repo/commit.

View file

@ -0,0 +1,52 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: Racket
file_extensions:
- rkt
scope: source.racket
contexts:
main:
- match: '[^\\](\"[^\"]*\")'
captures:
1: string.quoted.double.source.racket
- match: '\((define)\s+([a-zA-Z0-9_\-?\+^:/!]+)\s*'
scope: meta.variable.source.racket
captures:
1: keyword.source.racket
2: entity.name.variable.source.racket
- match: '\((define)\s+\(([a-zA-Z0-9_\-?\+^:/!]+)\s*'
scope: meta.function.source.racket
captures:
1: keyword.source.racket
2: entity.name.function
- match: '\((struct)\s+([a-zA-Z0-9_\-?\+^]+)\s+'
scope: meta.struct.source.racket
captures:
1: keyword.source.racket
2: entity.name.type
- match: '[\s\(](if|lambda|cond|define|type-case|let|letrec|let!|\#lang|require|test|else|first|rest|define-type|define-type-alias|define-struct|not|local|error|lang|module|module*|module+|require|provide|quote|#%datum|#%expression|#%top|#%variable-reference|#%app|lambda|case-lambda|let|let*|letrec|let-values|let*-values|let-syntax|letrec-syntax|let-syntaxes|letrec-syntaxes|letrec-syntaxes+values|local|shared|if|cond|and|or|case|define|else|=>|define|define-values|define-syntax|define-syntaxes|define-for-syntax|define-require-syntax|define-provide-syntax|define-syntax-rule|define-record-type|begin|begin0|begin-for-syntax|when|unless|set!|set!-values|for|for/list|for/vector|for/hash|for/hasheq|for/hasheqv|for/and|for/or|for/lists|for/first|for/last|for/fold|for*|for*/list|for*/vector|for*/hash|for*/hasheq|for*/hasheqv|for*/and|for*/or|for*/lists|for*/first|for*/last|for*/fold|for/fold/derived|for*/fold/derived|define-sequence-syntax|:do-in|do|with-continuation-mark|quasiquote|unquote|unquote-splicing|quote-syntax|#%top-interaction|define-package|open-package|package-begin|define*|define*-values|define*-syntax|define*-syntaxes|open*-package|package?|package-exported-identifiers|package-original-identifiers|block|#%stratified-body|match|match*|match/values|define/match|match-lambda|match-lambda*|match-lambda**|match-let|match-let*|match-let-values|match-let*-values|match-letrec|match-define|match-define-values|with-handlers|with-handlers*|let/cc|let/ec|%|prompt|control|prompt-at|control-at|reset|shift|reset-at|shift-at|prompt0|reset0|control0|shift0|prompt0-at|reset0-at|control0-at|shift0-at|set|cupto|write|display|displayln|print|fprintf|printf|eprintf|format|print-pair-curly-braces|print-mpair-curly-braces|print-unreadable|print-graph|print-struct|print-box|print-vector-length|print-hash-table|print-boolean-long-form|print-reader-abbreviations|print-as-expression|print-syntax-width|current-write-relative-directory|port-write-handler|port-display-handler|port-print-handler|global-port-print-handler)[\s\)]'
scope: meta.keywords.source.racket
captures:
1: keyword.source.racket
- match: '[\s\(](true|false|empty|null)[\s\)]'
captures:
1: constant.language.source.racket
- match: '[\s\(\{\[](#t|#f|#true|#false)[\s\)\}\]]'
captures:
1: constant.language.source.racket
- match: '(#\\[a-zA-Z0-9_\-?\+\.\!\"]+)'
captures:
1: constant.language.source.racket
- match: '\b(0|([1-9][0-9_]*))\b'
scope: constant.numeric.integer.source.racket
- match: ;
push:
- meta_scope: comment.line.documentation.source.racket
- match: $\n
pop: true
- match: '#\|'
push:
- meta_scope: comment.block.source.racket
- match: '\|#'
pop: true

View file

@ -10,6 +10,7 @@ contexts:
main:
- include: reason_comment_doc_block
- include: reason_comment_block
- include: reason_comment
- include: reason_named_arg
- include: reason_module
- include: reason_lifetime
@ -102,6 +103,12 @@ contexts:
scope: storage.modifier.rec.source.reason
- match: \bmodule\b
scope: storage.modifier.module.source.reason
reason_comment:
- match: //
push:
- meta_scope: comment.source.reason
- match: (?=^)
pop: true
reason_comment_block:
- match: /\*
push:

View file

@ -0,0 +1,711 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: Stylus
file_extensions:
- styl
- stylus
scope: source.stylus
contexts:
main:
- include: comments
- match: '^\s*(@(?:import|charset|css|font-face|(?:-webkit-)?keyframes)(?:\s+([\w-]+))?)\b'
captures:
1: keyword.control.at-rule.other.stylus
2: variable.other.animation-name.stylus
push:
- match: '$|;|(?=\{)'
pop: true
- include: string-quoted
- match: ^\s*(@media)\s*
captures:
1: keyword.control.at-rule.media.stylus
push:
- match: '$|(?=\{)'
pop: true
- include: media-query
- match: |-
(?x)
(?<=^|;|})
\s*
(?=
[\[\]'".\w$-]+
\s*
([?:]?=)
(?![^\[]*\])
)
push:
- match: $|;
pop: true
- include: expression
- include: iteration
- include: conditionals
- include: return
- match: |-
(?x) # multi-line regex definition mode
^(\s*) # starts at the beginning of line
([\w$-]+) # identifier (name)
(\() # start of argument list
(?=
.*?
\)\s*\{ # we see a curly brace afterwards
) # which means this is a function definition
captures:
2: entity.name.function.stylus
3: punctuation.definition.parameters.start.stylus
push:
- meta_scope: meta.function-call.stylus
- match: (\))
captures:
1: punctuation.definition.parameters.end.stylus
pop: true
- include: expression
- match: |-
(?x) # multi-line regex definition mode
(
(^|;) # starts at the beginning of line or at a ;
\s*
(\+?\s* # for block mixins
[\w$-]+) # identifier (name)
(\() # start of argument list
(?=
.*?
\)\s*;?\s* # if there are only spaces and semicolons
$|; # then this a
)
)
captures:
3: entity.other.attribute-name.mixin.stylus
4: punctuation.definition.parameters.start.stylus
push:
- meta_scope: meta.function-call.stylus
- match: (\))
captures:
1: punctuation.definition.parameters.end.stylus
pop: true
- include: expression
- match: |-
(?x) # multi-line regex definition mode
(^|(?<=\*/|\}))\s*
(?=
font(?!
\s*:\s
|
-
|
.*?
(?:
\/|normal|bold|light(er?)|serif|sans|monospace|
\b\d+(?:\b|px|r?em|%)|
var\s*\(|
['"][^\]]*$
)
) | # we need to distinguish between tag and property `cursor`
cursor(?!
\s*[:;]\s
|
-
|
.*?
(?:
(?:url\s*\()|
(?:-moz-|-webkit-|-ms-)?
(?:auto|default|none|context-menu|help|pointer|progress|
wait|cell|crosshair|text|vertical-text|alias|copy|
move|no-drop|not-allowed|e-resize|n-resize|ne-resize|
nw-resize|s-resize|se-resize|sw-resize|w-resize|
ew-resize|ns-resize|nesw-resize|nwse-resize|col-resize|
row-resize|all-scroll|zoom-in|zoom-out|grab|grabbing
normal|bold|light(er?)|serif|sans|monospace)
)
) | (
(
altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|
animateMotion|animateTransform|circle|clipPath|color-profile|
defs|desc|ellipse|feBlend|feColorMatrix|
feComponentTransfer|feComposite|feConvolveMatrix|
feDiffuseLighting|feDisplacementMap|feDistantLight|feFlood|
feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|
feMergeNode|feMorphology|feOffset|fePointLight|
feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|
font-face|font-face-format|font-face-name|font-face-src|
font-face-uri|foreignObject|g|glyph|glyphRef|hkern|image|line|
linearGradient|marker|mask|metadata|missing-glyph|mpath|path|
pattern|polygon|polyline|radialGradient|rect|set|stop|svg|
switch|symbol|text|textPath|tref|tspan|use|view|vkern|
a|abbr|acronym|address|applet|area|article|aside|audio|b|base|
basefont|bdi|bdo|bgsound|big|blink|blockquote|body|br|button|
canvas|caption|center|cite|code|col|colgroup|data|
datalist|dd|decorator|del|details|dfn|dir|div|dl|dt|element|
em|embed|fieldset|figcaption|figure|footer|form|frame|
frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|
img|input|ins|isindex|kbd|keygen|label|legend|li|link|listing|
main|map|mark|marquee|menu|menuitem|meta|meter|nav|nobr|
noframes|noscript|object|ol|optgroup|option|output|p|param|
plaintext|pre|progress|q|rp|rt|ruby|s|samp|script|section|
select|shadow|small|source|spacer|span|strike|strong|style|
sub|summary|sup|table|tbody|td|template|textarea|tfoot|th|
thead|time|title|tr|track|tt|u|ul|var|video|wbr|xmp)
\s*([\s,.#\[]|:[^\s]|(?=\{|$))
) | (
[:~>\[*\/] # symbols but they are valid for selector
) | (
\+\s*[\w$-]+\b\s* # are an identifier starting with $
(?!\() # and they can't have anything besides
) | ( # for animtions
\d+(\.\d+)?%|(from|to)\b
) | ( # Placeholder selectors
\$[\w$-]+\b\s* # are an identifier starting with $
(?=$|\{) # and they can't have anything besides
) | ( # CSS class
\.[a-zA-Z0-9_-]+
) | ( # CSS id
\#[a-zA-Z0-9_-]+
) | ( # Reference to parent
([\w\d_-]+)? # matching any word right before &
(&) # & itself, escaped because of plist
([\w\d_-]+)? # matching any word right after &
)
)
push:
- meta_scope: meta.selector.stylus
- match: |-
|$|(?=\{\s*\}.*$)|(?=\{.*?[:;])|(?=\{)(?!.+\}.*$)
pop: true
- include: comma
- match: \d+(\.\d+)?%|from|to
scope: entity.other.animation-keyframe.stylus
- include: selector-components
- match: .
scope: entity.other.attribute-name.stylus
- match: |-
(?x) # multi-line regex definition mode
(?<=^|;|{)\s* # starts after begining of line, '{' or ';''
(?= # lookahead for
(
[a-zA-Z0-9_-] # then a letter
| # or
(\{(.*?)\}) # interpolation
| # or
(/\*.*?\*/) # comment
)+
\s*[:\s]\s* # value is separted by colon or space
(?!(\s*\{)) # if there are only spaces afterwards
(?!
[^}]*? # checking for an unclosed curly braces on this
\{ # line because if one exists it means that
[^}]* # this is a selector and not a property
($|\})
)
)
push:
- match: '(?=\}|;)|(?<!,)\s*\n'
pop: true
- include: comments
- include: interpolation
- match: '(?<!^|;|{)\s*(?:(:)|\s)'
captures:
1: punctuation.separator.key-value.stylus
push:
- match: '(;)|(?=\})|(?=(?<!\,)\s*\n)'
captures:
1: punctuation.terminator.rule.stylus
pop: true
- include: comments
- include: expression
- match: "-(moz|o|ms|webkit|khtml)-"
scope: support.type.vendor-prefix.stylus
- match: .
scope: meta.property-name.stylus support.type.property-name.stylus
- match: '@extends?\s'
captures:
0: keyword.language.stylus
push:
- match: (?=$|;)
pop: true
- include: selector-components
- include: string-quoted
- include: escape
- include: language-constants
- include: language-operators
- include: language-keywords
- include: property-reference
- include: function-call
- match: '\{'
scope: punctuation.section.start.stylus
- match: '\}'
scope: punctuation.section.end.stylus
attribute-selector:
- match: '\[(?=[^\]]*\])'
captures:
0: punctuation.definition.entity.start.stylus
push:
- meta_scope: meta.attribute-selector.stylus
- match: '\]'
captures:
0: punctuation.definition.entity.end.stylus
pop: true
- match: '(?<=\[)|(?<=\{)'
push:
- match: '(?=[|~=\]\s])'
pop: true
- include: interpolation
- match: .
captures:
0: entity.other.attribute-name.stylus
- include: interpolation
- match: "([|~]?=)"
captures:
1: keyword.operator.stylus
- include: string-quoted
- match: .
captures:
0: string.unquoted.stylus
color-values:
- match: \b(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)\b
scope: constant.color.w3c-standard-color-name.stylus
- match: (hsla?|rgba?)\s*(\()
captures:
1: keyword.language.function.misc.stylus
2: punctuation.definition.parameters.start.stylus
push:
- match: \)
captures:
0: punctuation.definition.parameters.end.stylus
pop: true
- match: |-
(?x) # multi-line regex definition mode
\b
(?:0*((?:1?[0-9]{1,2})|(?:2(?:[0-4][0-9]|5[0-5])))\s*(,)\s*)
(?:0*((?:1?[0-9]{1,2})|(?:2(?:[0-4][0-9]|5[0-5])))\s*(,)\s*)
(?:0*((?:1?[0-9]{1,2})|(?:2(?:[0-4][0-9]|5[0-5])))\b)
captures:
1: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.red.stylus
2: punctuation.delimiter.comma.stylus
3: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.green.stylus
4: punctuation.delimiter.comma.stylus
5: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.blue.stylus
- match: |-
(?x) # multi-line regex definition mode
\b
((?:[0-9]{1,2}|100)%)(,) # red
\s*
((?:[0-9]{1,2}|100)%)(,) # green
\s*
((?:[0-9]{1,2}|100)%) # blue
captures:
1: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.red.stylus
2: punctuation.delimiter.comma.stylus
3: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.green.stylus
4: punctuation.delimiter.comma.stylus
5: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.blue.stylus
- match: |-
(?x) # multi-line regex definition mode
(?:\s*(,)\s*((0?\.[0-9]+)|[0-1]))?
captures:
1: punctuation.delimiter.comma.stylus
2: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.alpha.stylus
- include: numeric-values
- include: numeric-values
comma:
- match: \s*,\s*
scope: punctuation.delimiter.comma.stylus
comments:
- include: single-line-comment
- match: \/\*
captures:
0: punctuation.definition.comment.stylus
push:
- meta_scope: comment.block.stylus
- match: \*\/
captures:
0: punctuation.definition.comment.stylus
pop: true
conditionals:
- match: '(^\s*|\s+)(if|unless|else)(?=[\s({]|$)\s*'
captures:
2: keyword.control.stylus
push:
- match: '(?=$|\{)'
pop: true
- include: expression
escape:
- match: \\.
scope: constant.character.escape.stylus
expression:
- include: single-line-comment
- include: comma
- include: iteration
- include: conditionals
- include: language-operators
- include: language-keywords
- include: hash-definition
- include: color-values
- include: url
- include: function-call
- include: string-quoted
- include: escape
- include: hash-access
- include: language-constants
- include: language-property-value-constants
- include: property-reference
- include: variable
function-call:
- match: '([\w$-]+)(\()'
captures:
1: entity.function-name.stylus
2: punctuation.definition.parameters.start.stylus
push:
- meta_scope: meta.function-call.stylus
- match: (\))
captures:
1: punctuation.definition.parameters.end.stylus
pop: true
- include: expression
hash-access:
- match: '(?=[\w$-]+(?:\.|\[[^\]=]*\]))'
push:
- meta_scope: meta.hash-access.stylus
- match: '(?=[^''''""\[\]\w.$-]|\s|$)'
pop: true
- match: \.
scope: punctuation.delimiter.hash.stylus
- match: '\['
scope: punctuation.definition.entity.start.stylus
- match: '\]'
scope: punctuation.definition.entity.end.stylus
- include: string-quoted
- include: variable
hash-definition:
- match: '\{'
captures:
0: punctuation.section.embedded.start.stylus
push:
- meta_scope: meta.hash.stylus
- match: '\}'
captures:
0: punctuation.section.embedded.end.stylus
pop: true
- include: single-line-comment
- match: |-
(?x)
(?:
([\w$-]+)
|
('[^']*')
|
("[^"]*")
)
\s*
(:)
\s*
captures:
1: support.type.property-name.stylus
2: string.quoted.single.stylus
3: string.quoted.double.stylus
4: punctuation.separator.key-value.stylus
push:
- match: '(;)|(?=\}|$)'
captures:
1: punctuation.terminator.statement.stylus
pop: true
- include: expression
interpolation:
- match: '\{'
captures:
0: punctuation.section.embedded.start.stylus
push:
- meta_scope: stylus.embedded.source
- match: '\}'
captures:
0: punctuation.section.embedded.end.stylus
pop: true
- include: expression
iteration:
- match: (^\s*|\s+)(for)\s+(?=.*?\s+in\s+)
captures:
2: keyword.control.stylus
push:
- match: '$|\{'
pop: true
- include: expression
language-constants:
- match: \b(true|false|null)\b
scope: constant.language.stylus
language-keywords:
- match: (\b|\s)(return|else)\b
scope: keyword.control.stylus
- match: (\b|\s)(!important|in|is defined|is a)\b
scope: keyword.other.stylus
- match: \barguments\b
scope: variable.language.stylus
language-operators:
- match: ((?:\?|:|!|~|\+|-|(?:\*)?\*|\/|%|(\.)?\.\.|<|>|(?:=|:|\?|\+|-|\*|\/|%|<|>)?=|!=)|\b(?:in|is(?:nt)?|(?<!:)not)\b)
scope: keyword.operator.stylus
language-property-value-constants:
- match: \b(absolute|all(-scroll)?|always|armenian|auto|avoid|baseline|below|bidi-override|block|bold(er)?|(border|content|padding)-box|both|bottom|break-all|break-word|capitalize|center|char|circle|cjk-ideographic|col-resize|collapse|crosshair|cursive|dashed|decimal-leading-zero|decimal|default|disabled|disc|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ellipsis|fantasy|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|monospace|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|(sans-)?serif|s-resize|scroll|se-resize|separate|small-caps|solid|square|static|strict|sub|super|sw-resize|table(-(row|cell|footer-group|header-group))?|tb-rl|text-bottom|text-top|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(er)?|large(r)?))|painted|fill|stroke)\b
scope: constant.property-value.stylus
media-query:
- match: '\s*(?![{;]|$)'
push:
- meta_scope: meta.at-rule.media.stylus
- match: '\s*(?=[{;]|$)'
pop: true
- match: '(?i)\s*(only|not)?\s*(all|aural|braille|embossed|handheld|print|projection|screen|tty|tv)?(?![\w\d$-]+)'
captures:
1: keyword.operator.logic.media.stylus
2: support.constant.media.stylus
push:
- match: '\s*(?:(,)|(?=[{;]|$))'
pop: true
- include: media-query-list
- include: variable
media-query-list:
- match: \s*(and)?\s*(\()\s*
captures:
1: keyword.operator.logic.media.stylus
2: punctuation.definition.parameters.start.stylus
push:
- match: \)
captures:
0: punctuation.definition.parameters.end.stylus
pop: true
- include: media-query-properties
- include: numeric-values
media-query-properties:
- match: \s*:\s*
captures:
0: punctuation.separator.key-value.stylus
- match: |-
(?x)
(
((min|max)-)?
(
((device-)?(height|width|aspect-ratio))|
(color(-index)?)|monochrome|resolution
)
)|grid|scan|orientation
captures:
0: support.type.property-name.media.stylus
- match: \b(portrait|landscape|progressive|interlace)\b
captures:
1: support.constant.property-value.stylus
numeric-values:
- match: |-
(?x) # multi-line regex definition mode
(\#)(?:
([0-9a-fA-F])
([0-9a-fA-F])
([0-9a-fA-F])
([0-9a-fA-F])?
| ([0-9a-fA-F]{2})
([0-9a-fA-F]{2})
([0-9a-fA-F]{2})
([0-9a-fA-F]{2})?
)\b
scope: constant.other.color.rgb-value.stylus
captures:
1: punctuation.definition.constant.stylus
2: constant.other.color.rgb-value.red.stylus
3: constant.other.color.rgb-value.green.stylus
4: constant.other.color.rgb-value.blue.stylus
5: constant.other.color.rgb-value.alpha.stylus
6: constant.other.color.rgb-value.red.stylus
7: constant.other.color.rgb-value.green.stylus
8: constant.other.color.rgb-value.blue.stylus
9: constant.other.color.rgb-value.alpha.stylus
- match: |-
(?x) # multi-line regex definition mode
(?:-|\+)? # negative / positive
(?:
(?:
[0-9]+ # integer part
(?:\.[0-9]+)? # fraction
) |
(?:\.[0-9]+) # fraction without leading zero
)
((?: # units
px|pt|ch|cm|mm|in|
r?em|ex|pc|vw|vh|vmin|vmax|deg|
g?rad|turn|dpi|dpcm|dppx|m?s|k?Hz
)\b|%)?
scope: constant.numeric.stylus
captures:
1: keyword.other.unit.stylus
property-reference:
- match: "@[a-z-]+"
scope: variable.other.property.stylus
pseudo:
- match: (:)(active|checked|default|disabled|empty|enabled|first-child|first-of-type|first|fullscreen|focus|hover|indeterminate|in-range|invalid|last-child|last-of-type|left|link|only-child|only-of-type|optional|out-of-range|read-only|read-write|required|right|root|scope|target|valid|visited)\b
scope: entity.other.attribute-name.pseudo-class.stylus
captures:
1: puncutation.definition.entity.stylus
- match: (:?:)(before|after)\b
scope: entity.other.attribute-name.pseudo-element.stylus
captures:
1: puncutation.definition.entity.stylus
- match: (::)(first-letter|first-number|selection)\b
scope: entity.other.attribute-name.pseudo-element.stylus
captures:
1: puncutation.definition.entity.stylus
- match: ((:)dir)\s*(?:(\()(ltr|rtl)?(\)))?
captures:
1: entity.other.attribute-name.pseudo-element.stylus
2: puncutation.definition.entity.stylus
3: puncutation.definition.parameters.start.stylus
4: constant.language.stylus
5: puncutation.definition.parameters.end.stylus
- match: ((:)lang)\s*(?:(\()(\w+(-\w+)?)?(\)))?
captures:
1: entity.other.attribute-name.pseudo-element.stylus
2: puncutation.definition.entity.stylus
3: puncutation.definition.parameters.start.stylus
4: constant.language.stylus
5: puncutation.definition.parameters.end.stylus
- include: pseudo-nth
- include: pseudo-not
pseudo-not:
- match: ((:)not)\s*(\()
captures:
1: entity.other.attribute-name.pseudo-element.stylus
2: puncutation.definition.entity.stylus
3: puncutation.definition.parameters.start.stylus
push:
- match: \)
captures:
0: puncutation.definition.parameters.end.stylus
pop: true
- include: selector-components
pseudo-nth:
- match: ((:)(?:nth-child|nth-last-child|nth-of-type|nth-last-of-type|nth-match|nth-last-match|nth-column|nth-last-column))\s*(\()
captures:
1: entity.other.attribute-name.pseudo-class.stylus
2: puncutation.definition.entity.stylus
3: puncutation.definition.parameters.start.stylus
push:
- match: \)
captures:
0: puncutation.definition.parameters.end.stylus
pop: true
- include: language-operators
- include: interpolation
- match: \b(odd|even)\b
scope: constant.language.stylus
- match: \b(\d+)?n\b
scope: variable.language.stylus
captures:
1: constant.numeric.stylus
- match: \d+
scope: constant.numeric.stylus
return:
- match: ^\s*(return)
captures:
1: keyword.control.stylus
push:
- match: (;)|(?=$)
captures:
1: punctuation.terminator.statement.stylus
pop: true
- include: expression
selector-components:
- include: comments
- include: interpolation
- include: attribute-selector
- include: pseudo
- match: '\$[\w$-]+\b'
scope: entity.other.placeholder.stylus
- match: "[:~>]"
scope: keyword.operator.selector.stylus
- match: |-
(?x) # multi-line regex definition mode
\b(
altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|
animateMotion|animateTransform|circle|clipPath|color-profile|
defs|desc|ellipse|feBlend|feColorMatrix|
feComponentTransfer|feComposite|feConvolveMatrix|
feDiffuseLighting|feDisplacementMap|feDistantLight|feFlood|
feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|
feMergeNode|feMorphology|feOffset|fePointLight|
feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|
font-face|font-face-format|font-face-name|font-face-src|
font-face-uri|foreignObject|g|glyph|glyphRef|hkern|image|line|
linearGradient|marker|mask|metadata|missing-glyph|mpath|path|
pattern|polygon|polyline|radialGradient|rect|set|stop|svg|
switch|symbol|text|textPath|tref|tspan|use|view|vkern|
a|abbr|acronym|address|applet|area|article|aside|audio|b|base|
basefont|bdi|bdo|bgsound|big|blink|blockquote|body|br|button|
canvas|caption|center|cite|code|col|colgroup|content|data|
datalist|dd|decorator|del|details|dfn|dir|div|dl|dt|element|
em|embed|fieldset|figcaption|figure|font|footer|form|frame|
frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|
img|input|ins|isindex|kbd|keygen|label|legend|li|link|listing|
main|map|mark|marquee|menu|menuitem|meta|meter|nav|nobr|
noframes|noscript|object|ol|optgroup|option|output|p|param|
plaintext|pre|progress|q|rp|rt|ruby|s|samp|script|section|
select|shadow|small|source|spacer|span|strike|strong|style|
sub|summary|sup|table|tbody|td|template|textarea|tfoot|th|
thead|time|title|tr|track|tt|u|ul|var|video|wbr|xmp
)\b
scope: entity.name.tag.stylus
- match: '\.[a-zA-Z0-9_-]+'
scope: entity.other.attribute-name.class.stylus
- match: "#[a-zA-Z0-9_-]+"
scope: entity.other.attribute-name.id.stylus
- match: |-
(?x) # multi-line regex definition mode
([\w\d_-]+)? # matching any word right before &
(&) # & itself, escaped because of plist
([\w\d_-]+)? # matching any word right after &
captures:
1: entity.other.attribute-name.stylus
2: variable.language.stylus
3: entity.other.attribute-name.stylus
single-line-comment:
- match: (\/\/).*$
scope: comment.line.stylus
captures:
1: punctuation.definition.comment.stylus
string-quoted:
- match: "'[^']*'"
scope: string.quoted.single.stylus
- match: '"[^"]*"'
scope: string.quoted.double.stylus
url:
- match: (url)\s*(\()
captures:
1: entity.function-name.stylus
2: punctuation.definition.parameters.start.stylus
push:
- meta_scope: meta.function-call.stylus
- match: (\))
captures:
1: punctuation.definition.parameters.end.stylus
pop: true
- include: string-quoted
- include: language-constants
- include: language-property-value-constants
- include: property-reference
- include: variable
variable:
- match: '([\w$-]+\b)'
scope: variable.other.stylus

@ -0,0 +1 @@
Subproject commit 926b6818067c741d1d5cd9bfe901954fc23eb049

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

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

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

@ -0,0 +1 @@
Subproject commit 041d15667eca429afd4ff3df3b8f8617a66fc410

Some files were not shown because too many files have changed in this diff Show more