commit
1b237dc466
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,3 @@
|
||||||
target
|
target
|
||||||
.idea/
|
.idea/
|
||||||
site
|
test_site/public
|
||||||
theme
|
|
||||||
|
|
10
.travis.yml
Normal file
10
.travis.yml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
language: rust
|
||||||
|
cache: cargo
|
||||||
|
|
||||||
|
rust:
|
||||||
|
- nightly
|
||||||
|
- beta
|
||||||
|
- stable
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
685
Cargo.lock
generated
685
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
27
Cargo.toml
27
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gutenberg"
|
name = "gutenberg"
|
||||||
version = "0.1.0"
|
version = "0.0.1"
|
||||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
|
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -9,8 +9,11 @@ homepage = "https://github.com/Keats/gutenberg"
|
||||||
repository = "https://github.com/Keats/gutenberg"
|
repository = "https://github.com/Keats/gutenberg"
|
||||||
keywords = ["static", "site", "generator", "blog"]
|
keywords = ["static", "site", "generator", "blog"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "gutenberg"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
error-chain = "0.9"
|
error-chain = "0.10"
|
||||||
clap = "2.19"
|
clap = "2.19"
|
||||||
walkdir = "1"
|
walkdir = "1"
|
||||||
pulldown-cmark = "0"
|
pulldown-cmark = "0"
|
||||||
|
@ -20,12 +23,20 @@ glob = "0.2"
|
||||||
serde = "0.9"
|
serde = "0.9"
|
||||||
serde_json = "0.9"
|
serde_json = "0.9"
|
||||||
serde_derive = "0.9"
|
serde_derive = "0.9"
|
||||||
tera = { git = "https://github.com/Keats/tera", branch = "next" }
|
# tera = { path = "../tera" }
|
||||||
|
# tera = { git = "https://github.com/Keats/tera", branch = "reload" }
|
||||||
|
tera = "0.8"
|
||||||
|
slug = "0.1"
|
||||||
syntect = "1"
|
syntect = "1"
|
||||||
|
chrono = "0.3"
|
||||||
|
toml = { version = "0.3", default-features = false, features = ["serde"]}
|
||||||
|
|
||||||
[dependencies.toml]
|
# Below is for the serve cmd
|
||||||
version = "0.3"
|
staticfile = "0.4"
|
||||||
default-features = false
|
iron = "0.5"
|
||||||
features = ["serde"]
|
mount = "0.3"
|
||||||
|
notify = "4"
|
||||||
|
ws = "0.6"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempdir = "0.3"
|
||||||
|
|
|
@ -32,8 +32,12 @@ Split the file between front matter and content
|
||||||
Parse the front matter
|
Parse the front matter
|
||||||
markdown -> HTML for the content
|
markdown -> HTML for the content
|
||||||
|
|
||||||
|
### Themes
|
||||||
|
Gallery at https://tmtheme-editor.herokuapp.com/#!/editor/theme/Agola%20Dark
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
|
|
||||||
|
- find a way to add tests
|
||||||
- syntax highlighting
|
- syntax highlighting
|
||||||
- pass a --config arg to the CLI to change from `config.toml`
|
- pass a --config arg to the CLI to change from `config.toml`
|
||||||
- have verbosity levels with a `verbosity` config variable with a default
|
- have verbosity levels with a `verbosity` config variable with a default
|
||||||
|
|
|
@ -1,87 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use std::env;
|
||||||
use std::fs::{create_dir, remove_dir_all};
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use glob::glob;
|
use gutenberg::errors::Result;
|
||||||
use tera::{Tera, Context};
|
use gutenberg::Site;
|
||||||
|
|
||||||
use config:: Config;
|
|
||||||
use errors::{Result, ResultExt};
|
|
||||||
use page::{Page, order_pages};
|
|
||||||
use utils::create_file;
|
|
||||||
|
|
||||||
|
|
||||||
|
pub fn build() -> Result<()> {
|
||||||
pub fn build(config: Config) -> Result<()> {
|
Site::new(env::current_dir().unwrap())?.build()
|
||||||
if Path::new("public").exists() {
|
|
||||||
// Delete current `public` directory so we can start fresh
|
|
||||||
remove_dir_all("public").chain_err(|| "Couldn't delete `public` directory")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tera = Tera::new("templates/**/*").chain_err(|| "Error parsing templates")?;
|
|
||||||
|
|
||||||
// ok we got all the pages HTML, time to write them down to disk
|
|
||||||
create_dir("public")?;
|
|
||||||
let public = Path::new("public");
|
|
||||||
let mut pages: Vec<Page> = vec![];
|
|
||||||
let mut sections: HashMap<String, Vec<Page>> = HashMap::new();
|
|
||||||
|
|
||||||
// First step: do all the articles and group article by sections
|
|
||||||
// hardcoded pattern so can't error
|
|
||||||
for entry in glob("content/**/*.md").unwrap().filter_map(|e| e.ok()) {
|
|
||||||
let path = entry.as_path();
|
|
||||||
let mut page = Page::from_file(&path)?;
|
|
||||||
|
|
||||||
let mut current_path = public.to_path_buf();
|
|
||||||
|
|
||||||
for section in &page.sections {
|
|
||||||
current_path.push(section);
|
|
||||||
|
|
||||||
if !current_path.exists() {
|
|
||||||
create_dir(¤t_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let str_path = current_path.as_path().to_string_lossy().to_string();
|
|
||||||
sections.entry(str_path).or_insert_with(|| vec![page.clone()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref url) = page.meta.url {
|
|
||||||
println!("URL: {:?}", url);
|
|
||||||
current_path.push(url);
|
|
||||||
} else {
|
|
||||||
println!("REMOVE ME IF YOU DONT SEE ME");
|
|
||||||
current_path.push(&page.get_slug());
|
|
||||||
}
|
|
||||||
|
|
||||||
create_dir(¤t_path)?;
|
|
||||||
create_file(current_path.join("index.html"), &page.render_html(&tera, &config)?)?;
|
|
||||||
pages.push(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (section, pages) in sections {
|
|
||||||
render_section_index(section, pages, &tera, &config)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// and now the index page
|
|
||||||
let mut context = Context::new();
|
|
||||||
context.add("pages", &order_pages(pages));
|
|
||||||
context.add("config", &config);
|
|
||||||
create_file(public.join("index.html"), &tera.render("index.html", &context)?)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn render_section_index(section: String, pages: Vec<Page>, tera: &Tera, config: &Config) -> Result<()> {
|
|
||||||
let path = Path::new(§ion);
|
|
||||||
let mut context = Context::new();
|
|
||||||
context.add("pages", &order_pages(pages));
|
|
||||||
context.add("config", &config);
|
|
||||||
|
|
||||||
let section_name = match path.components().into_iter().last() {
|
|
||||||
Some(s) => s.as_ref().to_string_lossy().to_string(),
|
|
||||||
None => bail!("Couldn't find a section name in {:?}", path.display())
|
|
||||||
};
|
|
||||||
|
|
||||||
create_file(path.join("index.html"), &tera.render(&format!("{}.html", section_name), &context)?)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
use std::fs::{create_dir};
|
use std::fs::{create_dir};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use errors::Result;
|
use gutenberg::errors::Result;
|
||||||
use utils::create_file;
|
use gutenberg::create_file;
|
||||||
|
|
||||||
|
|
||||||
const CONFIG: &'static str = r#"
|
const CONFIG: &'static str = r#"
|
||||||
|
|
1175
src/cmd/livereload.js
Normal file
1175
src/cmd/livereload.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,7 @@
|
||||||
mod init;
|
mod init;
|
||||||
mod build;
|
mod build;
|
||||||
|
mod serve;
|
||||||
|
|
||||||
pub use self::init::create_new_project;
|
pub use self::init::create_new_project;
|
||||||
pub use self::build::build;
|
pub use self::build::build;
|
||||||
|
pub use self::serve::serve;
|
||||||
|
|
245
src/cmd/serve.rs
Normal file
245
src/cmd/serve.rs
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
use std::time::{Instant, Duration};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use iron::{Iron, Request, IronResult, Response, status};
|
||||||
|
use mount::Mount;
|
||||||
|
use staticfile::Static;
|
||||||
|
use notify::{Watcher, RecursiveMode, watcher};
|
||||||
|
use ws::{WebSocket, Sender};
|
||||||
|
use gutenberg::Site;
|
||||||
|
use gutenberg::errors::{Result};
|
||||||
|
|
||||||
|
|
||||||
|
use ::report_elapsed_time;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum ChangeKind {
|
||||||
|
Content,
|
||||||
|
Templates,
|
||||||
|
StaticFiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
const LIVE_RELOAD: &'static str = include_str!("livereload.js");
|
||||||
|
|
||||||
|
|
||||||
|
fn livereload_handler(_: &mut Request) -> IronResult<Response> {
|
||||||
|
Ok(Response::with((status::Ok, LIVE_RELOAD.to_string())))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &str) {
|
||||||
|
match res {
|
||||||
|
Ok(_) => {
|
||||||
|
broadcaster.send(format!(r#"
|
||||||
|
{{
|
||||||
|
"command": "reload",
|
||||||
|
"path": "{}",
|
||||||
|
"originalPath": "",
|
||||||
|
"liveCSS": true,
|
||||||
|
"liveImg": true,
|
||||||
|
"protocol": ["http://livereload.com/protocols/official-7"]
|
||||||
|
}}"#, reload_path)
|
||||||
|
).unwrap();
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to build the site");
|
||||||
|
println!("Error: {}", e);
|
||||||
|
for e in e.iter().skip(1) {
|
||||||
|
println!("Reason: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Most of it taken from mdbook
|
||||||
|
pub fn serve(interface: &str, port: &str) -> Result<()> {
|
||||||
|
println!("Building site...");
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut site = Site::new(env::current_dir().unwrap())?;
|
||||||
|
site.enable_live_reload();
|
||||||
|
site.build()?;
|
||||||
|
report_elapsed_time(start);
|
||||||
|
|
||||||
|
let address = format!("{}:{}", interface, port);
|
||||||
|
let ws_address = format!("{}:{}", interface, "1112");
|
||||||
|
|
||||||
|
// Start a webserver that serves the `public` directory
|
||||||
|
let mut mount = Mount::new();
|
||||||
|
mount.mount("/", Static::new(Path::new("public/")));
|
||||||
|
mount.mount("/livereload.js", livereload_handler);
|
||||||
|
// Starts with a _ to not trigger the unused lint
|
||||||
|
// we need to assign to a variable otherwise it will block
|
||||||
|
let _iron = Iron::new(mount).http(address.as_str()).unwrap();
|
||||||
|
println!("Web server is available at http://{}", address);
|
||||||
|
|
||||||
|
// The websocket for livereload
|
||||||
|
let ws_server = WebSocket::new(|_| {
|
||||||
|
|_| {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}).unwrap();
|
||||||
|
let broadcaster = ws_server.broadcaster();
|
||||||
|
thread::spawn(move || {
|
||||||
|
ws_server.listen(&*ws_address).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// And finally watching/reacting on file changes
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
|
||||||
|
watcher.watch("content/", RecursiveMode::Recursive).unwrap();
|
||||||
|
watcher.watch("static/", RecursiveMode::Recursive).unwrap();
|
||||||
|
watcher.watch("templates/", RecursiveMode::Recursive).unwrap();
|
||||||
|
let pwd = format!("{}", env::current_dir().unwrap().display());
|
||||||
|
println!("Listening for changes in {}/{{content, static, templates}}", pwd);
|
||||||
|
println!("Press CTRL+C to stop\n");
|
||||||
|
|
||||||
|
use notify::DebouncedEvent::*;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// See https://github.com/spf13/hugo/blob/master/commands/hugo.go
|
||||||
|
// for a more complete version of that
|
||||||
|
match rx.recv() {
|
||||||
|
Ok(event) => {
|
||||||
|
match event {
|
||||||
|
Create(path) |
|
||||||
|
Write(path) |
|
||||||
|
Remove(path) |
|
||||||
|
Rename(_, path) => {
|
||||||
|
if is_temp_file(&path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Change detected @ {}", Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
|
||||||
|
let start = Instant::now();
|
||||||
|
match detect_change_kind(&pwd, &path) {
|
||||||
|
(ChangeKind::Content, _) => {
|
||||||
|
println!("-> Content changed {}", path.display());
|
||||||
|
// Force refresh
|
||||||
|
rebuild_done_handling(&broadcaster, site.rebuild_after_content_change(), "/x.js");
|
||||||
|
},
|
||||||
|
(ChangeKind::Templates, _) => {
|
||||||
|
println!("-> Template changed {}", path.display());
|
||||||
|
// Force refresh
|
||||||
|
rebuild_done_handling(&broadcaster, site.rebuild_after_template_change(), "/x.js");
|
||||||
|
},
|
||||||
|
(ChangeKind::StaticFiles, p) => {
|
||||||
|
println!("-> Static file changes detected {}", path.display());
|
||||||
|
rebuild_done_handling(&broadcaster, site.copy_static_directory(), &p);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
report_elapsed_time(start);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => println!("Watch error: {:?}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Returns whether the path we received corresponds to a temp file create
|
||||||
|
/// by an editor
|
||||||
|
fn is_temp_file(path: &Path) -> bool {
|
||||||
|
let ext = path.extension();
|
||||||
|
match ext {
|
||||||
|
Some(ex) => match ex.to_str().unwrap() {
|
||||||
|
"swp" | "swx" | "tmp" | ".DS_STORE" => true,
|
||||||
|
// jetbrains IDE
|
||||||
|
x if x.ends_with("jb_old___") => true,
|
||||||
|
x if x.ends_with("jb_tmp___") => true,
|
||||||
|
x if x.ends_with("jb_bak___") => true,
|
||||||
|
// vim
|
||||||
|
x if x.ends_with('~') => true,
|
||||||
|
_ => {
|
||||||
|
if let Some(filename) = path.file_stem() {
|
||||||
|
// emacs
|
||||||
|
filename.to_str().unwrap().starts_with('#')
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
path.ends_with(".DS_STORE")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Detect what changed from the given path so we have an idea what needs
|
||||||
|
/// to be reloaded
|
||||||
|
fn detect_change_kind(pwd: &str, path: &Path) -> (ChangeKind, String) {
|
||||||
|
let path_str = format!("{}", path.display())
|
||||||
|
.replace(pwd, "")
|
||||||
|
.replace("\\", "/");
|
||||||
|
let change_kind = if path_str.starts_with("/templates") {
|
||||||
|
ChangeKind::Templates
|
||||||
|
} else if path_str.starts_with("/content") {
|
||||||
|
ChangeKind::Content
|
||||||
|
} else if path_str.starts_with("/static") {
|
||||||
|
ChangeKind::StaticFiles
|
||||||
|
} else {
|
||||||
|
panic!("Got a change in an unexpected path: {}", path_str);
|
||||||
|
};
|
||||||
|
|
||||||
|
(change_kind, path_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use super::{is_temp_file, detect_change_kind, ChangeKind};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_recognize_temp_files() {
|
||||||
|
let testcases = vec![
|
||||||
|
Path::new("hello.swp"),
|
||||||
|
Path::new("hello.swx"),
|
||||||
|
Path::new(".DS_STORE"),
|
||||||
|
Path::new("hello.tmp"),
|
||||||
|
Path::new("hello.html.__jb_old___"),
|
||||||
|
Path::new("hello.html.__jb_tmp___"),
|
||||||
|
Path::new("hello.html.__jb_bak___"),
|
||||||
|
Path::new("hello.html~"),
|
||||||
|
Path::new("#hello.html"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for t in testcases {
|
||||||
|
println!("{:?}", t.display());
|
||||||
|
assert!(is_temp_file(&t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_detect_kind_of_changes() {
|
||||||
|
let testcases = vec![
|
||||||
|
(
|
||||||
|
(ChangeKind::Templates, "/templates/hello.html".to_string()),
|
||||||
|
"/home/vincent/site", Path::new("/home/vincent/site/templates/hello.html")
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(ChangeKind::StaticFiles, "/static/site.css".to_string()),
|
||||||
|
"/home/vincent/site", Path::new("/home/vincent/site/static/site.css")
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(ChangeKind::Content, "/content/posts/hello.md".to_string()),
|
||||||
|
"/home/vincent/site", Path::new("/home/vincent/site/content/posts/hello.md")
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (expected, pwd, path) in testcases {
|
||||||
|
println!("{:?}", path.display());
|
||||||
|
assert_eq!(expected, detect_change_kind(&pwd, &path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -7,19 +7,28 @@ use toml::{Value as Toml, self};
|
||||||
|
|
||||||
use errors::{Result, ResultExt};
|
use errors::{Result, ResultExt};
|
||||||
|
|
||||||
// TODO: disable tag(s)/category(ies) page generation
|
|
||||||
|
// TO ADD:
|
||||||
|
// highlight code theme
|
||||||
|
// generate_tags_pages
|
||||||
|
// generate_categories_pages
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Title of the site
|
/// Title of the site
|
||||||
pub title: String,
|
pub title: String,
|
||||||
/// Base URL of the site
|
/// Base URL of the site
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
|
|
||||||
|
/// Whether to highlight all code blocks found in markdown files. Defaults to false
|
||||||
|
pub highlight_code: Option<bool>,
|
||||||
/// Description of the site
|
/// Description of the site
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
/// The language used in the site. Defaults to "en"
|
/// The language used in the site. Defaults to "en"
|
||||||
pub language_code: Option<String>,
|
pub language_code: Option<String>,
|
||||||
/// Whether to disable RSS generation, defaults to None (== generate RSS)
|
/// Whether to generate RSS, defaults to false
|
||||||
pub disable_rss: Option<bool>,
|
pub generate_rss: Option<bool>,
|
||||||
|
|
||||||
/// All user params set in [extra] in the config
|
/// All user params set in [extra] in the config
|
||||||
pub extra: Option<HashMap<String, Toml>>,
|
pub extra: Option<HashMap<String, Toml>>,
|
||||||
}
|
}
|
||||||
|
@ -32,10 +41,19 @@ impl Config {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => bail!(e)
|
Err(e) => bail!(e)
|
||||||
};
|
};
|
||||||
|
|
||||||
if config.language_code.is_none() {
|
if config.language_code.is_none() {
|
||||||
config.language_code = Some("en".to_string());
|
config.language_code = Some("en".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.highlight_code.is_none() {
|
||||||
|
config.highlight_code = Some(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.generate_rss.is_none() {
|
||||||
|
config.generate_rss = Some(false);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +66,44 @@ impl Config {
|
||||||
|
|
||||||
Config::parse(&content)
|
Config::parse(&content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Makes a url, taking into account that the base url might have a trailing slash
|
||||||
|
pub fn make_permalink(&self, path: &str) -> String {
|
||||||
|
if self.base_url.ends_with('/') {
|
||||||
|
format!("{}{}", self.base_url, path)
|
||||||
|
} else {
|
||||||
|
format!("{}/{}", self.base_url, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
/// Exists for testing purposes
|
||||||
|
fn default() -> Config {
|
||||||
|
Config {
|
||||||
|
title: "".to_string(),
|
||||||
|
base_url: "http://a-website.com/".to_string(),
|
||||||
|
highlight_code: Some(true),
|
||||||
|
description: None,
|
||||||
|
language_code: Some("en".to_string()),
|
||||||
|
generate_rss: Some(false),
|
||||||
|
extra: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Get and parse the config.
|
||||||
|
/// If it doesn't succeed, exit
|
||||||
|
pub fn get_config(path: &Path) -> Config {
|
||||||
|
match Config::from_file(path.join("config.toml")) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to load config.toml");
|
||||||
|
println!("Error: {}", e);
|
||||||
|
::std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,7 @@ use tera;
|
||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
errors {
|
errors {}
|
||||||
}
|
|
||||||
|
|
||||||
links {
|
links {
|
||||||
Tera(tera::Error, tera::ErrorKind);
|
Tera(tera::Error, tera::ErrorKind);
|
||||||
|
|
|
@ -1,21 +1,32 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use toml;
|
use toml;
|
||||||
use tera::Value;
|
use tera::Value;
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
|
||||||
use errors::{Result};
|
use errors::{Result, ResultExt};
|
||||||
|
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n?((?s).*(?-s))$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The front matter of every page
|
/// The front matter of every page
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FrontMatter {
|
pub struct FrontMatter {
|
||||||
// <title> of the page
|
// Mandatory fields
|
||||||
|
|
||||||
|
/// <title> of the page
|
||||||
pub title: String,
|
pub title: String,
|
||||||
/// Description that appears when linked, e.g. on twitter
|
/// Description that appears when linked, e.g. on twitter
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
|
||||||
|
// Optional stuff
|
||||||
|
|
||||||
/// Date if we want to order pages (ie blog post)
|
/// Date if we want to order pages (ie blog post)
|
||||||
pub date: Option<String>,
|
pub date: Option<String>,
|
||||||
/// The page slug. Will be used instead of the filename if present
|
/// The page slug. Will be used instead of the filename if present
|
||||||
|
@ -31,9 +42,9 @@ pub struct FrontMatter {
|
||||||
pub draft: Option<bool>,
|
pub draft: Option<bool>,
|
||||||
/// Only one category allowed
|
/// Only one category allowed
|
||||||
pub category: Option<String>,
|
pub category: Option<String>,
|
||||||
/// Optional layout, if we want to specify which tpl to render for that page
|
/// Optional template, if we want to specify which template to render for that page
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub layout: Option<String>,
|
pub template: Option<String>,
|
||||||
/// Any extra parameter present in the front matter
|
/// Any extra parameter present in the front matter
|
||||||
pub extra: Option<HashMap<String, Value>>,
|
pub extra: Option<HashMap<String, Value>>,
|
||||||
}
|
}
|
||||||
|
@ -44,7 +55,7 @@ impl FrontMatter {
|
||||||
bail!("Front matter of file is missing");
|
bail!("Front matter of file is missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut f: FrontMatter = match toml::from_str(toml) {
|
let f: FrontMatter = match toml::from_str(toml) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(e) => bail!(e),
|
Err(e) => bail!(e),
|
||||||
};
|
};
|
||||||
|
@ -63,129 +74,40 @@ impl FrontMatter {
|
||||||
|
|
||||||
Ok(f)
|
Ok(f)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Converts the date in the front matter, which can be in 2 formats, into a NaiveDateTime
|
||||||
#[cfg(test)]
|
pub fn parse_date(&self) -> Option<NaiveDateTime> {
|
||||||
mod tests {
|
match self.date {
|
||||||
use super::{FrontMatter};
|
Some(ref d) => {
|
||||||
use tera::to_value;
|
if d.contains('T') {
|
||||||
|
DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local()))
|
||||||
|
} else {
|
||||||
#[test]
|
NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0)))
|
||||||
fn test_can_parse_a_valid_front_matter() {
|
}
|
||||||
let content = r#"
|
},
|
||||||
title = "Hello"
|
None => None,
|
||||||
description = "hey there""#;
|
}
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
println!("{:?}", res);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let res = res.unwrap();
|
|
||||||
assert_eq!(res.title, "Hello".to_string());
|
|
||||||
assert_eq!(res.description, "hey there".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_can_parse_tags() {
|
|
||||||
let content = r#"
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
slug = "hello-world"
|
|
||||||
tags = ["rust", "html"]"#;
|
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let res = res.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(res.title, "Hello".to_string());
|
|
||||||
assert_eq!(res.slug.unwrap(), "hello-world".to_string());
|
|
||||||
assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_can_parse_extra_attributes_in_frontmatter() {
|
|
||||||
let content = r#"
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
slug = "hello-world"
|
|
||||||
|
|
||||||
[extra]
|
|
||||||
language = "en"
|
|
||||||
authors = ["Bob", "Alice"]"#;
|
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let res = res.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(res.title, "Hello".to_string());
|
|
||||||
assert_eq!(res.slug.unwrap(), "hello-world".to_string());
|
|
||||||
let extra = res.extra.unwrap();
|
|
||||||
assert_eq!(extra.get("language").unwrap(), &to_value("en").unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
extra.get("authors").unwrap(),
|
|
||||||
&to_value(["Bob".to_string(), "Alice".to_string()]).unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_is_ok_with_url_instead_of_slug() {
|
|
||||||
let content = r#"
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
url = "hello-world""#;
|
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let res = res.unwrap();
|
|
||||||
assert!(res.slug.is_none());
|
|
||||||
assert_eq!(res.url.unwrap(), "hello-world".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_errors_with_empty_front_matter() {
|
|
||||||
let content = r#" "#;
|
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_errors_with_invalid_front_matter() {
|
|
||||||
let content = r#"title = 1\n"#;
|
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_errors_with_missing_required_value_front_matter() {
|
|
||||||
let content = r#"title = """#;
|
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_errors_on_non_string_tag() {
|
|
||||||
let content = r#"
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
slug = "hello-world"
|
|
||||||
tags = ["rust", 1]"#;
|
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_errors_on_present_but_empty_slug() {
|
|
||||||
let content = r#"
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
slug = """#;
|
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn test_errors_on_present_but_empty_url() {
|
|
||||||
let content = r#"
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
url = """#;
|
|
||||||
let res = FrontMatter::parse(content);
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Split a file between the front matter and its content
|
||||||
|
/// It will parse the front matter as well and returns any error encountered
|
||||||
|
/// TODO: add tests
|
||||||
|
pub fn split_content(file_path: &Path, content: &str) -> Result<(FrontMatter, String)> {
|
||||||
|
if !PAGE_RE.is_match(content) {
|
||||||
|
bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", file_path.to_string_lossy());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. extract the front matter and the content
|
||||||
|
let caps = PAGE_RE.captures(content).unwrap();
|
||||||
|
// caps[0] is the full match
|
||||||
|
let front_matter = &caps[1];
|
||||||
|
let content = &caps[2];
|
||||||
|
|
||||||
|
// 3. create our page, parse front matter and assign all of that
|
||||||
|
let meta = FrontMatter::parse(front_matter)
|
||||||
|
.chain_err(|| format!("Error when parsing front matter of file `{}`", file_path.to_string_lossy()))?;
|
||||||
|
|
||||||
|
Ok((meta, content.to_string()))
|
||||||
|
}
|
||||||
|
|
34
src/lib.rs
Normal file
34
src/lib.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate error_chain;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate toml;
|
||||||
|
extern crate walkdir;
|
||||||
|
extern crate pulldown_cmark;
|
||||||
|
extern crate regex;
|
||||||
|
extern crate tera;
|
||||||
|
extern crate glob;
|
||||||
|
extern crate syntect;
|
||||||
|
extern crate slug;
|
||||||
|
extern crate chrono;
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
mod config;
|
||||||
|
pub mod errors;
|
||||||
|
mod page;
|
||||||
|
mod front_matter;
|
||||||
|
mod site;
|
||||||
|
mod markdown;
|
||||||
|
mod section;
|
||||||
|
|
||||||
|
pub use site::Site;
|
||||||
|
pub use config::Config;
|
||||||
|
pub use front_matter::{FrontMatter, split_content};
|
||||||
|
pub use page::{Page};
|
||||||
|
pub use section::{Section};
|
||||||
|
pub use utils::create_file;
|
77
src/main.rs
77
src/main.rs
|
@ -2,44 +2,31 @@
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate error_chain;
|
extern crate error_chain;
|
||||||
#[macro_use]
|
extern crate gutenberg;
|
||||||
extern crate lazy_static;
|
extern crate chrono;
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
extern crate staticfile;
|
||||||
extern crate serde;
|
extern crate iron;
|
||||||
extern crate toml;
|
extern crate mount;
|
||||||
extern crate walkdir;
|
extern crate notify;
|
||||||
extern crate pulldown_cmark;
|
extern crate ws;
|
||||||
extern crate regex;
|
|
||||||
extern crate tera;
|
|
||||||
extern crate glob;
|
|
||||||
extern crate syntect;
|
|
||||||
|
|
||||||
|
|
||||||
mod utils;
|
use std::time::Instant;
|
||||||
mod config;
|
use chrono::Duration;
|
||||||
mod errors;
|
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod page;
|
|
||||||
mod front_matter;
|
|
||||||
|
|
||||||
|
|
||||||
use config::Config;
|
// Print the time elapsed rounded to 1 decimal
|
||||||
|
fn report_elapsed_time(instant: Instant) {
|
||||||
|
let duration_ms = Duration::from_std(instant.elapsed()).unwrap().num_milliseconds() as f64;
|
||||||
|
|
||||||
|
if duration_ms < 1000.0 {
|
||||||
// Get and parse the config.
|
println!("Done in {}ms.\n", duration_ms);
|
||||||
// If it doesn't succeed, exit
|
} else {
|
||||||
fn get_config() -> Config {
|
let duration_sec = duration_ms / 1000.0;
|
||||||
match Config::from_file("config.toml") {
|
println!("Done in {:.1}s.\n", ((duration_sec * 10.0).round() / 10.0));
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failed to load config.toml");
|
|
||||||
println!("Error: {}", e);
|
|
||||||
for e in e.iter().skip(1) {
|
|
||||||
println!("Reason: {}", e)
|
|
||||||
}
|
|
||||||
::std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +44,11 @@ fn main() {
|
||||||
(@subcommand build =>
|
(@subcommand build =>
|
||||||
(about: "Builds the site")
|
(about: "Builds the site")
|
||||||
)
|
)
|
||||||
|
(@subcommand serve =>
|
||||||
|
(about: "Serve the site. Rebuild and reload on change automatically")
|
||||||
|
(@arg interface: "Interface to bind on (default to 127.0.0.1)")
|
||||||
|
(@arg port: "Which port to use (default to 1111)")
|
||||||
|
)
|
||||||
).get_matches();
|
).get_matches();
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
|
@ -64,7 +56,6 @@ fn main() {
|
||||||
match cmd::create_new_project(matches.value_of("name").unwrap()) {
|
match cmd::create_new_project(matches.value_of("name").unwrap()) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("Project created");
|
println!("Project created");
|
||||||
println!("You will now need to set a theme in `config.toml`");
|
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error: {}", e);
|
println!("Error: {}", e);
|
||||||
|
@ -73,9 +64,11 @@ fn main() {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
("build", Some(_)) => {
|
("build", Some(_)) => {
|
||||||
match cmd::build(get_config()) {
|
println!("Building site");
|
||||||
|
let start = Instant::now();
|
||||||
|
match cmd::build() {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("Project built.");
|
report_elapsed_time(start);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Failed to build the site");
|
println!("Failed to build the site");
|
||||||
|
@ -87,6 +80,20 @@ fn main() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
("serve", Some(matches)) => {
|
||||||
|
let interface = matches.value_of("interface").unwrap_or("127.0.0.1");
|
||||||
|
let port = matches.value_of("port").unwrap_or("1111");
|
||||||
|
match cmd::serve(interface, port) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error: {}", e);
|
||||||
|
for e in e.iter().skip(1) {
|
||||||
|
println!("Reason: {}", e)
|
||||||
|
}
|
||||||
|
::std::process::exit(1);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
150
src/markdown.rs
Normal file
150
src/markdown.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
use std::borrow::Cow::Owned;
|
||||||
|
|
||||||
|
use pulldown_cmark as cmark;
|
||||||
|
use self::cmark::{Parser, Event, Tag};
|
||||||
|
|
||||||
|
use syntect::easy::HighlightLines;
|
||||||
|
use syntect::parsing::SyntaxSet;
|
||||||
|
use syntect::highlighting::ThemeSet;
|
||||||
|
use syntect::html::{start_coloured_html_snippet, styles_to_coloured_html, IncludeBackground};
|
||||||
|
|
||||||
|
|
||||||
|
// We need to put those in a struct to impl Send and sync
|
||||||
|
struct Setup {
|
||||||
|
syntax_set: SyntaxSet,
|
||||||
|
theme_set: ThemeSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Setup {}
|
||||||
|
unsafe impl Sync for Setup {}
|
||||||
|
|
||||||
|
lazy_static!{
|
||||||
|
static ref SETUP: Setup = Setup {
|
||||||
|
syntax_set: SyntaxSet::load_defaults_newlines(),
|
||||||
|
theme_set: ThemeSet::load_defaults()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct CodeHighlightingParser<'a> {
|
||||||
|
// The block we're currently highlighting
|
||||||
|
highlighter: Option<HighlightLines<'a>>,
|
||||||
|
parser: Parser<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CodeHighlightingParser<'a> {
|
||||||
|
pub fn new(parser: Parser<'a>) -> CodeHighlightingParser<'a> {
|
||||||
|
CodeHighlightingParser {
|
||||||
|
highlighter: None,
|
||||||
|
parser: parser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for CodeHighlightingParser<'a> {
|
||||||
|
type Item = Event<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Event<'a>> {
|
||||||
|
// Not using pattern matching to reduce indentation levels
|
||||||
|
let next_opt = self.parser.next();
|
||||||
|
if next_opt.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = next_opt.unwrap();
|
||||||
|
// Below we just look for the start of a code block and highlight everything
|
||||||
|
// until we see the end of a code block.
|
||||||
|
// Everything else happens as normal in pulldown_cmark
|
||||||
|
match item {
|
||||||
|
Event::Text(text) => {
|
||||||
|
// if we are in the middle of a code block
|
||||||
|
if let Some(ref mut highlighter) = self.highlighter {
|
||||||
|
let highlighted = &highlighter.highlight(&text);
|
||||||
|
let html = styles_to_coloured_html(highlighted, IncludeBackground::Yes);
|
||||||
|
Some(Event::Html(Owned(html)))
|
||||||
|
} else {
|
||||||
|
Some(Event::Text(text))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::Start(Tag::CodeBlock(ref info)) => {
|
||||||
|
let syntax = info
|
||||||
|
.split(' ')
|
||||||
|
.next()
|
||||||
|
.and_then(|lang| SETUP.syntax_set.find_syntax_by_token(lang))
|
||||||
|
.unwrap_or_else(|| SETUP.syntax_set.find_syntax_plain_text());
|
||||||
|
self.highlighter = Some(
|
||||||
|
HighlightLines::new(syntax, &SETUP.theme_set.themes["base16-ocean.dark"])
|
||||||
|
);
|
||||||
|
let snippet = start_coloured_html_snippet(&SETUP.theme_set.themes["base16-ocean.dark"]);
|
||||||
|
Some(Event::Html(Owned(snippet)))
|
||||||
|
},
|
||||||
|
Event::End(Tag::CodeBlock(_)) => {
|
||||||
|
// reset highlight and close the code block
|
||||||
|
self.highlighter = None;
|
||||||
|
Some(Event::Html(Owned("</pre>".to_owned())))
|
||||||
|
},
|
||||||
|
_ => Some(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn markdown_to_html(content: &str, highlight_code: bool) -> String {
|
||||||
|
let mut html = String::new();
|
||||||
|
if highlight_code {
|
||||||
|
let parser = CodeHighlightingParser::new(Parser::new(content));
|
||||||
|
cmark::html::push_html(&mut html, parser);
|
||||||
|
} else {
|
||||||
|
let parser = Parser::new(content);
|
||||||
|
cmark::html::push_html(&mut html, parser);
|
||||||
|
};
|
||||||
|
html
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{markdown_to_html};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_markdown_to_html_simple() {
|
||||||
|
let res = markdown_to_html("# hello", true);
|
||||||
|
assert_eq!(res, "<h1>hello</h1>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_markdown_to_html_code_block_highlighting_off() {
|
||||||
|
let res = markdown_to_html("```\n$ gutenberg server\n```", false);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
"<pre><code>$ gutenberg server\n</code></pre>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_markdown_to_html_code_block_no_lang() {
|
||||||
|
let res = markdown_to_html("```\n$ gutenberg server\n$ ping\n```", true);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">$ gutenberg server\n</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">$ ping\n</span></pre>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_markdown_to_html_code_block_with_lang() {
|
||||||
|
let res = markdown_to_html("```python\nlist.append(1)\n```", true);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">.</span><span style=\"background-color:#2b303b;color:#bf616a;\">append</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">(</span><span style=\"background-color:#2b303b;color:#d08770;\">1</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">)</span><span style=\"background-color:#2b303b;color:#c0c5ce;\">\n</span></pre>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_markdown_to_html_code_block_with_unknown_lang() {
|
||||||
|
let res = markdown_to_html("```yolo\nlist.append(1)\n```", true);
|
||||||
|
// defaults to plain text
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
"<pre style=\"background-color:#2b303b\">\n<span style=\"background-color:#2b303b;color:#c0c5ce;\">list.append(1)\n</span></pre>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
366
src/page.rs
366
src/page.rs
|
@ -1,45 +1,78 @@
|
||||||
/// A page, can be a blog post or a basic page
|
/// A page, can be a blog post or a basic page
|
||||||
use std::fs::File;
|
use std::cmp::Ordering;
|
||||||
use std::io::prelude::*;
|
use std::fs::{read_dir};
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::result::Result as StdResult;
|
use std::result::Result as StdResult;
|
||||||
|
|
||||||
|
|
||||||
use pulldown_cmark as cmark;
|
|
||||||
use regex::Regex;
|
|
||||||
use tera::{Tera, Context};
|
use tera::{Tera, Context};
|
||||||
use serde::ser::{SerializeStruct, self};
|
use serde::ser::{SerializeStruct, self};
|
||||||
|
use slug::slugify;
|
||||||
|
|
||||||
use errors::{Result, ResultExt};
|
use errors::{Result, ResultExt};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use front_matter::{FrontMatter};
|
use front_matter::{FrontMatter, split_content};
|
||||||
|
use markdown::markdown_to_html;
|
||||||
|
use utils::{read_file, find_content_components};
|
||||||
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n((?s).*(?-s))$").unwrap();
|
/// Looks into the current folder for the path and see if there's anything that is not a .md
|
||||||
|
/// file. Those will be copied next to the rendered .html file
|
||||||
|
fn find_related_assets(path: &Path) -> Vec<PathBuf> {
|
||||||
|
let mut assets = vec![];
|
||||||
|
|
||||||
|
for entry in read_dir(path).unwrap().filter_map(|e| e.ok()) {
|
||||||
|
let entry_path = entry.path();
|
||||||
|
if entry_path.is_file() {
|
||||||
|
match entry_path.extension() {
|
||||||
|
Some(e) => match e.to_str() {
|
||||||
|
Some("md") => continue,
|
||||||
|
_ => assets.push(entry_path.to_path_buf()),
|
||||||
|
},
|
||||||
|
None => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
/// .md filepath, excluding the content/ bit
|
/// The .md path
|
||||||
#[serde(skip_serializing)]
|
pub file_path: PathBuf,
|
||||||
pub filepath: String,
|
/// The parent directory of the file. Is actually the grand parent directory
|
||||||
|
/// if it's an asset folder
|
||||||
|
pub parent_path: PathBuf,
|
||||||
/// The name of the .md file
|
/// The name of the .md file
|
||||||
#[serde(skip_serializing)]
|
pub file_name: String,
|
||||||
pub filename: String,
|
/// The directories above our .md file
|
||||||
/// The directories above our .md file are called sections
|
/// for example a file at content/kb/solutions/blabla.md will have 2 components:
|
||||||
/// for example a file at content/kb/solutions/blabla.md will have 2 sections:
|
|
||||||
/// `kb` and `solutions`
|
/// `kb` and `solutions`
|
||||||
#[serde(skip_serializing)]
|
pub components: Vec<String>,
|
||||||
pub sections: Vec<String>,
|
|
||||||
/// The actual content of the page, in markdown
|
/// The actual content of the page, in markdown
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub raw_content: String,
|
pub raw_content: String,
|
||||||
|
/// All the non-md files we found next to the .md file
|
||||||
|
pub assets: Vec<PathBuf>,
|
||||||
/// The HTML rendered of the page
|
/// The HTML rendered of the page
|
||||||
pub content: String,
|
pub content: String,
|
||||||
/// The front matter meta-data
|
/// The front matter meta-data
|
||||||
pub meta: FrontMatter,
|
pub meta: FrontMatter,
|
||||||
|
|
||||||
|
/// The slug of that page.
|
||||||
|
/// First tries to find the slug in the meta and defaults to filename otherwise
|
||||||
|
pub slug: String,
|
||||||
|
/// The relative URL of the page
|
||||||
|
pub url: String,
|
||||||
|
/// The full URL for that page
|
||||||
|
pub permalink: String,
|
||||||
|
/// The summary for the article, defaults to empty string
|
||||||
|
/// When <!-- more --> is found in the text, will take the content up to that part
|
||||||
|
/// as summary
|
||||||
|
pub summary: String,
|
||||||
|
|
||||||
/// The previous page, by date
|
/// The previous page, by date
|
||||||
pub previous: Option<Box<Page>>,
|
pub previous: Option<Box<Page>>,
|
||||||
/// The next page, by date
|
/// The next page, by date
|
||||||
|
@ -50,227 +83,200 @@ pub struct Page {
|
||||||
impl Page {
|
impl Page {
|
||||||
pub fn new(meta: FrontMatter) -> Page {
|
pub fn new(meta: FrontMatter) -> Page {
|
||||||
Page {
|
Page {
|
||||||
filepath: "".to_string(),
|
file_path: PathBuf::new(),
|
||||||
filename: "".to_string(),
|
parent_path: PathBuf::new(),
|
||||||
sections: vec![],
|
file_name: "".to_string(),
|
||||||
|
components: vec![],
|
||||||
raw_content: "".to_string(),
|
raw_content: "".to_string(),
|
||||||
|
assets: vec![],
|
||||||
content: "".to_string(),
|
content: "".to_string(),
|
||||||
|
slug: "".to_string(),
|
||||||
|
url: "".to_string(),
|
||||||
|
permalink: "".to_string(),
|
||||||
|
summary: "".to_string(),
|
||||||
meta: meta,
|
meta: meta,
|
||||||
previous: None,
|
previous: None,
|
||||||
next: None,
|
next: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the slug for the page.
|
/// Get word count and estimated reading time
|
||||||
/// First tries to find the slug in the meta and defaults to filename otherwise
|
pub fn get_reading_analytics(&self) -> (usize, usize) {
|
||||||
pub fn get_slug(&self) -> String {
|
// Only works for latin language but good enough for a start
|
||||||
if let Some(ref slug) = self.meta.slug {
|
let word_count: usize = self.raw_content.split_whitespace().count();
|
||||||
slug.to_string()
|
|
||||||
} else {
|
// https://help.medium.com/hc/en-us/articles/214991667-Read-time
|
||||||
self.filename.clone()
|
// 275 seems a bit too high though
|
||||||
}
|
(word_count, (word_count / 200))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a page given the content of the .md file
|
/// Parse a page given the content of the .md file
|
||||||
// Files without front matter or with invalid front matter are considered
|
/// Files without front matter or with invalid front matter are considered
|
||||||
// erroneous
|
/// erroneous
|
||||||
pub fn parse(filepath: &str, content: &str) -> Result<Page> {
|
pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Page> {
|
||||||
// 1. separate front matter from content
|
// 1. separate front matter from content
|
||||||
if !PAGE_RE.is_match(content) {
|
let (meta, content) = split_content(file_path, content)?;
|
||||||
bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", filepath);
|
let mut page = Page::new(meta);
|
||||||
|
page.file_path = file_path.to_path_buf();
|
||||||
|
page.parent_path = page.file_path.parent().unwrap().to_path_buf();
|
||||||
|
page.raw_content = content;
|
||||||
|
|
||||||
|
// We try to be smart about highlighting code as it can be time-consuming
|
||||||
|
// If the global config disables it, then we do nothing. However,
|
||||||
|
// if we see a code block in the content, we assume that this page needs
|
||||||
|
// to be highlighted. It could potentially have false positive if the content
|
||||||
|
// has ``` in it but that seems kind of unlikely
|
||||||
|
let should_highlight = if config.highlight_code.unwrap() {
|
||||||
|
page.raw_content.contains("```")
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
page.content = markdown_to_html(&page.raw_content, should_highlight);
|
||||||
|
|
||||||
|
if page.raw_content.contains("<!-- more -->") {
|
||||||
|
page.summary = {
|
||||||
|
let summary = page.raw_content.splitn(2, "<!-- more -->").collect::<Vec<&str>>()[0];
|
||||||
|
markdown_to_html(summary, should_highlight)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. extract the front matter and the content
|
let path = Path::new(file_path);
|
||||||
let caps = PAGE_RE.captures(content).unwrap();
|
page.file_name = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||||
// caps[0] is the full match
|
|
||||||
let front_matter = &caps[1];
|
|
||||||
let content = &caps[2];
|
|
||||||
|
|
||||||
// 3. create our page, parse front matter and assign all of that
|
page.slug = {
|
||||||
let meta = FrontMatter::parse(&front_matter)
|
if let Some(ref slug) = page.meta.slug {
|
||||||
.chain_err(|| format!("Error when parsing front matter of file `{}`", filepath))?;
|
slug.trim().to_string()
|
||||||
|
} else {
|
||||||
let mut page = Page::new(meta);
|
slugify(page.file_name.clone())
|
||||||
page.filepath = filepath.to_string();
|
}
|
||||||
page.raw_content = content.to_string();
|
|
||||||
page.content = {
|
|
||||||
let mut html = String::new();
|
|
||||||
let parser = cmark::Parser::new(&page.raw_content);
|
|
||||||
cmark::html::push_html(&mut html, parser);
|
|
||||||
html
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. Find sections
|
// 4. Find sections
|
||||||
// Pages with custom urls exists outside of sections
|
// Pages with custom urls exists outside of sections
|
||||||
if page.meta.url.is_none() {
|
if let Some(ref u) = page.meta.url {
|
||||||
let path = Path::new(filepath);
|
page.url = u.trim().to_string();
|
||||||
page.filename = path.file_stem().expect("Couldn't get filename").to_string_lossy().to_string();
|
} else {
|
||||||
|
page.components = find_content_components(&page.file_path);
|
||||||
|
if !page.components.is_empty() {
|
||||||
|
// If we have a folder with an asset, don't consider it as a component
|
||||||
|
if page.file_name == "index" {
|
||||||
|
page.components.pop();
|
||||||
|
// also set parent_path to grandparent instead
|
||||||
|
page.parent_path = page.parent_path.parent().unwrap().to_path_buf();
|
||||||
|
}
|
||||||
|
|
||||||
// find out if we have sections
|
// Don't add a trailing slash to sections
|
||||||
for section in path.parent().unwrap().components() {
|
page.url = format!("{}/{}", page.components.join("/"), page.slug);
|
||||||
page.sections.push(section.as_ref().to_string_lossy().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// now the url
|
|
||||||
// We get it from a combination of sections + slug
|
|
||||||
if !page.sections.is_empty() {
|
|
||||||
page.meta.url = Some(format!("/{}/{}", page.sections.join("/"), page.get_slug()));
|
|
||||||
} else {
|
} else {
|
||||||
page.meta.url = Some(format!("/{}", page.get_slug()));
|
page.url = page.slug.clone();
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page.permalink = config.make_permalink(&page.url);
|
||||||
|
|
||||||
Ok(page)
|
Ok(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Page> {
|
/// Read and parse a .md file into a Page struct
|
||||||
|
pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Page> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
let content = read_file(path)?;
|
||||||
|
let mut page = Page::parse(path, &content, config)?;
|
||||||
|
page.assets = find_related_assets(path.parent().unwrap());
|
||||||
|
|
||||||
let mut content = String::new();
|
if !page.assets.is_empty() && page.file_name != "index" {
|
||||||
File::open(path)
|
bail!("Page `{}` has assets but is not named index.md", path.display());
|
||||||
.chain_err(|| format!("Failed to open '{:?}'", path.display()))?
|
}
|
||||||
.read_to_string(&mut content)?;
|
|
||||||
|
Ok(page)
|
||||||
|
|
||||||
// Remove the content string from name
|
|
||||||
// Maybe get a path as an arg instead and use strip_prefix?
|
|
||||||
Page::parse(&path.strip_prefix("content").unwrap().to_string_lossy(), &content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_layout_name(&self) -> String {
|
/// Renders the page using the default layout, unless specified in front-matter
|
||||||
match self.meta.layout {
|
pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> {
|
||||||
|
let tpl_name = match self.meta.template {
|
||||||
Some(ref l) => l.to_string(),
|
Some(ref l) => l.to_string(),
|
||||||
None => "page.html".to_string()
|
None => "page.html".to_string()
|
||||||
}
|
};
|
||||||
}
|
// TODO: create a helper to create context to ensure all contexts
|
||||||
|
// have the same names
|
||||||
pub fn render_html(&mut self, tera: &Tera, config: &Config) -> Result<String> {
|
|
||||||
let tpl = self.get_layout_name();
|
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.add("site", config);
|
context.add("config", config);
|
||||||
context.add("page", self);
|
context.add("page", self);
|
||||||
|
|
||||||
tera.render(&tpl, &context)
|
tera.render(&tpl_name, &context)
|
||||||
.chain_err(|| "Error while rendering template")
|
.chain_err(|| format!("Failed to render page '{}'", self.file_name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ser::Serialize for Page {
|
impl ser::Serialize for Page {
|
||||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer {
|
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer {
|
||||||
let mut state = serializer.serialize_struct("page", 10)?;
|
let mut state = serializer.serialize_struct("page", 13)?;
|
||||||
state.serialize_field("content", &self.content)?;
|
state.serialize_field("content", &self.content)?;
|
||||||
state.serialize_field("title", &self.meta.title)?;
|
state.serialize_field("title", &self.meta.title)?;
|
||||||
state.serialize_field("description", &self.meta.description)?;
|
state.serialize_field("description", &self.meta.description)?;
|
||||||
state.serialize_field("date", &self.meta.date)?;
|
state.serialize_field("date", &self.meta.date)?;
|
||||||
state.serialize_field("slug", &self.meta.slug)?;
|
state.serialize_field("slug", &self.slug)?;
|
||||||
state.serialize_field("url", &self.meta.url)?;
|
state.serialize_field("url", &format!("/{}", self.url))?;
|
||||||
|
state.serialize_field("permalink", &self.permalink)?;
|
||||||
state.serialize_field("tags", &self.meta.tags)?;
|
state.serialize_field("tags", &self.meta.tags)?;
|
||||||
state.serialize_field("draft", &self.meta.draft)?;
|
state.serialize_field("draft", &self.meta.draft)?;
|
||||||
state.serialize_field("category", &self.meta.category)?;
|
state.serialize_field("category", &self.meta.category)?;
|
||||||
state.serialize_field("extra", &self.meta.extra)?;
|
state.serialize_field("extra", &self.meta.extra)?;
|
||||||
|
let (word_count, reading_time) = self.get_reading_analytics();
|
||||||
|
state.serialize_field("word_count", &word_count)?;
|
||||||
|
state.serialize_field("reading_time", &reading_time)?;
|
||||||
state.end()
|
state.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Order pages by date, no-op for now
|
impl PartialOrd for Page {
|
||||||
// TODO: impl PartialOrd on Vec<Page> so we can use sort()?
|
fn partial_cmp(&self, other: &Page) -> Option<Ordering> {
|
||||||
pub fn order_pages(pages: Vec<Page>) -> Vec<Page> {
|
if self.meta.date.is_none() {
|
||||||
pages
|
return Some(Ordering::Less);
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.meta.date.is_none() {
|
||||||
|
return Some(Ordering::Greater);
|
||||||
|
}
|
||||||
|
|
||||||
|
let this_date = self.meta.parse_date().unwrap();
|
||||||
|
let other_date = other.meta.parse_date().unwrap();
|
||||||
|
|
||||||
|
if this_date > other_date {
|
||||||
|
return Some(Ordering::Less);
|
||||||
|
}
|
||||||
|
if this_date < other_date {
|
||||||
|
return Some(Ordering::Greater);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Ordering::Equal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Page};
|
use tempdir::TempDir;
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
use super::{find_related_assets};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_can_parse_a_valid_page() {
|
fn test_find_related_assets() {
|
||||||
let content = r#"
|
let tmp_dir = TempDir::new("example").expect("create temp dir");
|
||||||
+++
|
File::create(tmp_dir.path().join("index.md")).unwrap();
|
||||||
title = "Hello"
|
File::create(tmp_dir.path().join("example.js")).unwrap();
|
||||||
description = "hey there"
|
File::create(tmp_dir.path().join("graph.jpg")).unwrap();
|
||||||
slug = "hello-world"
|
File::create(tmp_dir.path().join("fail.png")).unwrap();
|
||||||
+++
|
|
||||||
Hello world"#;
|
|
||||||
let res = Page::parse("post.md", content);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let page = res.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(page.meta.title, "Hello".to_string());
|
let assets = find_related_assets(tmp_dir.path());
|
||||||
assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string());
|
assert_eq!(assets.len(), 3);
|
||||||
assert_eq!(page.raw_content, "Hello world".to_string());
|
assert_eq!(assets.iter().filter(|p| p.extension().unwrap() != "md").count(), 3);
|
||||||
assert_eq!(page.content, "<p>Hello world</p>\n".to_string());
|
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "example.js").count(), 1);
|
||||||
}
|
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_can_find_one_parent_directory() {
|
|
||||||
let content = r#"
|
|
||||||
+++
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
slug = "hello-world"
|
|
||||||
+++
|
|
||||||
Hello world"#;
|
|
||||||
let res = Page::parse("posts/intro.md", content);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let page = res.unwrap();
|
|
||||||
assert_eq!(page.sections, vec!["posts".to_string()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_can_find_multiple_parent_directories() {
|
|
||||||
let content = r#"
|
|
||||||
+++
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
slug = "hello-world"
|
|
||||||
+++
|
|
||||||
Hello world"#;
|
|
||||||
let res = Page::parse("posts/intro/start.md", content);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let page = res.unwrap();
|
|
||||||
assert_eq!(page.sections, vec!["posts".to_string(), "intro".to_string()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_can_make_url_from_sections_and_slug() {
|
|
||||||
let content = r#"
|
|
||||||
+++
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
slug = "hello-world"
|
|
||||||
+++
|
|
||||||
Hello world"#;
|
|
||||||
let res = Page::parse("posts/intro/start.md", content);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let page = res.unwrap();
|
|
||||||
assert_eq!(page.meta.url.unwrap(), "/posts/intro/hello-world");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_can_make_url_from_sections_and_slug_root() {
|
|
||||||
let content = r#"
|
|
||||||
+++
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
slug = "hello-world"
|
|
||||||
+++
|
|
||||||
Hello world"#;
|
|
||||||
let res = Page::parse("start.md", content);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
let page = res.unwrap();
|
|
||||||
assert_eq!(page.meta.url.unwrap(), "/hello-world");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_errors_on_invalid_front_matter_format() {
|
|
||||||
let content = r#"
|
|
||||||
title = "Hello"
|
|
||||||
description = "hey there"
|
|
||||||
slug = "hello-world"
|
|
||||||
+++
|
|
||||||
Hello world"#;
|
|
||||||
let res = Page::parse("start.md", content);
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
97
src/section.rs
Normal file
97
src/section.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::result::Result as StdResult;
|
||||||
|
|
||||||
|
use tera::{Tera, Context};
|
||||||
|
use serde::ser::{SerializeStruct, self};
|
||||||
|
|
||||||
|
use config::Config;
|
||||||
|
use front_matter::{FrontMatter, split_content};
|
||||||
|
use errors::{Result, ResultExt};
|
||||||
|
use utils::{read_file, find_content_components};
|
||||||
|
use page::Page;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Section {
|
||||||
|
/// The _index.md full path
|
||||||
|
pub file_path: PathBuf,
|
||||||
|
/// Path of the directory containing the _index.md file
|
||||||
|
pub parent_path: PathBuf,
|
||||||
|
/// The folder names from `content` to this section file
|
||||||
|
pub components: Vec<String>,
|
||||||
|
/// The relative URL of the page
|
||||||
|
pub url: String,
|
||||||
|
/// The full URL for that page
|
||||||
|
pub permalink: String,
|
||||||
|
/// The front matter meta-data
|
||||||
|
pub meta: FrontMatter,
|
||||||
|
/// All direct pages of that section
|
||||||
|
pub pages: Vec<Page>,
|
||||||
|
/// All direct subsections
|
||||||
|
pub subsections: Vec<Section>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Section {
|
||||||
|
pub fn new(file_path: &Path, meta: FrontMatter) -> Section {
|
||||||
|
Section {
|
||||||
|
file_path: file_path.to_path_buf(),
|
||||||
|
parent_path: file_path.parent().unwrap().to_path_buf(),
|
||||||
|
components: vec![],
|
||||||
|
url: "".to_string(),
|
||||||
|
permalink: "".to_string(),
|
||||||
|
meta: meta,
|
||||||
|
pages: vec![],
|
||||||
|
subsections: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Section> {
|
||||||
|
let (meta, _) = split_content(file_path, content)?;
|
||||||
|
let mut section = Section::new(file_path, meta);
|
||||||
|
section.components = find_content_components(§ion.file_path);
|
||||||
|
section.url = section.components.join("/");
|
||||||
|
section.permalink = section.components.join("/");
|
||||||
|
|
||||||
|
section.permalink = config.make_permalink(§ion.url);
|
||||||
|
|
||||||
|
Ok(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read and parse a .md file into a Page struct
|
||||||
|
pub fn from_file<P: AsRef<Path>>(path: P, config: &Config) -> Result<Section> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let content = read_file(path)?;
|
||||||
|
|
||||||
|
Section::parse(path, &content, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the page using the default layout, unless specified in front-matter
|
||||||
|
pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> {
|
||||||
|
let tpl_name = match self.meta.template {
|
||||||
|
Some(ref l) => l.to_string(),
|
||||||
|
None => "section.html".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: create a helper to create context to ensure all contexts
|
||||||
|
// have the same names
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.add("config", config);
|
||||||
|
context.add("section", self);
|
||||||
|
|
||||||
|
tera.render(&tpl_name, &context)
|
||||||
|
.chain_err(|| format!("Failed to render section '{}'", self.file_path.display()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ser::Serialize for Section {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer {
|
||||||
|
let mut state = serializer.serialize_struct("section", 6)?;
|
||||||
|
state.serialize_field("title", &self.meta.title)?;
|
||||||
|
state.serialize_field("description", &self.meta.description)?;
|
||||||
|
state.serialize_field("url", &format!("/{}", self.url))?;
|
||||||
|
state.serialize_field("permalink", &self.permalink)?;
|
||||||
|
state.serialize_field("pages", &self.pages)?;
|
||||||
|
state.serialize_field("subsections", &self.subsections)?;
|
||||||
|
state.end()
|
||||||
|
}
|
||||||
|
}
|
449
src/site.rs
Normal file
449
src/site.rs
Normal file
|
@ -0,0 +1,449 @@
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
use std::fs::{remove_dir_all, copy, remove_file};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use glob::glob;
|
||||||
|
use tera::{Tera, Context};
|
||||||
|
use slug::slugify;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
use errors::{Result, ResultExt};
|
||||||
|
use config::{Config, get_config};
|
||||||
|
use page::{Page};
|
||||||
|
use utils::{create_file, create_directory};
|
||||||
|
use section::{Section};
|
||||||
|
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref GUTENBERG_TERA: Tera = {
|
||||||
|
let mut tera = Tera::default();
|
||||||
|
tera.add_raw_templates(vec![
|
||||||
|
("rss.xml", include_str!("templates/rss.xml")),
|
||||||
|
("sitemap.xml", include_str!("templates/sitemap.xml")),
|
||||||
|
]).unwrap();
|
||||||
|
tera
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum RenderList {
|
||||||
|
Tags,
|
||||||
|
Categories,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A tag or category
|
||||||
|
#[derive(Debug, Serialize, PartialEq)]
|
||||||
|
struct ListItem {
|
||||||
|
name: String,
|
||||||
|
slug: String,
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListItem {
|
||||||
|
pub fn new(name: &str, count: usize) -> ListItem {
|
||||||
|
ListItem {
|
||||||
|
name: name.to_string(),
|
||||||
|
slug: slugify(name),
|
||||||
|
count: count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Site {
|
||||||
|
pub base_path: PathBuf,
|
||||||
|
pub config: Config,
|
||||||
|
pub pages: HashMap<PathBuf, Page>,
|
||||||
|
pub sections: BTreeMap<PathBuf, Section>,
|
||||||
|
pub templates: Tera,
|
||||||
|
live_reload: bool,
|
||||||
|
output_path: PathBuf,
|
||||||
|
pub tags: HashMap<String, Vec<PathBuf>>,
|
||||||
|
pub categories: HashMap<String, Vec<PathBuf>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Site {
|
||||||
|
/// Parse a site at the given path. Defaults to the current dir
|
||||||
|
/// Passing in a path is only used in tests
|
||||||
|
pub fn new<P: AsRef<Path>>(path: P) -> Result<Site> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
let tpl_glob = format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*");
|
||||||
|
let mut tera = Tera::new(&tpl_glob).chain_err(|| "Error parsing templates")?;
|
||||||
|
tera.extend(&GUTENBERG_TERA)?;
|
||||||
|
|
||||||
|
let mut site = Site {
|
||||||
|
base_path: path.to_path_buf(),
|
||||||
|
config: get_config(path),
|
||||||
|
pages: HashMap::new(),
|
||||||
|
sections: BTreeMap::new(),
|
||||||
|
templates: tera,
|
||||||
|
live_reload: false,
|
||||||
|
output_path: PathBuf::from("public"),
|
||||||
|
tags: HashMap::new(),
|
||||||
|
categories: HashMap::new(),
|
||||||
|
};
|
||||||
|
site.parse()?;
|
||||||
|
|
||||||
|
Ok(site)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What the function name says
|
||||||
|
pub fn enable_live_reload(&mut self) {
|
||||||
|
self.live_reload = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by tests to change the output path to a tmp dir
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn set_output_path<P: AsRef<Path>>(&mut self, path: P) {
|
||||||
|
self.output_path = path.as_ref().to_path_buf();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads all .md files in the `content` directory and create pages
|
||||||
|
/// out of them
|
||||||
|
pub fn parse(&mut self) -> Result<()> {
|
||||||
|
let path = self.base_path.to_string_lossy().replace("\\", "/");
|
||||||
|
let content_glob = format!("{}/{}", path, "content/**/*.md");
|
||||||
|
|
||||||
|
// parent_dir -> Section
|
||||||
|
let mut sections = BTreeMap::new();
|
||||||
|
|
||||||
|
// Glob is giving us the result order so _index will show up first
|
||||||
|
// for each directory
|
||||||
|
for entry in glob(&content_glob).unwrap().filter_map(|e| e.ok()) {
|
||||||
|
let path = entry.as_path();
|
||||||
|
|
||||||
|
if path.file_name().unwrap() == "_index.md" {
|
||||||
|
let section = Section::from_file(&path, &self.config)?;
|
||||||
|
sections.insert(section.parent_path.clone(), section);
|
||||||
|
} else {
|
||||||
|
let page = Page::from_file(&path, &self.config)?;
|
||||||
|
if sections.contains_key(&page.parent_path) {
|
||||||
|
sections.get_mut(&page.parent_path).unwrap().pages.push(page.clone());
|
||||||
|
}
|
||||||
|
self.pages.insert(page.file_path.clone(), page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Find out the direct subsections of each subsection if there are some
|
||||||
|
let mut grandparent_paths = HashMap::new();
|
||||||
|
for section in sections.values() {
|
||||||
|
let grand_parent = section.parent_path.parent().unwrap().to_path_buf();
|
||||||
|
grandparent_paths.entry(grand_parent).or_insert_with(|| vec![]).push(section.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (parent_path, section) in &mut sections {
|
||||||
|
section.pages.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||||
|
|
||||||
|
match grandparent_paths.get(parent_path) {
|
||||||
|
Some(paths) => section.subsections.extend(paths.clone()),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sections = sections;
|
||||||
|
self.parse_tags_and_categories();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Separated from `parse` for easier testing
|
||||||
|
pub fn parse_tags_and_categories(&mut self) {
|
||||||
|
for page in self.pages.values() {
|
||||||
|
if let Some(ref category) = page.meta.category {
|
||||||
|
self.categories
|
||||||
|
.entry(category.to_string())
|
||||||
|
.or_insert_with(|| vec![])
|
||||||
|
.push(page.file_path.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref tags) = page.meta.tags {
|
||||||
|
for tag in tags {
|
||||||
|
self.tags
|
||||||
|
.entry(tag.to_string())
|
||||||
|
.or_insert_with(|| vec![])
|
||||||
|
.push(page.file_path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inject live reload script tag if in live reload mode
|
||||||
|
fn inject_livereload(&self, html: String) -> String {
|
||||||
|
if self.live_reload {
|
||||||
|
return html.replace(
|
||||||
|
"</body>",
|
||||||
|
r#"<script src="/livereload.js?port=1112&mindelay=10"></script></body>"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
html
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy the content of the `static` folder into the `public` folder
|
||||||
|
///
|
||||||
|
/// TODO: only copy one file if possible because that would be a waste
|
||||||
|
/// to do re-copy the whole thing. Benchmark first to see if it's a big difference
|
||||||
|
pub fn copy_static_directory(&self) -> Result<()> {
|
||||||
|
let from = Path::new("static");
|
||||||
|
let target = Path::new("public");
|
||||||
|
|
||||||
|
for entry in WalkDir::new(from).into_iter().filter_map(|e| e.ok()) {
|
||||||
|
let relative_path = entry.path().strip_prefix(&from).unwrap();
|
||||||
|
let target_path = {
|
||||||
|
let mut target_path = target.to_path_buf();
|
||||||
|
target_path.push(relative_path);
|
||||||
|
target_path
|
||||||
|
};
|
||||||
|
|
||||||
|
if entry.path().is_dir() {
|
||||||
|
if !target_path.exists() {
|
||||||
|
create_directory(&target_path)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if target_path.exists() {
|
||||||
|
remove_file(&target_path)?;
|
||||||
|
}
|
||||||
|
copy(entry.path(), &target_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes the `public` directory if it exists
|
||||||
|
pub fn clean(&self) -> Result<()> {
|
||||||
|
if Path::new("public").exists() {
|
||||||
|
// Delete current `public` directory so we can start fresh
|
||||||
|
remove_dir_all("public").chain_err(|| "Couldn't delete `public` directory")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rebuild_after_content_change(&mut self) -> Result<()> {
|
||||||
|
self.parse()?;
|
||||||
|
self.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rebuild_after_template_change(&mut self) -> Result<()> {
|
||||||
|
self.templates.full_reload()?;
|
||||||
|
self.build_pages()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_pages(&self) -> Result<()> {
|
||||||
|
let public = self.output_path.clone();
|
||||||
|
if !public.exists() {
|
||||||
|
create_directory(&public)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pages = vec![];
|
||||||
|
|
||||||
|
// First we render the pages themselves
|
||||||
|
for page in self.pages.values() {
|
||||||
|
// Copy the nesting of the content directory if we have sections for that page
|
||||||
|
let mut current_path = public.to_path_buf();
|
||||||
|
|
||||||
|
for component in page.url.split('/') {
|
||||||
|
current_path.push(component);
|
||||||
|
|
||||||
|
if !current_path.exists() {
|
||||||
|
create_directory(¤t_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the folder exists
|
||||||
|
create_directory(¤t_path)?;
|
||||||
|
|
||||||
|
// Finally, create a index.html file there with the page rendered
|
||||||
|
let output = page.render_html(&self.templates, &self.config)?;
|
||||||
|
create_file(current_path.join("index.html"), &self.inject_livereload(output))?;
|
||||||
|
|
||||||
|
// Copy any asset we found previously into the same directory as the index.html
|
||||||
|
for asset in &page.assets {
|
||||||
|
let asset_path = asset.as_path();
|
||||||
|
copy(&asset_path, ¤t_path.join(asset_path.file_name().unwrap()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pages.push(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputting categories and pages
|
||||||
|
self.render_categories_and_tags(RenderList::Categories)?;
|
||||||
|
self.render_categories_and_tags(RenderList::Tags)?;
|
||||||
|
|
||||||
|
// And finally the index page
|
||||||
|
let mut context = Context::new();
|
||||||
|
pages.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||||
|
context.add("pages", &pages);
|
||||||
|
context.add("config", &self.config);
|
||||||
|
let index = self.templates.render("index.html", &context)?;
|
||||||
|
create_file(public.join("index.html"), &self.inject_livereload(index))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the site to the `public` directory after deleting it
|
||||||
|
pub fn build(&self) -> Result<()> {
|
||||||
|
self.clean()?;
|
||||||
|
self.build_pages()?;
|
||||||
|
self.render_sitemap()?;
|
||||||
|
|
||||||
|
if self.config.generate_rss.unwrap() {
|
||||||
|
self.render_rss_feed()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render_sections()?;
|
||||||
|
self.copy_static_directory()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render the /{categories, list} pages and each individual category/tag page
|
||||||
|
/// They are the same thing fundamentally, a list of pages with something in common
|
||||||
|
fn render_categories_and_tags(&self, kind: RenderList) -> Result<()> {
|
||||||
|
let items = match kind {
|
||||||
|
RenderList::Categories => &self.categories,
|
||||||
|
RenderList::Tags => &self.tags,
|
||||||
|
};
|
||||||
|
|
||||||
|
if items.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (list_tpl_name, single_tpl_name, name, var_name) = if kind == RenderList::Categories {
|
||||||
|
("categories.html", "category.html", "categories", "category")
|
||||||
|
} else {
|
||||||
|
("tags.html", "tag.html", "tags", "tag")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the categories/tags directory first
|
||||||
|
let public = self.output_path.clone();
|
||||||
|
let mut output_path = public.to_path_buf();
|
||||||
|
output_path.push(name);
|
||||||
|
create_directory(&output_path)?;
|
||||||
|
|
||||||
|
// Then render the index page for that kind.
|
||||||
|
// We sort by number of page in that category/tag
|
||||||
|
let mut sorted_items = vec![];
|
||||||
|
for (item, count) in Vec::from_iter(items).into_iter().map(|(a, b)| (a, b.len())) {
|
||||||
|
sorted_items.push(ListItem::new(&item, count));
|
||||||
|
}
|
||||||
|
sorted_items.sort_by(|a, b| b.count.cmp(&a.count));
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.add(name, &sorted_items);
|
||||||
|
context.add("config", &self.config);
|
||||||
|
// And render it immediately
|
||||||
|
let list_output = self.templates.render(list_tpl_name, &context)?;
|
||||||
|
create_file(output_path.join("index.html"), &self.inject_livereload(list_output))?;
|
||||||
|
|
||||||
|
// Now, each individual item
|
||||||
|
for (item_name, pages_paths) in items.iter() {
|
||||||
|
let mut pages: Vec<&Page> = self.pages
|
||||||
|
.iter()
|
||||||
|
.filter(|&(path, _)| pages_paths.contains(&path))
|
||||||
|
.map(|(_, page)| page)
|
||||||
|
.collect();
|
||||||
|
pages.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||||
|
|
||||||
|
let mut context = Context::new();
|
||||||
|
let slug = slugify(&item_name);
|
||||||
|
context.add(var_name, &item_name);
|
||||||
|
context.add(&format!("{}_slug", var_name), &slug);
|
||||||
|
context.add("pages", &pages);
|
||||||
|
context.add("config", &self.config);
|
||||||
|
let single_output = self.templates.render(single_tpl_name, &context)?;
|
||||||
|
|
||||||
|
create_directory(&output_path.join(&slug))?;
|
||||||
|
create_file(
|
||||||
|
output_path.join(&slug).join("index.html"),
|
||||||
|
&self.inject_livereload(single_output)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_sitemap(&self) -> Result<()> {
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.add("pages", &self.pages.values().collect::<Vec<&Page>>());
|
||||||
|
context.add("sections", &self.sections.values().collect::<Vec<&Section>>());
|
||||||
|
|
||||||
|
let mut categories = vec![];
|
||||||
|
if !self.categories.is_empty() {
|
||||||
|
categories.push(self.config.make_permalink("categories"));
|
||||||
|
for category in self.categories.keys() {
|
||||||
|
categories.push(
|
||||||
|
self.config.make_permalink(&format!("categories/{}", slugify(category)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.add("categories", &categories);
|
||||||
|
|
||||||
|
let mut tags = vec![];
|
||||||
|
if !self.tags.is_empty() {
|
||||||
|
tags.push(self.config.make_permalink("tags"));
|
||||||
|
for tag in self.tags.keys() {
|
||||||
|
tags.push(
|
||||||
|
self.config.make_permalink(&format!("tags/{}", slugify(tag)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.add("tags", &tags);
|
||||||
|
|
||||||
|
let sitemap = self.templates.render("sitemap.xml", &context)?;
|
||||||
|
|
||||||
|
create_file(self.output_path.join("sitemap.xml"), &sitemap)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_rss_feed(&self) -> Result<()> {
|
||||||
|
let mut context = Context::new();
|
||||||
|
let mut pages = self.pages.values()
|
||||||
|
.filter(|p| p.meta.date.is_some())
|
||||||
|
.take(15) // limit to the last 15 elements
|
||||||
|
.collect::<Vec<&Page>>();
|
||||||
|
|
||||||
|
// Don't generate a RSS feed if none of the pages has a date
|
||||||
|
if pages.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pages.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||||
|
context.add("pages", &pages);
|
||||||
|
context.add("last_build_date", &pages[0].meta.date);
|
||||||
|
context.add("config", &self.config);
|
||||||
|
|
||||||
|
let rss_feed_url = if self.config.base_url.ends_with('/') {
|
||||||
|
format!("{}{}", self.config.base_url, "feed.xml")
|
||||||
|
} else {
|
||||||
|
format!("{}/{}", self.config.base_url, "feed.xml")
|
||||||
|
};
|
||||||
|
context.add("feed_url", &rss_feed_url);
|
||||||
|
|
||||||
|
let sitemap = self.templates.render("rss.xml", &context)?;
|
||||||
|
|
||||||
|
create_file(self.output_path.join("rss.xml"), &sitemap)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_sections(&self) -> Result<()> {
|
||||||
|
let public = self.output_path.clone();
|
||||||
|
|
||||||
|
for section in self.sections.values() {
|
||||||
|
let mut output_path = public.to_path_buf();
|
||||||
|
for component in §ion.components {
|
||||||
|
output_path.push(component);
|
||||||
|
|
||||||
|
if !output_path.exists() {
|
||||||
|
create_directory(&output_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = section.render_html(&self.templates, &self.config)?;
|
||||||
|
create_file(output_path.join("index.html"), &self.inject_livereload(output))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
20
src/templates/rss.xml
Normal file
20
src/templates/rss.xml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
||||||
|
<channel>
|
||||||
|
<title>{{ config.title }}</title>
|
||||||
|
<link>{{ config.base_url }}</link>
|
||||||
|
<description>{{ config.description }}</description>
|
||||||
|
<generator>Gutenberg</generator>
|
||||||
|
<language>{{ config.language_code }}</language>
|
||||||
|
<atom:link href="{{ feed_url }}" rel="self" type="application/rss+xml"/>
|
||||||
|
<lastBuildDate>{{ last_build_date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</lastBuildDate>
|
||||||
|
{% for page in pages %}
|
||||||
|
<item>
|
||||||
|
<title>{{ page.title }}</title>
|
||||||
|
<pubDate>{{ page.date | date(format="%a, %d %b %Y %H:%M:%S %z") }}</pubDate>
|
||||||
|
<link>{{ page.permalink }}</link>
|
||||||
|
<guid>{{ page.permalink }}</guid>
|
||||||
|
<description>"{{ page.content | escape }}"</description>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</channel>
|
||||||
|
</rss>
|
25
src/templates/sitemap.xml
Normal file
25
src/templates/sitemap.xml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
{% for page in pages %}
|
||||||
|
<url>
|
||||||
|
<loc>{{ page.permalink | safe }}</loc>
|
||||||
|
{% if page.date %}
|
||||||
|
<lastmod>{{ page.date }}</lastmod>
|
||||||
|
{% endif %}
|
||||||
|
</url>
|
||||||
|
{% endfor %}
|
||||||
|
{% for section in sections %}
|
||||||
|
<url>
|
||||||
|
<loc>{{ section.permalink | safe }}</loc>
|
||||||
|
</url>
|
||||||
|
{% endfor %}
|
||||||
|
{% for category in categories %}
|
||||||
|
<url>
|
||||||
|
<loc>{{ category | safe }}</loc>
|
||||||
|
</url>
|
||||||
|
{% endfor %}
|
||||||
|
{% for tag in tags %}
|
||||||
|
<url>
|
||||||
|
<loc>{{ tag | safe }}</loc>
|
||||||
|
</url>
|
||||||
|
{% endfor %}
|
||||||
|
</urlset>
|
64
src/utils.rs
64
src/utils.rs
|
@ -1,8 +1,8 @@
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::fs::{File};
|
use std::fs::{File, create_dir};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use errors::Result;
|
use errors::{Result, ResultExt};
|
||||||
|
|
||||||
|
|
||||||
pub fn create_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
|
pub fn create_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
|
||||||
|
@ -10,3 +10,63 @@ pub fn create_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
|
||||||
file.write_all(content.as_bytes())?;
|
file.write_all(content.as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Very similar to `create_dir` from the std except it checks if the folder
|
||||||
|
/// exists before creating it
|
||||||
|
pub fn create_directory<P: AsRef<Path>>(path: P) -> Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
if !path.exists() {
|
||||||
|
create_dir(path)
|
||||||
|
.chain_err(|| format!("Was not able to create folder {}", path.display()))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Return the content of a file, with error handling added
|
||||||
|
pub fn read_file<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
let mut content = String::new();
|
||||||
|
File::open(path)
|
||||||
|
.chain_err(|| format!("Failed to open '{:?}'", path.display()))?
|
||||||
|
.read_to_string(&mut content)?;
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Takes a full path to a .md and returns only the components after the `content` directory
|
||||||
|
/// Will not return the filename as last component
|
||||||
|
pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let mut is_in_content = false;
|
||||||
|
let mut components = vec![];
|
||||||
|
|
||||||
|
for section in path.parent().unwrap().components() {
|
||||||
|
let component = section.as_ref().to_string_lossy();
|
||||||
|
|
||||||
|
if is_in_content {
|
||||||
|
components.push(component.to_string());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if component == "content" {
|
||||||
|
is_in_content = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
components
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{find_content_components};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_content_components() {
|
||||||
|
let res = find_content_components("/home/vincent/code/site/content/posts/tutorials/python.md");
|
||||||
|
assert_eq!(res, ["posts".to_string(), "tutorials".to_string()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
593
sublime_themes/base16-ocean-dark.tmTheme
Normal file
593
sublime_themes/base16-ocean-dark.tmTheme
Normal file
|
@ -0,0 +1,593 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>author</key>
|
||||||
|
<string>Chris Kempson (http://chriskempson.com)</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Base16 Ocean Dark</string>
|
||||||
|
<key>semanticClass</key>
|
||||||
|
<string>base16.ocean.dark</string>
|
||||||
|
<key>colorSpaceName</key>
|
||||||
|
<string>sRGB</string>
|
||||||
|
<key>gutterSettings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#343d46</string>
|
||||||
|
<key>divider</key>
|
||||||
|
<string>#343d46</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#65737e</string>
|
||||||
|
<key>selectionBackground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
<key>selectionForeground</key>
|
||||||
|
<string>#a7adba</string>
|
||||||
|
</dict>
|
||||||
|
<key>settings</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#2b303b</string>
|
||||||
|
<key>caret</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
<key>invisibles</key>
|
||||||
|
<string>#65737e</string>
|
||||||
|
<key>lineHighlight</key>
|
||||||
|
<string>#65737e30</string>
|
||||||
|
<key>selection</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
<key>guide</key>
|
||||||
|
<string>#3b5364</string>
|
||||||
|
<key>activeGuide</key>
|
||||||
|
<string>#96b5b4</string>
|
||||||
|
<key>stackGuide</key>
|
||||||
|
<string>#343d46</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Text</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable.parameter.function</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Comments</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>comment, punctuation.definition.comment</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#65737e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Punctuation</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.definition.string, punctuation.definition.variable, punctuation.definition.string, punctuation.definition.parameters, punctuation.definition.string, punctuation.definition.array</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Delimiters</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>none</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Operators</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.operator</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Keywords</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Variables</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Functions</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.function, meta.require, support.function.any-method</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8fa1b3</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Classes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.class, entity.name.class, entity.name.type.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#ebcb8b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Classes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#eff1f5</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Methods</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.other.special-method</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8fa1b3</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Storage</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>storage</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Support</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.function</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#96b5b4</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Strings, Inherited Class</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string, constant.other.symbol, entity.other.inherited-class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#a3be8c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Integers</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.numeric</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Floats</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>none</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>none</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constants</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Tags</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.tag</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Attributes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.other.attribute-name</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Attribute IDs</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.other.attribute-name.id, punctuation.definition.entity</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8fa1b3</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Selector</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.selector</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Values</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>none</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Headings</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.heading punctuation.definition.heading, entity.name.section</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8fa1b3</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Units</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.other.unit</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Bold</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.bold, punctuation.definition.bold</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>bold</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#ebcb8b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Italic</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.italic, punctuation.definition.italic</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Code</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.raw.inline</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#a3be8c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Link Text</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.other.link</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Link Url</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.link</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Lists</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.list</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Quotes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.quote</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Separator</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.separator</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Inserted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.inserted, markup.inserted.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#a3be8c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Deleted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.deleted, markup.deleted.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Changed</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.changed, markup.changed.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Ignored</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.ignored, markup.ignored.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Untracked</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.untracked, markup.untracked.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Colors</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other.color</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#96b5b4</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Regular Expressions</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.regexp</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#96b5b4</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Escape Characters</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.character.escape</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#96b5b4</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Embedded</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.section.embedded, variable.interpolation</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#ab7967</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Invalid</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>invalid.illegal</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#2b303b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter deleted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.deleted.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#F92672</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter inserted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.inserted.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#A6E22E</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter changed</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.changed.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#967EFB</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter ignored</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.ignored.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#565656</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter untracked</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.untracked.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#565656</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>uuid</key>
|
||||||
|
<string>59c1e2f2-7b41-46f9-91f2-1b4c6f5866f7</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
589
sublime_themes/base16-ocean-light.tmTheme
Normal file
589
sublime_themes/base16-ocean-light.tmTheme
Normal file
|
@ -0,0 +1,589 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>author</key>
|
||||||
|
<string>Chris Kempson (http://chriskempson.com)</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Base16 Ocean Light</string>
|
||||||
|
<key>semanticClass</key>
|
||||||
|
<string>base16.ocean.light</string>
|
||||||
|
<key>colorSpaceName</key>
|
||||||
|
<string>sRGB</string>
|
||||||
|
<key>gutterSettings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#eff1f5</string>
|
||||||
|
<key>divider</key>
|
||||||
|
<string>#eff1f5</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
<key>selectionBackground</key>
|
||||||
|
<string>#eff1f5</string>
|
||||||
|
<key>selectionForeground</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
</dict>
|
||||||
|
<key>settings</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#eff1f5</string>
|
||||||
|
<key>caret</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
<key>invisibles</key>
|
||||||
|
<string>#dfe1e8</string>
|
||||||
|
<key>lineHighlight</key>
|
||||||
|
<string>#a7adba30</string>
|
||||||
|
<key>selection</key>
|
||||||
|
<string>#dfe1e8</string>
|
||||||
|
<key>shadow</key>
|
||||||
|
<string>#dfe1e8</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Text</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable.parameter.function</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Comments</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>comment, punctuation.definition.comment</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#a7adba</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Punctuation</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.definition.string, punctuation.definition.variable, punctuation.definition.string, punctuation.definition.parameters, punctuation.definition.string, punctuation.definition.array</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Delimiters</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>none</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Operators</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.operator</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Keywords</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Variables</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Functions</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.function, meta.require, support.function.any-method</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8fa1b3</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Classes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.class, entity.name.class, entity.name.type.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Classes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#343d46</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Methods</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.other.special-method</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8fa1b3</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Storage</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>storage</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Support</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.function</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#96b5b4</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Strings, Inherited Class</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string, constant.other.symbol, entity.other.inherited-class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#a3be8c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Integers</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.numeric</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Floats</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>none</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Boolean</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>none</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constants</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Tags</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.tag</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Attributes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.other.attribute-name</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Attribute IDs</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.other.attribute-name.id, punctuation.definition.entity</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8fa1b3</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Selector</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.selector</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Values</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>none</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Headings</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.heading punctuation.definition.heading, entity.name.section</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8fa1b3</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Units</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.other.unit</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Bold</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.bold, punctuation.definition.bold</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>bold</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Italic</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.italic, punctuation.definition.italic</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Code</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.raw.inline</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#a3be8c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Link Text</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.other.link</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Link Url</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.link</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Lists</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.list</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Quotes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.quote</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d08770</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Separator</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.separator</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#dfe1e8</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#4f5b66</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Inserted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.inserted, markup.inserted.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#a3be8c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Deleted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.deleted, markup.deleted.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Changed</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.changed, markup.changed.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b48ead</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Ignored</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.ignored, markup.ignored.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Untracked</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.untracked, markup.untracked.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#c0c5ce</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Colors</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other.color</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#96b5b4</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Regular Expressions</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.regexp</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#96b5b4</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Escape Characters</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.character.escape</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#96b5b4</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Embedded</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.section.embedded, variable.interpolation</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#ab7967</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Invalid</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>invalid.illegal</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#bf616a</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#eff1f5</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter deleted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.deleted.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#F92672</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter inserted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.inserted.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#A6E22E</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter changed</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.changed.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#967EFB</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter ignored</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.ignored.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#565656</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>GitGutter untracked</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.untracked.git_gutter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#565656</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>uuid</key>
|
||||||
|
<string>52997033-52ea-4534-af9f-7572613947d8</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
766
sublime_themes/gruvbox-dark.tmTheme
Normal file
766
sublime_themes/gruvbox-dark.tmTheme
Normal file
|
@ -0,0 +1,766 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>comment</key>
|
||||||
|
<string>Based on original gruvbox color scheme.</string>
|
||||||
|
<key>author</key>
|
||||||
|
<string>peaceant</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>gruvbox</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
|
||||||
|
<key>caret</key>
|
||||||
|
<string>#fcf9e3</string>
|
||||||
|
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fdf4c1aa</string>
|
||||||
|
|
||||||
|
<key>invisibles</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
|
||||||
|
<key>lineHighlight</key>
|
||||||
|
<string>#3c3836</string>
|
||||||
|
|
||||||
|
<key>selection</key>
|
||||||
|
<string>#504945</string>
|
||||||
|
|
||||||
|
<key>bracketContentsForeground</key>
|
||||||
|
<string>#928374</string>
|
||||||
|
|
||||||
|
<key>bracketsForeground</key>
|
||||||
|
<string>#d5c4a1</string>
|
||||||
|
|
||||||
|
<key>guide</key>
|
||||||
|
<string>#3c3836</string>
|
||||||
|
|
||||||
|
<key>activeGuide</key>
|
||||||
|
<string>#a89984</string>
|
||||||
|
|
||||||
|
<key>stackGuide</key>
|
||||||
|
<string>#665c54</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Punctuation</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.definition.tag</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#83a598</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Punctuation</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.definition.entity</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d3869b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d3869b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant escape</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.character.escape</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant other</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fdf4c1</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Entity</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Keyword</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.operator.comparison, keyword.operator, keyword.operator.symbolic, keyword.operator.string, keyword.operator.assignment, keyword.operator.arithmetic, keyword.operator.class, keyword.operator.key, keyword.operator.logical</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fe8019</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Keyword</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword, keyword.operator.new, keyword.other, keyword.control</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fa5c4b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Storage</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>storage</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fa5c4b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>String</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string -string.unquoted.old-plist -string.unquoted.heredoc, string.unquoted.heredoc string</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Comment</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>comment</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#928374</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Regexp</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.regexp constant.character.escape</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Support</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Variable</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fdf4c1</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Lang Variable</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable.language</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fdf4c1</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Function Call</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.function-call</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fdf4c1</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Invalid</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>invalid</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#932b1e</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fdf4c1</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Embedded Source</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>text source, string.unquoted.heredoc, source source</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fdf4c1</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>String embedded-source</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.quoted source</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>String constant</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Support.constant</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.constant</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Support.class</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Meta.tag.A</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.tag</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>bold</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Inner tag</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.tag, meta.tag entity</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css colors</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other.color.rgb-value</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#83a598</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css tag-name</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.selector.css entity.name.tag</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fa5c4b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css#id</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.selector.css, entity.other.attribute-name.id</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css.class</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.selector.css entity.other.attribute-name.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css property-name:</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.type.property-name.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css @at-rule</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.preprocessor.at-rule keyword.control.at-rule</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css additional-constants</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.property-value constant</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css additional-constants</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.property-value support.constant.named-color.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fe8019</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css constructor.argument</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.constructor.argument.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- diff -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>diff.header</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.diff, meta.diff.header</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#83a598</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>diff.deleted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.deleted</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fa5c4b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>diff.changed</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.changed</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>diff.inserted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.inserted</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- markup -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Bold Markup</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.bold</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>bold</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Italic Markup</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.italic</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Heading Markup</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.heading</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>bold</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Language Specific -->
|
||||||
|
|
||||||
|
<!-- PHP -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>PHP: class name</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.type.class.php</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>PHP: Comment</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.other.phpdoc</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#928374</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<!-- CSS -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CSS: numbers</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.numeric.css, keyword.other.unit.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d3869b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CSS: entity dot, hash, comma, etc.</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.definition.entity.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<!-- JS -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>JS: variable</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable.language.js</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>JS: unquoted labe</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.unquoted.label.js</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fdf4c1</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<!-- SQL -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant other sql</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other.table-name.sql</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant other sql</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other.database-name.sql</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<!-- Plugins -->
|
||||||
|
|
||||||
|
<!-- dired plugin -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>dired directory</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>storage.type.dired.item.directory, dired.item.directory</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<!-- orgmode plugin -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode link</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.link</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>underline</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode page</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.page</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b8bb26</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode break</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.break</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#d3869b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode headline</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.headline</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8ec07c</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode tack</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.tack</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode follow up</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.follow_up</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode checkbox</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.checkbox</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode checkbox summary</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.checkbox.summary</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fabd2f</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode tags</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.tags</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#fa5c4b</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
</array>
|
||||||
|
<key>uuid</key>
|
||||||
|
<string>06CD1FB2-A00A-4F8C-97B2-60E131980454</string>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
</plist>
|
774
sublime_themes/gruvbox-light.tmTheme
Normal file
774
sublime_themes/gruvbox-light.tmTheme
Normal file
|
@ -0,0 +1,774 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>comment</key>
|
||||||
|
<string>Based on original gruvbox color scheme.</string>
|
||||||
|
<key>author</key>
|
||||||
|
<string>Martin Radimec</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>gruvbox</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#FCF0CA</string>
|
||||||
|
|
||||||
|
<key>caret</key>
|
||||||
|
<string>#3C3836</string>
|
||||||
|
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828aa</string>
|
||||||
|
|
||||||
|
<key>invisibles</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
|
||||||
|
<key>lineHighlight</key>
|
||||||
|
<string>#EDDAB5</string>
|
||||||
|
|
||||||
|
<key>selection</key>
|
||||||
|
<string>#D6C3A3</string>
|
||||||
|
|
||||||
|
<key>bracketContentsForeground</key>
|
||||||
|
<string>#928374</string>
|
||||||
|
|
||||||
|
<key>bracketsForeground</key>
|
||||||
|
<string>#d5c4a1</string>
|
||||||
|
|
||||||
|
<key>guide</key>
|
||||||
|
<string>#EDDAB5</string>
|
||||||
|
|
||||||
|
<key>activeGuide</key>
|
||||||
|
<string>#7c6f64</string>
|
||||||
|
|
||||||
|
<key>stackGuide</key>
|
||||||
|
<string>#BEAD95</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Punctuation</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.definition.tag</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#076678</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Punctuation</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.definition.entity</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8f3f71</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8f3f71</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant escape</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.character.escape</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant other</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Entity</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Keyword</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.operator.comparison, keyword.operator, keyword.operator.symbolic, keyword.operator.string, keyword.operator.assignment, keyword.operator.arithmetic, keyword.operator.class, keyword.operator.key, keyword.operator.logical</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#B23C15</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Keyword</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword, keyword.operator.new, keyword.other, keyword.control</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#9d0006</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Storage</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>storage</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#9d0006</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>String</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string -string.unquoted.old-plist -string.unquoted.heredoc, string.unquoted.heredoc string</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Comment</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>comment</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#928374</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Regexp</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.regexp constant.character.escape</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Support</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Variable</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Lang Variable</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable.language</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Function Call</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.function-call</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Invalid</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>invalid</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#932b1e</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Embedded Source</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>text source, string.unquoted.heredoc, source source</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>String embedded-source</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.quoted source</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>String constant</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Support.constant</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.constant</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Support.class</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Meta.tag.A</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.tag</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>bold</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Inner tag</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.tag, meta.tag entity</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css colors</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other.color.rgb-value</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#076678</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css tag-name</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.selector.css entity.name.tag</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#9d0006</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css#id</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.selector.css, entity.other.attribute-name.id</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css.class</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.selector.css entity.other.attribute-name.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css property-name:</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.type.property-name.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css @at-rule</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.preprocessor.at-rule keyword.control.at-rule</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css additional-constants</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.property-value constant</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css additional-constants</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.property-value support.constant.named-color.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#B23C15</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>css constructor.argument</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.constructor.argument.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- diff -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>diff.header</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>meta.diff, meta.diff.header</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#076678</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>diff.deleted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.deleted</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#9d0006</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>diff.changed</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.changed</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>diff.inserted</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.inserted</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- markup -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Bold Markup</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.bold</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>bold</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Italic Markup</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.italic</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Heading Markup</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>markup.heading</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>bold</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Language Specific -->
|
||||||
|
|
||||||
|
<!-- PHP -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>PHP: class name</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.type.class.php</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>PHP: Comment</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword.other.phpdoc</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#928374</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<!-- CSS -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CSS: numbers</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.numeric.css, keyword.other.unit.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8f3f71</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>CSS: entity dot, hash, comma, etc.</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>punctuation.definition.entity.css</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<!-- JS -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>JS: variable</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable.language.js</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>JS: unquoted labe</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string.unquoted.label.js</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#282828</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<!-- SQL -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant other sql</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other.table-name.sql</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Constant other sql</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.other.database-name.sql</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<!-- Plugins -->
|
||||||
|
|
||||||
|
<!-- dired plugin -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>dired directory</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>storage.type.dired.item.directory, dired.item.directory</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<!-- orgmode plugin -->
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode link</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.link</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>underline</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode page</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.page</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#79740e</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode break</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.break</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#8f3f71</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode headline</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.headline</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#407959</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode tack</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.tack</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode follow up</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.follow_up</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode checkbox</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.checkbox</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode checkbox summary</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.checkbox.summary</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#b57614</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>orgmode tags</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>orgmode.tags</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#9d0006</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
</array>
|
||||||
|
<key>uuid</key>
|
||||||
|
<string>06CD1FB2-A00A-4F8C-97B2-60E131980454</string>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
</plist>
|
1725
sublime_themes/inspired-github.tmTheme
Normal file
1725
sublime_themes/inspired-github.tmTheme
Normal file
File diff suppressed because it is too large
Load diff
1843
sublime_themes/kronuz.tmTheme
Normal file
1843
sublime_themes/kronuz.tmTheme
Normal file
File diff suppressed because it is too large
Load diff
1011
sublime_themes/material-dark.tmTheme
Normal file
1011
sublime_themes/material-dark.tmTheme
Normal file
File diff suppressed because it is too large
Load diff
1011
sublime_themes/material-light.tmTheme
Normal file
1011
sublime_themes/material-light.tmTheme
Normal file
File diff suppressed because it is too large
Load diff
297
sublime_themes/monokai.tmTheme
Normal file
297
sublime_themes/monokai.tmTheme
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<!-- Generated by: TmTheme-Editor -->
|
||||||
|
<!-- ============================================ -->
|
||||||
|
<!-- app: http://tmtheme-editor.herokuapp.com -->
|
||||||
|
<!-- code: https://github.com/aziz/tmTheme-Editor -->
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Monokai</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#272822</string>
|
||||||
|
<key>caret</key>
|
||||||
|
<string>#F8F8F0</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#F8F8F2</string>
|
||||||
|
<key>invisibles</key>
|
||||||
|
<string>#3B3A32</string>
|
||||||
|
<key>lineHighlight</key>
|
||||||
|
<string>#3E3D32</string>
|
||||||
|
<key>selection</key>
|
||||||
|
<string>#49483E</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Comment</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>comment</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#75715E</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>String</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>string</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#E6DB74</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Number</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.numeric</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#AE81FF</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Built-in constant</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.language</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#AE81FF</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>User-defined constant</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>constant.character, constant.other</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#AE81FF</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Variable</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Keyword</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>keyword</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#F92672</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Storage</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>storage</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#F92672</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Storage type</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>storage.type</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#66D9EF</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Class name</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>underline</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#A6E22E</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Inherited class</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.other.inherited-class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic underline</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#A6E22E</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Function name</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.function</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#A6E22E</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Function argument</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>variable.parameter</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#FD971F</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Tag name</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.name.tag</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#F92672</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Tag attribute</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>entity.other.attribute-name</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#A6E22E</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Library function</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.function</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#66D9EF</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Library constant</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.constant</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#66D9EF</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Library class/type</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.type, support.class</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string>italic</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#66D9EF</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Library variable</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>support.other.variable</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Invalid</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>invalid</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#F92672</string>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
<string></string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#F8F8F0</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Invalid deprecated</string>
|
||||||
|
<key>scope</key>
|
||||||
|
<string>invalid.deprecated</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>background</key>
|
||||||
|
<string>#AE81FF</string>
|
||||||
|
<key>foreground</key>
|
||||||
|
<string>#F8F8F0</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>uuid</key>
|
||||||
|
<string>D8D5E82E-3D5B-46B5-B38E-8C841C21347D</string>
|
||||||
|
<key>colorSpaceName</key>
|
||||||
|
<string>sRGB</string>
|
||||||
|
<key>semanticClass</key>
|
||||||
|
<string>theme.dark.monokai</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
1189
sublime_themes/solarized-dark.tmTheme
Normal file
1189
sublime_themes/solarized-dark.tmTheme
Normal file
File diff suppressed because it is too large
Load diff
1189
sublime_themes/solarized-light.tmTheme
Normal file
1189
sublime_themes/solarized-light.tmTheme
Normal file
File diff suppressed because it is too large
Load diff
7
test_site/config.toml
Normal file
7
test_site/config.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
title = "My site"
|
||||||
|
base_url = "https://replace-this-with-your-url.com"
|
||||||
|
highlight_code = true
|
||||||
|
|
||||||
|
|
||||||
|
[extra.author]
|
||||||
|
name = "Vincent Prouillet"
|
4
test_site/content/posts/_index.md
Normal file
4
test_site/content/posts/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
+++
|
||||||
|
title = "Posts"
|
||||||
|
description = ""
|
||||||
|
+++
|
7
test_site/content/posts/fixed-slug.md
Normal file
7
test_site/content/posts/fixed-slug.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
+++
|
||||||
|
title = "Fixed slug"
|
||||||
|
description = ""
|
||||||
|
slug = "something-else"
|
||||||
|
+++
|
||||||
|
|
||||||
|
A simple page with a slug defined
|
7
test_site/content/posts/fixed-url.md
Normal file
7
test_site/content/posts/fixed-url.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
+++
|
||||||
|
title = "Fixed URL"
|
||||||
|
description = ""
|
||||||
|
url = "a-fixed-url"
|
||||||
|
+++
|
||||||
|
|
||||||
|
A simple page with fixed url
|
6
test_site/content/posts/no-section/simple.md
Normal file
6
test_site/content/posts/no-section/simple.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Simple"
|
||||||
|
description = ""
|
||||||
|
+++
|
||||||
|
|
||||||
|
A simple page
|
6
test_site/content/posts/python.md
Normal file
6
test_site/content/posts/python.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Python in posts"
|
||||||
|
description = ""
|
||||||
|
+++
|
||||||
|
|
||||||
|
Same filename but different path
|
6
test_site/content/posts/simple.md
Normal file
6
test_site/content/posts/simple.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Simple"
|
||||||
|
description = ""
|
||||||
|
+++
|
||||||
|
|
||||||
|
A simple page
|
4
test_site/content/posts/tutorials/_index.md
Normal file
4
test_site/content/posts/tutorials/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
+++
|
||||||
|
title = "Tutorials"
|
||||||
|
description = ""
|
||||||
|
+++
|
4
test_site/content/posts/tutorials/devops/_index.md
Normal file
4
test_site/content/posts/tutorials/devops/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
+++
|
||||||
|
title = "DevOps"
|
||||||
|
description = ""
|
||||||
|
+++
|
6
test_site/content/posts/tutorials/devops/docker.md
Normal file
6
test_site/content/posts/tutorials/devops/docker.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Docker"
|
||||||
|
description = ""
|
||||||
|
+++
|
||||||
|
|
||||||
|
A simple page
|
6
test_site/content/posts/tutorials/devops/nix.md
Normal file
6
test_site/content/posts/tutorials/devops/nix.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Nix"
|
||||||
|
description = ""
|
||||||
|
+++
|
||||||
|
|
||||||
|
A simple page
|
4
test_site/content/posts/tutorials/programming/_index.md
Normal file
4
test_site/content/posts/tutorials/programming/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
+++
|
||||||
|
title = "Programming"
|
||||||
|
description = ""
|
||||||
|
+++
|
6
test_site/content/posts/tutorials/programming/python.md
Normal file
6
test_site/content/posts/tutorials/programming/python.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Python tutorial"
|
||||||
|
description = ""
|
||||||
|
+++
|
||||||
|
|
||||||
|
A simple page
|
6
test_site/content/posts/tutorials/programming/rust.md
Normal file
6
test_site/content/posts/tutorials/programming/rust.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Rust"
|
||||||
|
description = ""
|
||||||
|
+++
|
||||||
|
|
||||||
|
A simple page
|
7
test_site/content/posts/with-assets/index.md
Normal file
7
test_site/content/posts/with-assets/index.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
+++
|
||||||
|
title = "With assets"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "with-assets"
|
||||||
|
+++
|
||||||
|
|
||||||
|
Hello world
|
0
test_site/content/posts/with-assets/with.js
Normal file
0
test_site/content/posts/with-assets/with.js
Normal file
0
test_site/static/scripts/hello.js
Normal file
0
test_site/static/scripts/hello.js
Normal file
3
test_site/static/site.css
Normal file
3
test_site/static/site.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
color: red;
|
||||||
|
}
|
3
test_site/templates/categories.html
Normal file
3
test_site/templates/categories.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% for category in categories %}
|
||||||
|
{{ category.name }} {{ category.slug }} {{ category.count }}
|
||||||
|
{% endfor %}
|
8
test_site/templates/category.html
Normal file
8
test_site/templates/category.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Category: {{ category }}
|
||||||
|
|
||||||
|
|
||||||
|
{% for page in pages %}
|
||||||
|
<article>
|
||||||
|
<h3 class="post__title"><a href="{{ page.url }}">{{ page.title }}</a></h3>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
27
test_site/templates/index.html
Normal file
27
test_site/templates/index.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ config.language_code }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="{{ config.description }}">
|
||||||
|
<meta name="author" content="{{ config.extra.author.name }}">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Fira+Mono|Fira+Sans|Merriweather" rel="stylesheet">
|
||||||
|
<link href="site.css" rel="stylesheet">
|
||||||
|
<title>{{ config.title }}</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
{% block content %}
|
||||||
|
<div class="list-posts">
|
||||||
|
{% for page in pages %}
|
||||||
|
<article>
|
||||||
|
<h3 class="post__title"><a href="{{ page.url }}">{{ page.title }}</a></h3>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
test_site/templates/page.html
Normal file
5
test_site/templates/page.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{% extends "index.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{{ page.content | safe }}
|
||||||
|
{% endblock content %}
|
10
test_site/templates/section.html
Normal file
10
test_site/templates/section.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "index.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% for page in section.pages %}
|
||||||
|
{{page.title}}
|
||||||
|
{% endfor %}
|
||||||
|
{% for subsection in section.subsections %}
|
||||||
|
{{subsection.title}}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock content %}
|
7
test_site/templates/tag.html
Normal file
7
test_site/templates/tag.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Tag: {{ tag }}
|
||||||
|
|
||||||
|
{% for page in pages %}
|
||||||
|
<article>
|
||||||
|
<h3 class="post__title"><a href="{{ page.url }}">{{ page.title }}</a></h3>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
3
test_site/templates/tags.html
Normal file
3
test_site/templates/tags.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% for tag in tags %}
|
||||||
|
{{ tag.name }} {{ tag.slug }} {{ tag.count }}
|
||||||
|
{% endfor %}
|
197
tests/front_matter.rs
Normal file
197
tests/front_matter.rs
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
extern crate gutenberg;
|
||||||
|
extern crate tera;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use gutenberg::{FrontMatter, split_content};
|
||||||
|
use tera::to_value;
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_parse_a_valid_front_matter() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there""#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
println!("{:?}", res);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let res = res.unwrap();
|
||||||
|
assert_eq!(res.title, "Hello".to_string());
|
||||||
|
assert_eq!(res.description, "hey there".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_parse_tags() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
tags = ["rust", "html"]"#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let res = res.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.title, "Hello".to_string());
|
||||||
|
assert_eq!(res.slug.unwrap(), "hello-world".to_string());
|
||||||
|
assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_parse_extra_attributes_in_frontmatter() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
language = "en"
|
||||||
|
authors = ["Bob", "Alice"]"#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let res = res.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.title, "Hello".to_string());
|
||||||
|
assert_eq!(res.slug.unwrap(), "hello-world".to_string());
|
||||||
|
let extra = res.extra.unwrap();
|
||||||
|
assert_eq!(extra.get("language").unwrap(), &to_value("en").unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
extra.get("authors").unwrap(),
|
||||||
|
&to_value(["Bob".to_string(), "Alice".to_string()]).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_ok_with_url_instead_of_slug() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
url = "hello-world""#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let res = res.unwrap();
|
||||||
|
assert!(res.slug.is_none());
|
||||||
|
assert_eq!(res.url.unwrap(), "hello-world".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_errors_with_empty_front_matter() {
|
||||||
|
let content = r#" "#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_errors_with_invalid_front_matter() {
|
||||||
|
let content = r#"title = 1\n"#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_errors_with_missing_required_value_front_matter() {
|
||||||
|
let content = r#"title = """#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_errors_on_non_string_tag() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
tags = ["rust", 1]"#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_errors_on_present_but_empty_slug() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = """#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_errors_on_present_but_empty_url() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
url = """#;
|
||||||
|
let res = FrontMatter::parse(content);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_date_yyyy_mm_dd() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
date = "2016-10-10""#;
|
||||||
|
let res = FrontMatter::parse(content).unwrap();
|
||||||
|
assert!(res.parse_date().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_date_rfc3339() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
date = "2002-10-02T15:00:00Z""#;
|
||||||
|
let res = FrontMatter::parse(content).unwrap();
|
||||||
|
assert!(res.parse_date().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cant_parse_random_date_format() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
date = "2002/10/12""#;
|
||||||
|
let res = FrontMatter::parse(content).unwrap();
|
||||||
|
assert!(res.parse_date().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_split_content_valid() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Title"
|
||||||
|
description = "hey there"
|
||||||
|
date = "2002/10/12"
|
||||||
|
+++
|
||||||
|
Hello
|
||||||
|
"#;
|
||||||
|
let (front_matter, content) = split_content(Path::new(""), content).unwrap();
|
||||||
|
assert_eq!(content, "Hello\n");
|
||||||
|
assert_eq!(front_matter.title, "Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_split_content_with_only_frontmatter_valid() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Title"
|
||||||
|
description = "hey there"
|
||||||
|
date = "2002/10/12"
|
||||||
|
+++"#;
|
||||||
|
let (front_matter, content) = split_content(Path::new(""), content).unwrap();
|
||||||
|
assert_eq!(content, "");
|
||||||
|
assert_eq!(front_matter.title, "Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_if_cannot_locate_frontmatter() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Title"
|
||||||
|
description = "hey there"
|
||||||
|
date = "2002/10/12"
|
||||||
|
"#;
|
||||||
|
let res = split_content(Path::new(""), content);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
249
tests/page.rs
Normal file
249
tests/page.rs
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
extern crate gutenberg;
|
||||||
|
extern crate tempdir;
|
||||||
|
|
||||||
|
use tempdir::TempDir;
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use gutenberg::{Page, Config};
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_parse_a_valid_page() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::parse(Path::new("post.md"), content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(page.meta.title, "Hello".to_string());
|
||||||
|
assert_eq!(page.meta.slug.unwrap(), "hello-world".to_string());
|
||||||
|
assert_eq!(page.raw_content, "Hello world".to_string());
|
||||||
|
assert_eq!(page.content, "<p>Hello world</p>\n".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_find_one_parent_directory() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::parse(Path::new("content/posts/intro.md"), content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.components, vec!["posts".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_find_multiple_parent_directories() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::parse(Path::new("content/posts/intro/start.md"), content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.components, vec!["posts".to_string(), "intro".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_make_url_from_sections_and_slug() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let mut conf = Config::default();
|
||||||
|
conf.base_url = "http://hello.com/".to_string();
|
||||||
|
let res = Page::parse(Path::new("content/posts/intro/start.md"), content, &conf);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.url, "posts/intro/hello-world");
|
||||||
|
assert_eq!(page.permalink, "http://hello.com/posts/intro/hello-world");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_make_permalink_with_non_trailing_slash_base_url() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let mut conf = Config::default();
|
||||||
|
conf.base_url = "http://hello.com".to_string();
|
||||||
|
let res = Page::parse(Path::new("content/posts/intro/hello-world.md"), content, &conf);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.url, "posts/intro/hello-world");
|
||||||
|
assert_eq!(page.permalink, format!("{}{}", conf.base_url, "/posts/intro/hello-world"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_make_url_from_slug_only() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::parse(Path::new("start.md"), content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.url, "hello-world");
|
||||||
|
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "hello-world"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_errors_on_invalid_front_matter_format() {
|
||||||
|
let content = r#"
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
slug = "hello-world"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::parse(Path::new("start.md"), content, &Config::default());
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_make_slug_from_non_slug_filename() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::parse(Path::new("file with space.md"), content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.slug, "file-with-space");
|
||||||
|
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trim_slug_if_needed() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::parse(Path::new(" file with space.md"), content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.slug, "file-with-space");
|
||||||
|
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reading_analytics_short() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
+++
|
||||||
|
Hello world"#;
|
||||||
|
let res = Page::parse(Path::new("hello.md"), content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
let (word_count, reading_time) = page.get_reading_analytics();
|
||||||
|
assert_eq!(word_count, 2);
|
||||||
|
assert_eq!(reading_time, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reading_analytics_long() {
|
||||||
|
let mut content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
+++
|
||||||
|
Hello world"#.to_string();
|
||||||
|
for _ in 0..1000 {
|
||||||
|
content.push_str(" Hello world");
|
||||||
|
}
|
||||||
|
let res = Page::parse(Path::new("hello.md"), &content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
let (word_count, reading_time) = page.get_reading_analytics();
|
||||||
|
assert_eq!(word_count, 2002);
|
||||||
|
assert_eq!(reading_time, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_automatic_summary_is_empty_string() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
+++
|
||||||
|
Hello world"#.to_string();
|
||||||
|
let res = Page::parse(Path::new("hello.md"), &content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.summary, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_specify_summary() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
+++
|
||||||
|
Hello world
|
||||||
|
<!-- more -->
|
||||||
|
"#.to_string();
|
||||||
|
let res = Page::parse(Path::new("hello.md"), &content, &Config::default());
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert_eq!(page.summary, "<p>Hello world</p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_auto_detect_when_highlighting_needed() {
|
||||||
|
let content = r#"
|
||||||
|
+++
|
||||||
|
title = "Hello"
|
||||||
|
description = "hey there"
|
||||||
|
+++
|
||||||
|
```
|
||||||
|
Hey there
|
||||||
|
```
|
||||||
|
"#.to_string();
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.highlight_code = Some(true);
|
||||||
|
let res = Page::parse(Path::new("hello.md"), &content, &config);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let page = res.unwrap();
|
||||||
|
assert!(page.content.starts_with("<pre"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_file_not_named_index_with_assets() {
|
||||||
|
let tmp_dir = TempDir::new("example").expect("create temp dir");
|
||||||
|
File::create(tmp_dir.path().join("something.md")).unwrap();
|
||||||
|
File::create(tmp_dir.path().join("example.js")).unwrap();
|
||||||
|
File::create(tmp_dir.path().join("graph.jpg")).unwrap();
|
||||||
|
File::create(tmp_dir.path().join("fail.png")).unwrap();
|
||||||
|
|
||||||
|
let page = Page::from_file(tmp_dir.path().join("something.md"), &Config::default());
|
||||||
|
assert!(page.is_err());
|
||||||
|
}
|
258
tests/site.rs
Normal file
258
tests/site.rs
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
extern crate gutenberg;
|
||||||
|
extern crate tempdir;
|
||||||
|
extern crate glob;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
|
||||||
|
// use glob::glob;
|
||||||
|
use tempdir::TempDir;
|
||||||
|
use gutenberg::{Site};
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_parse_site() {
|
||||||
|
let mut path = env::current_dir().unwrap().to_path_buf();
|
||||||
|
path.push("test_site");
|
||||||
|
let site = Site::new(&path).unwrap();
|
||||||
|
|
||||||
|
// Correct number of pages (sections are pages too)
|
||||||
|
assert_eq!(site.pages.len(), 10);
|
||||||
|
let posts_path = path.join("content").join("posts");
|
||||||
|
|
||||||
|
// Make sure we remove all the pwd + content from the sections
|
||||||
|
let basic = &site.pages[&posts_path.join("simple.md")];
|
||||||
|
assert_eq!(basic.components, vec!["posts".to_string()]);
|
||||||
|
|
||||||
|
// Make sure the page with a url doesn't have any sections
|
||||||
|
let url_post = &site.pages[&posts_path.join("fixed-url.md")];
|
||||||
|
assert!(url_post.components.is_empty());
|
||||||
|
|
||||||
|
// Make sure the article in a folder with only asset doesn't get counted as a section
|
||||||
|
let asset_folder_post = &site.pages[&posts_path.join("with-assets").join("index.md")];
|
||||||
|
assert_eq!(asset_folder_post.components, vec!["posts".to_string()]);
|
||||||
|
|
||||||
|
// That we have the right number of sections
|
||||||
|
assert_eq!(site.sections.len(), 4);
|
||||||
|
|
||||||
|
// And that the sections are correct
|
||||||
|
let posts_section = &site.sections[&posts_path];
|
||||||
|
assert_eq!(posts_section.subsections.len(), 1);
|
||||||
|
assert_eq!(posts_section.pages.len(), 5);
|
||||||
|
|
||||||
|
let tutorials_section = &site.sections[&posts_path.join("tutorials")];
|
||||||
|
assert_eq!(tutorials_section.subsections.len(), 2);
|
||||||
|
assert_eq!(tutorials_section.pages.len(), 0);
|
||||||
|
|
||||||
|
let devops_section = &site.sections[&posts_path.join("tutorials").join("devops")];
|
||||||
|
assert_eq!(devops_section.subsections.len(), 0);
|
||||||
|
assert_eq!(devops_section.pages.len(), 2);
|
||||||
|
|
||||||
|
let prog_section = &site.sections[&posts_path.join("tutorials").join("programming")];
|
||||||
|
assert_eq!(prog_section.subsections.len(), 0);
|
||||||
|
assert_eq!(prog_section.pages.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 helper macros to make all the build testing more bearable
|
||||||
|
macro_rules! file_exists {
|
||||||
|
($root: expr, $path: expr) => {
|
||||||
|
{
|
||||||
|
let mut path = $root.clone();
|
||||||
|
for component in $path.split("/") {
|
||||||
|
path = path.join(component);
|
||||||
|
}
|
||||||
|
Path::new(&path).exists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! file_contains {
|
||||||
|
($root: expr, $path: expr, $text: expr) => {
|
||||||
|
{
|
||||||
|
let mut path = $root.clone();
|
||||||
|
for component in $path.split("/") {
|
||||||
|
path = path.join(component);
|
||||||
|
}
|
||||||
|
let mut file = File::open(&path).unwrap();
|
||||||
|
let mut s = String::new();
|
||||||
|
file.read_to_string(&mut s).unwrap();
|
||||||
|
s.contains($text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_build_site_without_live_reload() {
|
||||||
|
let mut path = env::current_dir().unwrap().to_path_buf();
|
||||||
|
path.push("test_site");
|
||||||
|
let mut site = Site::new(&path).unwrap();
|
||||||
|
let tmp_dir = TempDir::new("example").expect("create temp dir");
|
||||||
|
let public = &tmp_dir.path().join("public");
|
||||||
|
site.set_output_path(&public);
|
||||||
|
site.build().unwrap();
|
||||||
|
|
||||||
|
assert!(Path::new(&public).exists());
|
||||||
|
|
||||||
|
assert!(file_exists!(public, "index.html"));
|
||||||
|
assert!(file_exists!(public, "sitemap.xml"));
|
||||||
|
assert!(file_exists!(public, "a-fixed-url/index.html"));
|
||||||
|
|
||||||
|
assert!(file_exists!(public, "posts/python/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/with-assets/index.html"));
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
assert!(file_exists!(public, "posts/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
|
||||||
|
// TODO: add assertion for syntax highlighting
|
||||||
|
|
||||||
|
// No tags or categories
|
||||||
|
assert_eq!(file_exists!(public, "categories/index.html"), false);
|
||||||
|
assert_eq!(file_exists!(public, "tags/index.html"), false);
|
||||||
|
|
||||||
|
// no live reload code
|
||||||
|
assert_eq!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"), false);
|
||||||
|
|
||||||
|
// Both pages and sections are in the sitemap
|
||||||
|
assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/posts/simple</loc>"));
|
||||||
|
assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/posts</loc>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_build_site_with_live_reload() {
|
||||||
|
let mut path = env::current_dir().unwrap().to_path_buf();
|
||||||
|
path.push("test_site");
|
||||||
|
let mut site = Site::new(&path).unwrap();
|
||||||
|
let tmp_dir = TempDir::new("example").expect("create temp dir");
|
||||||
|
let public = &tmp_dir.path().join("public");
|
||||||
|
site.set_output_path(&public);
|
||||||
|
site.enable_live_reload();
|
||||||
|
site.build().unwrap();
|
||||||
|
|
||||||
|
assert!(Path::new(&public).exists());
|
||||||
|
|
||||||
|
assert!(file_exists!(public, "index.html"));
|
||||||
|
assert!(file_exists!(public, "sitemap.xml"));
|
||||||
|
assert!(file_exists!(public, "a-fixed-url/index.html"));
|
||||||
|
|
||||||
|
assert!(file_exists!(public, "posts/python/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/with-assets/index.html"));
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
assert!(file_exists!(public, "posts/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
|
||||||
|
// TODO: add assertion for syntax highlighting
|
||||||
|
|
||||||
|
// No tags or categories
|
||||||
|
assert_eq!(file_exists!(public, "categories/index.html"), false);
|
||||||
|
assert_eq!(file_exists!(public, "tags/index.html"), false);
|
||||||
|
|
||||||
|
// no live reload code
|
||||||
|
assert!(file_contains!(public, "index.html", "/livereload.js?port=1112&mindelay=10"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_build_site_with_categories() {
|
||||||
|
let mut path = env::current_dir().unwrap().to_path_buf();
|
||||||
|
path.push("test_site");
|
||||||
|
let mut site = Site::new(&path).unwrap();
|
||||||
|
|
||||||
|
for (i, page) in site.pages.values_mut().enumerate() {
|
||||||
|
page.meta.category = if i % 2 == 0 {
|
||||||
|
Some("A".to_string())
|
||||||
|
} else {
|
||||||
|
Some("B".to_string())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
site.parse_tags_and_categories();
|
||||||
|
let tmp_dir = TempDir::new("example").expect("create temp dir");
|
||||||
|
let public = &tmp_dir.path().join("public");
|
||||||
|
site.set_output_path(&public);
|
||||||
|
site.build().unwrap();
|
||||||
|
|
||||||
|
assert!(Path::new(&public).exists());
|
||||||
|
assert_eq!(site.categories.len(), 2);
|
||||||
|
|
||||||
|
assert!(file_exists!(public, "index.html"));
|
||||||
|
assert!(file_exists!(public, "sitemap.xml"));
|
||||||
|
assert!(file_exists!(public, "a-fixed-url/index.html"));
|
||||||
|
|
||||||
|
assert!(file_exists!(public, "posts/python/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/with-assets/index.html"));
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
assert!(file_exists!(public, "posts/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
|
||||||
|
// TODO: add assertion for syntax highlighting
|
||||||
|
|
||||||
|
// Categories are there
|
||||||
|
assert!(file_exists!(public, "categories/index.html"));
|
||||||
|
assert!(file_exists!(public, "categories/a/index.html"));
|
||||||
|
assert!(file_exists!(public, "categories/b/index.html"));
|
||||||
|
// Tags aren't
|
||||||
|
assert_eq!(file_exists!(public, "tags/index.html"), false);
|
||||||
|
|
||||||
|
// Categories are in the sitemap
|
||||||
|
assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/categories</loc>"));
|
||||||
|
assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/categories/a</loc>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_build_site_with_tags() {
|
||||||
|
let mut path = env::current_dir().unwrap().to_path_buf();
|
||||||
|
path.push("test_site");
|
||||||
|
let mut site = Site::new(&path).unwrap();
|
||||||
|
|
||||||
|
for (i, page) in site.pages.values_mut().enumerate() {
|
||||||
|
page.meta.tags = if i % 2 == 0 {
|
||||||
|
Some(vec!["tag1".to_string(), "tag2".to_string()])
|
||||||
|
} else {
|
||||||
|
Some(vec!["tag with space".to_string()])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
site.parse_tags_and_categories();
|
||||||
|
|
||||||
|
let tmp_dir = TempDir::new("example").expect("create temp dir");
|
||||||
|
let public = &tmp_dir.path().join("public");
|
||||||
|
site.set_output_path(&public);
|
||||||
|
site.build().unwrap();
|
||||||
|
|
||||||
|
assert!(Path::new(&public).exists());
|
||||||
|
assert_eq!(site.tags.len(), 3);
|
||||||
|
|
||||||
|
assert!(file_exists!(public, "index.html"));
|
||||||
|
assert!(file_exists!(public, "sitemap.xml"));
|
||||||
|
assert!(file_exists!(public, "a-fixed-url/index.html"));
|
||||||
|
|
||||||
|
assert!(file_exists!(public, "posts/python/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/devops/nix/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/with-assets/index.html"));
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
assert!(file_exists!(public, "posts/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/devops/index.html"));
|
||||||
|
assert!(file_exists!(public, "posts/tutorials/programming/index.html"));
|
||||||
|
// TODO: add assertion for syntax highlighting
|
||||||
|
|
||||||
|
// Tags are there
|
||||||
|
assert!(file_exists!(public, "tags/index.html"));
|
||||||
|
assert!(file_exists!(public, "tags/tag1/index.html"));
|
||||||
|
assert!(file_exists!(public, "tags/tag2/index.html"));
|
||||||
|
assert!(file_exists!(public, "tags/tag-with-space/index.html"));
|
||||||
|
// Categories aren't
|
||||||
|
assert_eq!(file_exists!(public, "categories/index.html"), false);
|
||||||
|
// Tags are in the sitemap
|
||||||
|
assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/tags</loc>"));
|
||||||
|
assert!(file_contains!(public, "sitemap.xml", "<loc>https://replace-this-with-your-url.com/tags/tag-with-space</loc>"));
|
||||||
|
}
|
Loading…
Reference in a new issue