From 23e4b911e797939198321b7be24db7dde00ad517 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 27 Jul 2017 18:24:43 +0900 Subject: [PATCH] Improve gutenberg init Fix #104 --- CHANGELOG.md | 2 + Cargo.lock | 1 + Cargo.toml | 3 ++ components/config/src/lib.rs | 10 ++-- components/templates/src/builtins/index.html | 0 components/templates/src/builtins/page.html | 0 .../templates/src/builtins/section.html | 0 components/templates/src/lib.rs | 6 +++ src/cli.rs | 2 +- src/cmd/init.rs | 46 ++++++++++++---- src/main.rs | 4 +- src/prompt.rs | 53 +++++++++++++++++++ 12 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 components/templates/src/builtins/index.html create mode 100644 components/templates/src/builtins/page.html create mode 100644 components/templates/src/builtins/section.html create mode 100644 src/prompt.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ca4b8..8fdc4ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Add `redirect_to` to section front matter to redirect when landing on section root page +- Make `title` in config optional +- Improved `gutenberg init` UX and users first experience ## 0.1.1 (2017-07-16) diff --git a/Cargo.lock b/Cargo.lock index 236554c..c2e0d2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,6 +400,7 @@ dependencies = [ "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)", "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", "ws 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index a30ecae..42e7abf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ chrono = "0.4" toml = "0.4" term-painter = "0.2" +# Used in init to ensure the url given as base_url is a valid one +url = "1.5" + # Below is for the serve cmd staticfile = "0.4" iron = "0.5" diff --git a/components/config/src/lib.rs b/components/config/src/lib.rs index 87b07a2..32391c9 100644 --- a/components/config/src/lib.rs +++ b/components/config/src/lib.rs @@ -18,11 +18,11 @@ use rendering::highlighting::THEME_SET; #[derive(Debug, PartialEq, Serialize, Deserialize)] 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, + /// Title of the site. Defaults to None + pub title: Option, /// Whether to highlight all code blocks found in markdown files. Defaults to false pub highlight_code: Option, /// Which themes to use for code highlighting. See Readme for supported themes @@ -123,7 +123,7 @@ impl Config { impl Default for Config { fn default() -> Config { Config { - title: "".to_string(), + title: Some("".to_string()), base_url: "http://a-website.com/".to_string(), highlight_code: Some(true), 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(); - assert_eq!(config.title, "My site".to_string()); + assert_eq!(config.title.unwrap(), "My site".to_string()); } #[test] diff --git a/components/templates/src/builtins/index.html b/components/templates/src/builtins/index.html new file mode 100644 index 0000000..e69de29 diff --git a/components/templates/src/builtins/page.html b/components/templates/src/builtins/page.html new file mode 100644 index 0000000..e69de29 diff --git a/components/templates/src/builtins/section.html b/components/templates/src/builtins/section.html new file mode 100644 index 0000000..e69de29 diff --git a/components/templates/src/lib.rs b/components/templates/src/lib.rs index 0837df0..c2b8d78 100644 --- a/components/templates/src/lib.rs +++ b/components/templates/src/lib.rs @@ -20,6 +20,12 @@ lazy_static! { pub static ref GUTENBERG_TERA: Tera = { let mut tera = Tera::default(); 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")), ("sitemap.xml", include_str!("builtins/sitemap.xml")), ("robots.txt", include_str!("builtins/robots.txt")), diff --git a/src/cli.rs b/src/cli.rs index a25fcde..c6f9091 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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") (@subcommand init => (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 => (about: "Builds the site") diff --git a/src/cmd/init.rs b/src/cmd/init.rs index d1eca9a..2eb2789 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -1,14 +1,23 @@ -use std::fs::create_dir; +use std::fs::{create_dir, canonicalize}; use std::path::Path; use errors::Result; use utils::fs::create_file; +use prompt::{ask_bool, ask_url}; +use console; + 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] # Put all your custom variables here @@ -17,23 +26,38 @@ base_url = "https://example.com" pub fn create_new_project(name: &str) -> Result<()> { let path = Path::new(name); - // Better error message than the rust default if path.exists() && path.is_dir() { bail!("Folder `{}` already exists", path.to_string_lossy().to_string()); } - // main folder 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"))?; + 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(()) } diff --git a/src/main.rs b/src/main.rs index 29cb6f9..2ae977d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ extern crate staticfile; extern crate iron; extern crate mount; extern crate notify; +extern crate url; extern crate ws; extern crate site; @@ -21,6 +22,7 @@ mod cmd; mod console; mod rebuild; mod cli; +mod prompt; fn main() { @@ -31,7 +33,7 @@ fn main() { match matches.subcommand() { ("init", Some(matches)) => { match cmd::create_new_project(matches.value_of("name").unwrap()) { - Ok(()) => console::success("Project created"), + Ok(()) => (), Err(e) => { console::unravel_errors("Failed to create the project", &e); ::std::process::exit(1); diff --git a/src/prompt.rs b/src/prompt.rs new file mode 100644 index 0000000..b51891b --- /dev/null +++ b/src/prompt.rs @@ -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 { + 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 { + 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 { + 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) + } + } + }, + } +}