@@ -4,6 +4,8 @@ | |||||
- Add `redirect_to` to section front matter to redirect when landing on section | - Add `redirect_to` to section front matter to redirect when landing on section | ||||
root page | root page | ||||
- Make `title` in config optional | |||||
- Improved `gutenberg init` UX and users first experience | |||||
## 0.1.1 (2017-07-16) | ## 0.1.1 (2017-07-16) | ||||
@@ -400,6 +400,7 @@ dependencies = [ | |||||
"staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | "staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"term-painter 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", | "term-painter 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"toml 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", | "toml 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"utils 0.1.0", | "utils 0.1.0", | ||||
"ws 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", | "ws 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
@@ -22,6 +22,9 @@ chrono = "0.4" | |||||
toml = "0.4" | toml = "0.4" | ||||
term-painter = "0.2" | term-painter = "0.2" | ||||
# Used in init to ensure the url given as base_url is a valid one | |||||
url = "1.5" | |||||
# Below is for the serve cmd | # Below is for the serve cmd | ||||
staticfile = "0.4" | staticfile = "0.4" | ||||
iron = "0.5" | iron = "0.5" | ||||
@@ -18,11 +18,11 @@ use rendering::highlighting::THEME_SET; | |||||
#[derive(Debug, PartialEq, Serialize, Deserialize)] | #[derive(Debug, PartialEq, Serialize, Deserialize)] | ||||
pub struct Config { | pub struct Config { | ||||
/// Title of the site | |||||
pub title: String, | |||||
/// Base URL of the site | |||||
/// Base URL of the site, the only required config argument | |||||
pub base_url: String, | pub base_url: String, | ||||
/// Title of the site. Defaults to None | |||||
pub title: Option<String>, | |||||
/// Whether to highlight all code blocks found in markdown files. Defaults to false | /// Whether to highlight all code blocks found in markdown files. Defaults to false | ||||
pub highlight_code: Option<bool>, | pub highlight_code: Option<bool>, | ||||
/// Which themes to use for code highlighting. See Readme for supported themes | /// Which themes to use for code highlighting. See Readme for supported themes | ||||
@@ -123,7 +123,7 @@ impl Config { | |||||
impl Default for Config { | impl Default for Config { | ||||
fn default() -> Config { | fn default() -> Config { | ||||
Config { | Config { | ||||
title: "".to_string(), | |||||
title: Some("".to_string()), | |||||
base_url: "http://a-website.com/".to_string(), | base_url: "http://a-website.com/".to_string(), | ||||
highlight_code: Some(true), | highlight_code: Some(true), | ||||
highlight_theme: Some("base16-ocean-dark".to_string()), | highlight_theme: Some("base16-ocean-dark".to_string()), | ||||
@@ -167,7 +167,7 @@ base_url = "https://replace-this-with-your-url.com" | |||||
"#; | "#; | ||||
let config = Config::parse(config).unwrap(); | let config = Config::parse(config).unwrap(); | ||||
assert_eq!(config.title, "My site".to_string()); | |||||
assert_eq!(config.title.unwrap(), "My site".to_string()); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -20,6 +20,12 @@ lazy_static! { | |||||
pub static ref GUTENBERG_TERA: Tera = { | pub static ref GUTENBERG_TERA: Tera = { | ||||
let mut tera = Tera::default(); | let mut tera = Tera::default(); | ||||
tera.add_raw_templates(vec![ | tera.add_raw_templates(vec![ | ||||
// adding default built-ins templates for index/page/section so | |||||
// users don't get an error when they run gutenberg after init | |||||
("index.html", include_str!("builtins/index.html")), | |||||
("page.html", include_str!("builtins/page.html")), | |||||
("section.html", include_str!("builtins/section.html")), | |||||
("rss.xml", include_str!("builtins/rss.xml")), | ("rss.xml", include_str!("builtins/rss.xml")), | ||||
("sitemap.xml", include_str!("builtins/sitemap.xml")), | ("sitemap.xml", include_str!("builtins/sitemap.xml")), | ||||
("robots.txt", include_str!("builtins/robots.txt")), | ("robots.txt", include_str!("builtins/robots.txt")), | ||||
@@ -10,7 +10,7 @@ pub fn build_cli() -> App<'static, 'static> { | |||||
(@arg config: -c --config +takes_value "Path to a config file other than config.toml") | (@arg config: -c --config +takes_value "Path to a config file other than config.toml") | ||||
(@subcommand init => | (@subcommand init => | ||||
(about: "Create a new Gutenberg project") | (about: "Create a new Gutenberg project") | ||||
(@arg name: +required "Name of the project. Will create a directory with that name in the current directory") | |||||
(@arg name: +required "Name of the project. Will create a new directory with that name in the current directory") | |||||
) | ) | ||||
(@subcommand build => | (@subcommand build => | ||||
(about: "Builds the site") | (about: "Builds the site") | ||||
@@ -1,14 +1,23 @@ | |||||
use std::fs::create_dir; | |||||
use std::fs::{create_dir, canonicalize}; | |||||
use std::path::Path; | use std::path::Path; | ||||
use errors::Result; | use errors::Result; | ||||
use utils::fs::create_file; | use utils::fs::create_file; | ||||
use prompt::{ask_bool, ask_url}; | |||||
use console; | |||||
const CONFIG: &'static str = r#" | const CONFIG: &'static str = r#" | ||||
title = "My site" | |||||
# replace the url below with yours | |||||
base_url = "https://example.com" | |||||
# The URL the site will be built for | |||||
base_url = "%BASE_URL%" | |||||
# Whether to automatically compile all Sass files in the sass directory | |||||
compile_sass = %COMPILE_SASS% | |||||
# Whether to do syntax highlighting | |||||
# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Gutenberg | |||||
highlight_code = %HIGHLIGHT% | |||||
[extra] | [extra] | ||||
# Put all your custom variables here | # Put all your custom variables here | ||||
@@ -17,23 +26,38 @@ base_url = "https://example.com" | |||||
pub fn create_new_project(name: &str) -> Result<()> { | pub fn create_new_project(name: &str) -> Result<()> { | ||||
let path = Path::new(name); | let path = Path::new(name); | ||||
// Better error message than the rust default | // Better error message than the rust default | ||||
if path.exists() && path.is_dir() { | if path.exists() && path.is_dir() { | ||||
bail!("Folder `{}` already exists", path.to_string_lossy().to_string()); | bail!("Folder `{}` already exists", path.to_string_lossy().to_string()); | ||||
} | } | ||||
// main folder | |||||
create_dir(path)?; | create_dir(path)?; | ||||
create_file(&path.join("config.toml"), CONFIG.trim_left())?; | |||||
console::info("Welcome to Gutenberg!"); | |||||
// content folder | |||||
create_dir(path.join("content"))?; | |||||
let base_url = ask_url("> What is the URL of your site?", "https://example.com")?; | |||||
let compile_sass = ask_bool("> Do you want to enable Sass compilation?", true)?; | |||||
let highlight = ask_bool("> Do you want to enable syntax highlighting?", false)?; | |||||
// layouts folder | |||||
create_dir(path.join("templates"))?; | |||||
let config = CONFIG | |||||
.trim_left() | |||||
.replace("%BASE_URL%", &base_url) | |||||
.replace("%COMPILE_SASS%", &format!("{}", compile_sass)) | |||||
.replace("%HIGHLIGHT%", &format!("{}", highlight)); | |||||
create_file(&path.join("config.toml"), &config)?; | |||||
create_dir(path.join("content"))?; | |||||
create_dir(path.join("templates"))?; | |||||
create_dir(path.join("static"))?; | create_dir(path.join("static"))?; | ||||
if compile_sass { | |||||
create_dir(path.join("sass"))?; | |||||
} | |||||
println!(); | |||||
console::success(&format!("Done! Your site was created in {:?}", canonicalize(path).unwrap())); | |||||
println!(); | |||||
console::info("Get started by using the built-in server: `gutenberg serve`"); | |||||
println!("There is no built-in theme so you will see a white page."); | |||||
println!("Visit https://github.com/Keats/gutenberg for the full documentation."); | |||||
Ok(()) | Ok(()) | ||||
} | } |
@@ -6,6 +6,7 @@ extern crate staticfile; | |||||
extern crate iron; | extern crate iron; | ||||
extern crate mount; | extern crate mount; | ||||
extern crate notify; | extern crate notify; | ||||
extern crate url; | |||||
extern crate ws; | extern crate ws; | ||||
extern crate site; | extern crate site; | ||||
@@ -21,6 +22,7 @@ mod cmd; | |||||
mod console; | mod console; | ||||
mod rebuild; | mod rebuild; | ||||
mod cli; | mod cli; | ||||
mod prompt; | |||||
fn main() { | fn main() { | ||||
@@ -31,7 +33,7 @@ fn main() { | |||||
match matches.subcommand() { | match matches.subcommand() { | ||||
("init", Some(matches)) => { | ("init", Some(matches)) => { | ||||
match cmd::create_new_project(matches.value_of("name").unwrap()) { | match cmd::create_new_project(matches.value_of("name").unwrap()) { | ||||
Ok(()) => console::success("Project created"), | |||||
Ok(()) => (), | |||||
Err(e) => { | Err(e) => { | ||||
console::unravel_errors("Failed to create the project", &e); | console::unravel_errors("Failed to create the project", &e); | ||||
::std::process::exit(1); | ::std::process::exit(1); | ||||
@@ -0,0 +1,53 @@ | |||||
use std::io::{self, Write, BufRead}; | |||||
use url::Url; | |||||
use errors::Result; | |||||
/// Wait for user input and return what they typed | |||||
fn read_line() -> Result<String> { | |||||
let stdin = io::stdin(); | |||||
let stdin = stdin.lock(); | |||||
let mut lines = stdin.lines(); | |||||
lines | |||||
.next() | |||||
.and_then(|l| l.ok()) | |||||
.ok_or("unable to read from stdin for confirmation".into()) | |||||
} | |||||
/// Ask a yes/no question to the user | |||||
pub fn ask_bool(question: &str, default: bool) -> Result<bool> { | |||||
print!("{} {}: ", question, if default { "[Y/n]" } else { "[y/N]" }); | |||||
let _ = io::stdout().flush(); | |||||
let input = read_line()?; | |||||
match &*input { | |||||
"y" | "Y" | "yes" | "YES" | "true" => Ok(true), | |||||
"n" | "N" | "no" | "NO" | "false" => Ok(false), | |||||
"" => Ok(default), | |||||
_ => { | |||||
println!("Invalid choice: '{}'", input); | |||||
ask_bool(question, default) | |||||
}, | |||||
} | |||||
} | |||||
/// Ask a question to the user where they can write a URL | |||||
pub fn ask_url(question: &str, default: &str) -> Result<String> { | |||||
print!("{} ({}): ", question, default); | |||||
let _ = io::stdout().flush(); | |||||
let input = read_line()?; | |||||
match &*input { | |||||
"" => Ok(default.to_string()), | |||||
_ => { | |||||
match Url::parse(&input) { | |||||
Ok(_) => Ok(input), | |||||
Err(_) => { | |||||
println!("Invalid URL: '{}'", input); | |||||
ask_url(question, default) | |||||
} | |||||
} | |||||
}, | |||||
} | |||||
} |