refactor(cli)!: Port to clap_derive

BREAKING CHANGE: Logging arguments now can't be called on the subcommand
and now are repeated `-q` or `-v`, with "info" being the default log
level.
This commit is contained in:
Ed Page 2022-01-13 15:51:54 -06:00
parent 3c5855d8e9
commit e8b76902e1
9 changed files with 515 additions and 497 deletions

96
Cargo.lock generated
View file

@ -270,6 +270,7 @@ checksum = "d01c9347757e131122b19cd19a05c85805b68c2352a97b623efdc3c295290299"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"indexmap",
"lazy_static",
"os_str_bytes",
@ -278,6 +279,29 @@ dependencies = [
"textwrap",
]
[[package]]
name = "clap-verbosity-flag"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb1281ab1a7abc0f415a57cf6bc1f46282957ce0c5f2b3fe6b98ff3adf8e29b3"
dependencies = [
"clap",
"log",
]
[[package]]
name = "clap_derive"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "517358c28fcef6607bf6f76108e02afad7e82297d132a6b846dcc1fc3efcd153"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cobalt-bin"
version = "0.17.5"
@ -286,6 +310,7 @@ dependencies = [
"assert_fs",
"chrono",
"clap",
"clap-verbosity-flag",
"cobalt-config",
"cobalt-core",
"deunicode",
@ -294,6 +319,7 @@ dependencies = [
"failure",
"ghp",
"html-minifier",
"human-panic",
"ignore",
"itertools",
"jsonfeed",
@ -308,6 +334,7 @@ dependencies = [
"notify",
"open",
"predicates",
"proc-exit",
"pulldown-cmark",
"regex",
"relative-path",
@ -872,6 +899,12 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -902,6 +935,21 @@ dependencies = [
"minifier",
]
[[package]]
name = "human-panic"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39f357a500abcbd7c5f967c1d45c8838585b36743823b9d43488f24850534e36"
dependencies = [
"backtrace",
"os_type",
"serde",
"serde_derive",
"termcolor",
"toml",
"uuid",
]
[[package]]
name = "humantime"
version = "2.1.0"
@ -1390,6 +1438,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "os_type"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3df761f6470298359f84fcfb60d86db02acc22c251c37265c07a3d1057d2389"
dependencies = [
"regex",
]
[[package]]
name = "pathdiff"
version = "0.2.1"
@ -1501,6 +1558,36 @@ dependencies = [
"termtree",
]
[[package]]
name = "proc-exit"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da6bbc8ef87314d4f596ad9d02db375c3f2d77fba91067a6f6a5754fdc8cb49"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
@ -2144,6 +2231,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom",
]
[[package]]
name = "version_check"
version = "0.9.4"

View file

@ -46,7 +46,10 @@ doc = false
[dependencies]
cobalt-config = { version = "=0.17.0", path = "crates/config", features = ["unstable"] }
cobalt-core = { version = "=0.17.0", path = "crates/core", features = ["unstable"] }
clap = { version = "3.0", features = ["cargo"] }
clap = { version = "3.0", features = ["derive"] }
clap-verbosity-flag = "0.4"
proc-exit = "1"
human-panic = "1"
kstring = "1.0"
relative-path = { version = "1", features = ["serde"] }
liquid = "0.23"

View file

@ -6,112 +6,73 @@ use failure::ResultExt;
use crate::error::*;
pub fn get_config_args() -> Vec<clap::Arg<'static>> {
[
clap::Arg::new("config")
.short('c')
.long("config")
.value_name("FILE")
.help("Config file to use [default: _cobalt.yml]")
.takes_value(true),
clap::Arg::new("destination")
.short('d')
.long("destination")
.value_name("DIR")
.help("Site destination folder [default: ./]")
.takes_value(true),
clap::Arg::new("drafts")
.long("drafts")
.help("Include drafts.")
.takes_value(false),
clap::Arg::new("no-drafts")
.long("no-drafts")
.help("Ignore drafts.")
.conflicts_with("drafts")
.takes_value(false),
]
.to_vec()
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
pub struct ConfigArgs {
/// Config file to use [default: _cobalt.yml]
#[clap(short, long, value_name = "FILE", parse(from_os_str))]
config: Option<path::PathBuf>,
/// Site destination folder [default: ./]
#[clap(short, long, value_name = "DIR", parse(from_os_str))]
destination: Option<path::PathBuf>,
/// Include drafts.
#[clap(long)]
drafts: bool,
/// Ignore drafts.
#[clap(long, conflicts_with = "drafts")]
no_drafts: bool,
}
pub fn get_config(matches: &clap::ArgMatches) -> Result<cobalt_config::Config> {
let config_path = matches.value_of("config");
impl ConfigArgs {
pub fn load_config(&self) -> Result<cobalt_config::Config> {
let config_path = self.config.as_deref();
// Fetch config information if available
let mut config = if let Some(config_path) = config_path {
cobalt_config::Config::from_file(config_path)
.with_context(|_| failure::format_err!("Error reading config file {:?}", config_path))?
} else {
let cwd = env::current_dir().expect("How does this fail?");
cobalt_config::Config::from_cwd(cwd)?
};
// Fetch config information if available
let mut config = if let Some(config_path) = config_path {
cobalt_config::Config::from_file(config_path).with_context(|_| {
failure::format_err!("Error reading config file {:?}", config_path)
})?
} else {
let cwd = env::current_dir().expect("How does this fail?");
cobalt_config::Config::from_cwd(cwd)?
};
config.abs_dest = matches
.value_of("destination")
.map(|d| {
let d = path::PathBuf::from(d);
std::fs::create_dir_all(&d)?;
d.canonicalize()
})
.transpose()?;
config.abs_dest = self
.destination
.as_deref()
.map(|d| {
std::fs::create_dir_all(d)?;
d.canonicalize()
})
.transpose()?;
if matches.is_present("drafts") {
config.include_drafts = true;
}
if matches.is_present("no-drafts") {
config.include_drafts = false;
if let Some(drafts) = self.drafts() {
config.include_drafts = drafts;
}
Ok(config)
}
Ok(config)
pub fn drafts(&self) -> Option<bool> {
resolve_bool_arg(self.drafts, self.no_drafts)
}
}
pub fn get_logging_args() -> Vec<clap::Arg<'static>> {
[
clap::Arg::new("log-level")
.short('L')
.long("log-level")
.possible_values(&["error", "warn", "info", "debug", "trace", "off"])
.help("Log level [default: info]")
.global(true)
.takes_value(true),
clap::Arg::new("trace")
.long("trace")
.help("Log ultra-verbose (trace level) information")
.global(true)
.takes_value(false),
clap::Arg::new("silent")
.long("silent")
.help("Suppress all output")
.global(true)
.takes_value(false),
]
.to_vec()
fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
match (yes, no) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(_, _) => unreachable!("clap should make this impossible"),
}
}
pub fn get_logging(
global_matches: &clap::ArgMatches,
matches: &clap::ArgMatches,
) -> Result<env_logger::Builder> {
pub fn get_logging(level: log::Level) -> Result<env_logger::Builder> {
let mut builder = env_logger::Builder::new();
let level = if matches.is_present("trace") {
log::LevelFilter::Trace
} else if matches.is_present("silent") {
log::LevelFilter::Off
} else {
match matches
.value_of("log-level")
.or_else(|| global_matches.value_of("log-level"))
{
Some("error") => log::LevelFilter::Error,
Some("warn") => log::LevelFilter::Warn,
Some("debug") => log::LevelFilter::Debug,
Some("trace") => log::LevelFilter::Trace,
Some("off") => log::LevelFilter::Off,
Some("info") => log::LevelFilter::Info,
_ => log::LevelFilter::Info,
}
};
builder.filter(None, level);
builder.filter(None, level.to_level_filter());
if level == log::LevelFilter::Trace {
builder.format_timestamp_secs();

View file

@ -5,20 +5,23 @@ use std::path;
use crate::args;
use crate::error::*;
pub fn build_command_args() -> clap::App<'static> {
clap::App::new("build")
.about("build the cobalt project at the source dir")
.args(args::get_config_args())
/// Build the cobalt project at the source dir
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
pub struct BuildArgs {
#[clap(flatten, help_heading = "CONFIG")]
pub config: args::ConfigArgs,
}
pub fn build_command(matches: &clap::ArgMatches) -> Result<()> {
let config = args::get_config(matches)?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
impl BuildArgs {
pub fn run(&self) -> Result<()> {
let config = self.config.load_config()?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
build(config)?;
info!("Build successful");
build(config)?;
info!("Build successful");
Ok(())
Ok(())
}
}
pub fn build(config: cobalt::Config) -> Result<()> {
@ -31,17 +34,20 @@ pub fn build(config: cobalt::Config) -> Result<()> {
Ok(())
}
pub fn clean_command_args() -> clap::App<'static> {
clap::App::new("clean")
.about("cleans `destination` directory")
.args(args::get_config_args())
/// Cleans `destination` directory
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
pub struct CleanArgs {
#[clap(flatten, help_heading = "CONFIG")]
pub config: args::ConfigArgs,
}
pub fn clean_command(matches: &clap::ArgMatches) -> Result<()> {
let config = args::get_config(matches)?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
impl CleanArgs {
pub fn run(&self) -> Result<()> {
let config = self.config.load_config()?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
clean(&config)
clean(&config)
}
}
pub fn clean(config: &cobalt::Config) -> Result<()> {
@ -70,42 +76,38 @@ pub fn clean(config: &cobalt::Config) -> Result<()> {
Ok(())
}
pub fn import_command_args() -> clap::App<'static> {
clap::App::new("import")
.about("moves the contents of the dest folder to the gh-pages branch")
.args(args::get_config_args())
.arg(
clap::Arg::new("branch")
.short('b')
.long("branch")
.value_name("BRANCH")
.help("Branch that will be used to import the site to")
.default_value("gh-pages")
.takes_value(true),
)
.arg(
clap::Arg::new("message")
.short('m')
.long("message")
.value_name("COMMIT-MESSAGE")
.help("Commit message that will be used on import")
.default_value("cobalt site import")
.takes_value(true),
)
/// Moves the contents of the dest folder to the gh-pages branch
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
pub struct ImportArgs {
/// Branch that will be used to import the site to
#[clap(short, long, default_value = "gh-pages")]
pub branch: String,
/// Commit message that will be used on import
#[clap(
short,
long,
value_name = "COMMIT-MESSAGE",
default_value = "cobalt site import"
)]
pub message: String,
#[clap(flatten, help_heading = "CONFIG")]
pub config: args::ConfigArgs,
}
pub fn import_command(matches: &clap::ArgMatches) -> Result<()> {
let config = args::get_config(matches)?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
impl ImportArgs {
pub fn run(&self) -> Result<()> {
let config = self.config.load_config()?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
clean(&config)?;
build(config.clone())?;
clean(&config)?;
build(config.clone())?;
let branch = matches.value_of("branch").unwrap().to_string();
let message = matches.value_of("message").unwrap().to_string();
import(&config, &branch, &message)?;
import(&config, &self.branch, &self.message)?;
Ok(())
Ok(())
}
}
fn import(config: &cobalt::Config, branch: &str, message: &str) -> Result<()> {

View file

@ -1,78 +1,83 @@
use crate::args;
use crate::error::*;
pub fn debug_command_args() -> clap::App<'static> {
clap::App::new("debug")
.about("Print site debug information")
.subcommand(clap::App::new("config").about("Prints post-processed config"))
.subcommand(
clap::App::new("highlight")
.about("Print syntax-highlight information")
.subcommand(clap::App::new("themes"))
.subcommand(clap::App::new("syntaxes")),
)
.subcommand(
clap::App::new("files")
.about("Print files associated with a collection")
.args(args::get_config_args())
.arg(
clap::Arg::new("COLLECTION")
.help("Collection name")
.index(1),
),
)
/// Print site debug information
#[derive(Clone, Debug, PartialEq, Eq, clap::Subcommand)]
pub enum DebugCommands {
/// Prints post-processed config
Config {
#[clap(flatten, help_heading = "CONFIG")]
config: args::ConfigArgs,
},
/// Print syntax-highlight information
#[clap(subcommand)]
Highlight(HighlightCommands),
/// Print files associated with a collection
Files {
/// Collection name
collection: Option<String>,
#[clap(flatten, help_heading = "CONFIG")]
config: args::ConfigArgs,
},
}
pub fn debug_command(matches: &clap::ArgMatches) -> Result<()> {
match matches.subcommand() {
Some(("config", _)) => {
let config = args::get_config(matches)?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
println!("{}", config);
}
Some(("highlight", matches)) => match matches.subcommand() {
Some(("themes", _)) => {
#[derive(Clone, Debug, PartialEq, Eq, clap::Subcommand)]
pub enum HighlightCommands {
Themes {},
Syntaxes {},
}
impl DebugCommands {
pub fn run(&self) -> Result<()> {
match self {
Self::Config { config } => {
let config = config.load_config()?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
println!("{}", config);
}
Self::Highlight(HighlightCommands::Themes {}) => {
for name in cobalt::list_syntax_themes() {
println!("{}", name);
}
}
Some(("syntaxes", _)) => {
Self::Highlight(HighlightCommands::Syntaxes {}) => {
for name in cobalt::list_syntaxes() {
println!("{}", name);
}
}
_ => unreachable!("Unexpected subcommand"),
},
Some(("files", matches)) => {
let config = args::get_config(matches)?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
let collection = matches.value_of("COLLECTION");
match collection {
Some("assets") => {
failure::bail!("TODO Re-implement");
}
Some("pages") => {
failure::bail!("TODO Re-implement");
}
Some("posts") => {
failure::bail!("TODO Re-implement");
}
None => {
let source_files = cobalt_core::Source::new(
&config.source,
config.ignore.iter().map(|s| s.as_str()),
)?;
for path in source_files.iter() {
println!("{}", path.rel_path);
Self::Files { collection, config } => {
let config = config.load_config()?;
let config = cobalt::cobalt_model::Config::from_config(config)?;
match collection.as_deref() {
Some("assets") => {
failure::bail!("TODO Re-implement");
}
Some("pages") => {
failure::bail!("TODO Re-implement");
}
Some("posts") => {
failure::bail!("TODO Re-implement");
}
None => {
let source_files = cobalt_core::Source::new(
&config.source,
config.ignore.iter().map(|s| s.as_str()),
)?;
for path in source_files.iter() {
println!("{}", path.rel_path);
}
}
_ => {
failure::bail!("Collection is not yet supported");
}
}
_ => {
failure::bail!("Collection is not yet supported");
}
}
}
_ => unreachable!("Unexpected subcommand"),
}
Ok(())
Ok(())
}
}

View file

@ -3,9 +3,6 @@
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate log;
@ -19,71 +16,72 @@ mod serve;
use std::alloc;
use clap::{App, AppSettings};
use failure::ResultExt;
use clap::{AppSettings, Parser};
use crate::error::*;
#[global_allocator]
static GLOBAL: alloc::System = alloc::System;
fn main() -> std::result::Result<(), exitfailure::ExitFailure> {
run()?;
Ok(())
/// Static site generator
#[derive(Clone, Debug, Parser)]
#[clap(global_setting = AppSettings::PropagateVersion)]
#[clap(version)]
struct Cli {
#[clap(flatten)]
pub logging: clap_verbosity_flag::Verbosity,
#[clap(subcommand)]
command: Command,
}
fn cli() -> App<'static> {
let app_cli = App::new("Cobalt")
.version(crate_version!())
.author("Benny Klotz <r3qnbenni@gmail.com>, Johann Hofmann")
.about("A static site generator written in Rust.")
.setting(AppSettings::SubcommandRequired)
.setting(AppSettings::PropagateVersion)
.args(&args::get_logging_args())
.subcommand(new::init_command_args())
.subcommand(new::new_command_args())
.subcommand(new::rename_command_args())
.subcommand(new::publish_command_args())
.subcommand(build::build_command_args())
.subcommand(build::clean_command_args())
.subcommand(build::import_command_args())
.subcommand(debug::debug_command_args());
#[derive(Clone, Debug, PartialEq, Eq, Parser)]
enum Command {
Init(new::InitArgs),
New(new::NewArgs),
Rename(new::RenameArgs),
Publish(new::PublishArgs),
Build(build::BuildArgs),
Clean(build::CleanArgs),
Import(build::ImportArgs),
#[cfg(feature = "serve")]
let app_cli = app_cli.subcommand(serve::serve_command_args());
app_cli
Serve(serve::ServeArgs),
#[clap(subcommand)]
Debug(debug::DebugCommands),
}
fn run() -> Result<()> {
let app_cli = cli();
let global_matches = app_cli.get_matches();
impl Cli {
pub fn run(&self) -> Result<()> {
let mut logging = self.logging.clone();
logging.set_default(Some(log::Level::Info));
if let Some(level) = logging.log_level() {
let mut builder = args::get_logging(level)?;
builder.init();
}
let (command, matches) = match global_matches.subcommand() {
Some((command, matches)) => (command, matches),
None => unreachable!(),
};
let mut builder = args::get_logging(&global_matches, matches)?;
builder.init();
match command {
"init" => new::init_command(matches),
"new" => new::new_command(matches),
"rename" => new::rename_command(matches),
"publish" => new::publish_command(matches),
"build" => build::build_command(matches),
"clean" => build::clean_command(matches),
#[cfg(feature = "serve")]
"serve" => serve::serve_command(matches),
"import" => build::import_command(matches),
"debug" => debug::debug_command(matches),
_ => unreachable!("Unexpected subcommand"),
match &self.command {
Command::Init(cmd) => cmd.run(),
Command::New(cmd) => cmd.run(),
Command::Rename(cmd) => cmd.run(),
Command::Publish(cmd) => cmd.run(),
Command::Build(cmd) => cmd.run(),
Command::Clean(cmd) => cmd.run(),
Command::Import(cmd) => cmd.run(),
#[cfg(feature = "serve")]
Command::Serve(cmd) => cmd.run(),
Command::Debug(cmd) => cmd.run(),
}
}
.with_context(|_| failure::format_err!("{} command failed", command))?;
}
fn main() -> std::result::Result<(), exitfailure::ExitFailure> {
let cli = Cli::parse();
cli.run()?;
Ok(())
}
#[test]
fn verify_app() {
cli().debug_assert()
use clap::IntoApp;
Cli::into_app().debug_assert()
}

View file

@ -11,148 +11,130 @@ use failure::ResultExt;
use crate::args;
use crate::error::*;
pub fn init_command_args() -> clap::App<'static> {
clap::App::new("init")
.about("create a new cobalt project")
.arg(
clap::Arg::new("DIRECTORY")
.help("Target directory")
.default_value("./")
.index(1),
)
/// Create a document
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
pub struct InitArgs {
/// Target directory
#[clap(default_value = "./", parse(from_os_str))]
pub directory: path::PathBuf,
}
pub fn init_command(matches: &clap::ArgMatches) -> Result<()> {
let directory = matches.value_of("DIRECTORY").unwrap();
impl InitArgs {
pub fn run(&self) -> Result<()> {
create_new_project(&self.directory)
.with_context(|_| failure::err_msg("Could not create a new cobalt project"))?;
info!("Created new project at {}", self.directory.display());
create_new_project(&directory.to_string())
.with_context(|_| failure::err_msg("Could not create a new cobalt project"))?;
info!("Created new project at {}", directory);
Ok(())
}
pub fn new_command_args() -> clap::App<'static> {
clap::App::new("new")
.about("Create a document")
.args(args::get_config_args())
.arg(
clap::Arg::new("TITLE")
.required(true)
.help("Title of the post")
.takes_value(true),
)
.arg(
clap::Arg::new("file")
.short('f')
.long("file")
.value_name("DIR_OR_FILE")
.help("New document's parent directory or file (default: `<CWD>/title.ext`)")
.takes_value(true),
)
.arg(
clap::Arg::new("with-ext")
.long("with-ext")
.value_name("EXT")
.help("The default file's extension (e.g. `liquid`)")
.takes_value(true),
)
}
pub fn new_command(matches: &clap::ArgMatches) -> Result<()> {
let mut config = args::get_config(matches)?;
config.include_drafts = true;
let config = cobalt::cobalt_model::Config::from_config(config)?;
let title = matches.value_of("TITLE").unwrap();
let mut file = env::current_dir().expect("How does this fail?");
if let Some(rel_file) = matches.value_of("file") {
file.push(path::Path::new(rel_file))
Ok(())
}
let ext = matches.value_of("with-ext");
create_new_document(&config, title, file, ext)
.with_context(|_| failure::format_err!("Could not create `{}`", title))?;
Ok(())
}
pub fn rename_command_args() -> clap::App<'static> {
clap::App::new("rename")
.about("Rename a document")
.args(args::get_config_args())
.arg(
clap::Arg::new("SRC")
.required(true)
.help("File to rename")
.takes_value(true),
)
.arg(
clap::Arg::new("TITLE")
.required(true)
.help("Title of the post")
.takes_value(true),
)
.arg(
clap::Arg::new("file")
.short('f')
.long("file")
.value_name("DIR_OR_FILE")
.help("New document's parent directory or file (default: `<CWD>/title.ext`)")
.takes_value(true),
)
/// Create a document
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
pub struct NewArgs {
/// Title of the post
pub title: String,
/// New document's parent directory or file (default: `<CWD>/title.ext`)
#[clap(short, long, value_name = "DIR_OR_FILE", parse(from_os_str))]
pub file: Option<path::PathBuf>,
/// The default file's extension (e.g. `liquid`)
#[clap(long, value_name = "EXT")]
pub with_ext: Option<String>,
#[clap(flatten, help_heading = "CONFIG")]
pub config: args::ConfigArgs,
}
pub fn rename_command(matches: &clap::ArgMatches) -> Result<()> {
let mut config = args::get_config(matches)?;
config.include_drafts = true;
let config = cobalt::cobalt_model::Config::from_config(config)?;
impl NewArgs {
pub fn run(&self) -> Result<()> {
let mut config = self.config.load_config()?;
config.include_drafts = true;
let config = cobalt::cobalt_model::Config::from_config(config)?;
let source = path::PathBuf::from(matches.value_of("SRC").unwrap());
let title = self.title.as_ref();
let title = matches.value_of("TITLE").unwrap();
let mut file = env::current_dir().expect("How does this fail?");
if let Some(rel_file) = self.file.as_deref() {
file.push(rel_file)
}
let mut file = env::current_dir().expect("How does this fail?");
if let Some(rel_file) = matches.value_of("file") {
file.push(path::Path::new(rel_file))
let ext = self.with_ext.as_deref();
create_new_document(&config, title, file, ext)
.with_context(|_| failure::format_err!("Could not create `{}`", title))?;
Ok(())
}
let file = file;
rename_document(&config, source, title, file)
.with_context(|_| failure::format_err!("Could not rename `{}`", title))?;
Ok(())
}
pub fn publish_command_args() -> clap::App<'static> {
clap::App::new("publish")
.about("Publish a document")
.args(args::get_config_args())
.arg(
clap::Arg::new("FILENAME")
.required(true)
.help("Document path to publish")
.takes_value(true),
)
/// Rename a document
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
pub struct RenameArgs {
/// File to rename
#[clap(value_name = "FILE", parse(from_os_str))]
pub src: path::PathBuf,
/// Title of the post
pub title: String,
/// New document's parent directory or file (default: `<CWD>/title.ext`)
#[clap(short, long, value_name = "DIR_OR_FILE", parse(from_os_str))]
pub file: Option<path::PathBuf>,
#[clap(flatten, help_heading = "CONFIG")]
pub config: args::ConfigArgs,
}
pub fn publish_command(matches: &clap::ArgMatches) -> Result<()> {
let filename = matches
.value_of("FILENAME")
.expect("required parameters are present");
let mut file = env::current_dir().expect("How does this fail?");
file.push(path::Path::new(filename));
let file = file;
let mut config = args::get_config(matches)?;
config.include_drafts = true;
let config = cobalt::cobalt_model::Config::from_config(config)?;
impl RenameArgs {
pub fn run(&self) -> Result<()> {
let mut config = self.config.load_config()?;
config.include_drafts = true;
let config = cobalt::cobalt_model::Config::from_config(config)?;
publish_document(&config, &file)
.with_context(|_| failure::format_err!("Could not publish `{:?}`", file))?;
let source = self.src.clone();
Ok(())
let title = self.title.as_ref();
let mut file = env::current_dir().expect("How does this fail?");
if let Some(rel_file) = self.file.as_deref() {
file.push(rel_file)
}
rename_document(&config, source, title, file)
.with_context(|_| failure::format_err!("Could not rename `{}`", title))?;
Ok(())
}
}
/// Publish a document
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
pub struct PublishArgs {
/// Document to publish
#[clap(value_name = "FILE", parse(from_os_str))]
pub filename: path::PathBuf,
#[clap(flatten, help_heading = "CONFIG")]
pub config: args::ConfigArgs,
}
impl PublishArgs {
pub fn run(&self) -> Result<()> {
let mut config = self.config.load_config()?;
config.include_drafts = true;
let config = cobalt::cobalt_model::Config::from_config(config)?;
let filename = self.filename.as_path();
let mut file = env::current_dir().expect("How does this fail?");
file.push(path::Path::new(filename));
publish_document(&config, &file)
.with_context(|_| failure::format_err!("Could not publish `{:?}`", file))?;
Ok(())
}
}
const COBALT_YML: &str = "

View file

@ -15,76 +15,67 @@ use crate::args;
use crate::build;
use crate::error::*;
pub fn serve_command_args() -> clap::App<'static> {
clap::App::new("serve")
.about("build, serve, and watch the project at the source dir")
.args(args::get_config_args())
.arg(
clap::Arg::new("port")
.short('P')
.long("port")
.value_name("INT")
.help("Port to serve from")
.default_value("3000")
.takes_value(true),
)
.arg(
clap::Arg::new("host")
.long("host")
.value_name("host-name/IP")
.help("Host to serve from")
.default_value("localhost")
.takes_value(true),
)
.arg(
clap::Arg::new("no-watch")
.long("no-watch")
.help("Disable rebuilding on change")
.conflicts_with("drafts")
.takes_value(false),
)
.arg(
clap::Arg::new("open")
.long("open")
.help("Open in browser")
.takes_value(false),
)
/// Build, serve, and watch the project at the source dir
#[derive(Clone, Debug, PartialEq, Eq, clap::Args)]
pub struct ServeArgs {
/// Open a browser
#[clap(long)]
pub open: bool,
/// Host to serve from
#[clap(long, value_name = "HOSTNAME_OR_IP", default_value = "localhost")]
pub host: String,
/// Port to serve from
#[clap(short = 'P', long, value_name = "NUM", default_value_t = 3000)]
pub port: usize,
/// Disable rebuilding on change
#[clap(long)]
pub no_watch: bool,
#[clap(flatten, help_heading = "CONFIG")]
pub config: args::ConfigArgs,
}
pub fn serve_command(matches: &clap::ArgMatches) -> Result<()> {
let host = matches.value_of("host").unwrap().to_string();
let port = matches.value_of("port").unwrap().to_string();
let ip = format!("{}:{}", host, port);
let open_in_browser = matches.is_present("open");
let url = format!("http://{}", ip);
impl ServeArgs {
pub fn run(&self) -> Result<()> {
let host = self.host.as_str();
let port = self.port;
let ip = format!("{}:{}", host, port);
let url = format!("http://{}", ip);
let open_in_browser = self.open;
let mut config = args::get_config(matches)?;
debug!("Overriding config `site.base_url` with `{}`", ip);
config.site.base_url = Some(format!("http://{}", ip).into());
let config = cobalt::cobalt_model::Config::from_config(config)?;
let dest = path::Path::new(&config.destination).to_owned();
let mut config = self.config.load_config()?;
debug!("Overriding config `site.base_url` with `{}`", ip);
config.site.base_url = Some(format!("http://{}", ip).into());
let config = cobalt::cobalt_model::Config::from_config(config)?;
build::build(config.clone())?;
let dest = path::Path::new(&config.destination).to_owned();
if open_in_browser {
open_browser(url)?;
build::build(config.clone())?;
if open_in_browser {
open_browser(url)?;
}
if self.no_watch {
serve(&dest, &ip)?;
} else {
info!("Watching {:?} for changes", &config.source);
thread::spawn(move || {
let e = serve(&dest, &ip);
if let Some(e) = e.err() {
error!("{}", e);
}
process::exit(1)
});
watch(&config)?;
}
Ok(())
}
if matches.is_present("no-watch") {
serve(&dest, &ip)?;
} else {
info!("Watching {:?} for changes", &config.source);
thread::spawn(move || {
let e = serve(&dest, &ip);
if let Some(e) = e.err() {
error!("{}", e);
}
process::exit(1)
});
watch(&config)?;
}
Ok(())
}
fn static_file_handler(dest: &path::Path, req: Request) -> Result<()> {

View file

@ -10,7 +10,7 @@ pub fn invalid_calls() {
.unwrap()
.assert()
.failure()
.stderr(predicate::str::contains("requires a subcommand").from_utf8());
.stderr(predicate::str::contains("SUBCOMMANDS:").from_utf8());
process::Command::cargo_bin("cobalt")
.unwrap()
@ -29,27 +29,7 @@ pub fn log_levels_trace() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "-L", "trace"])
.current_dir(project_root.path())
.assert()
.success()
.stderr(predicate::str::contains("TRACE").from_utf8())
.stderr(predicate::str::contains("DEBUG").from_utf8())
.stderr(predicate::str::contains("INFO").from_utf8());
project_root.close().unwrap();
}
#[test]
pub fn log_levels_trace_alias() {
let project_root = assert_fs::TempDir::new().unwrap();
project_root
.copy_from("tests/fixtures/example", &["**"])
.unwrap();
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace"])
.args(&["-vv", "build"])
.current_dir(project_root.path())
.assert()
.success()
@ -69,7 +49,7 @@ pub fn log_levels_debug() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "-L", "debug"])
.args(&["-v", "build"])
.current_dir(project_root.path())
.assert()
.success()
@ -89,7 +69,7 @@ pub fn log_levels_info() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "-L", "info"])
.args(&["build"])
.current_dir(project_root.path())
.assert()
.success()
@ -109,7 +89,7 @@ pub fn log_levels_silent() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--silent"])
.args(&["-qqqq", "build"])
.current_dir(project_root.path())
.assert()
.success()
@ -130,7 +110,7 @@ pub fn clean() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace", "-d", "_dest"])
.args(&["-vv", "build", "-d", "_dest"])
.current_dir(project_root.path())
.assert()
.success();
@ -138,7 +118,7 @@ pub fn clean() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["clean", "--trace", "-d", "_dest"])
.args(&["-vv", "clean", "-d", "_dest"])
.current_dir(project_root.path())
.assert()
.success();
@ -158,7 +138,7 @@ pub fn clean_empty() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["clean", "--trace", "-d", "_dest"])
.args(&["-vv", "clean", "-d", "_dest"])
.current_dir(project_root.path())
.assert()
.success();
@ -175,7 +155,7 @@ pub fn init_project_can_build() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["init", "--trace"])
.args(&["-vv", "init"])
.current_dir(project_root.path())
.assert()
.success();
@ -183,7 +163,7 @@ pub fn init_project_can_build() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace", "-d", "_dest", "--drafts"])
.args(&["-vv", "build", "-d", "_dest", "--drafts"])
.current_dir(project_root.path())
.assert()
.success();
@ -200,14 +180,14 @@ pub fn new_page_can_build() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["init", "--trace"])
.args(&["-vv", "init"])
.current_dir(project_root.path())
.assert()
.success();
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["new", "--trace", "My New Special Page"])
.args(&["-vv", "new", "My New Special Page"])
.current_dir(project_root.path())
.assert()
.success();
@ -218,7 +198,7 @@ pub fn new_page_can_build() {
dest.assert(predicate::path::missing());
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace", "-d", "_dest", "--drafts"])
.args(&["-vv", "build", "-d", "_dest", "--drafts"])
.current_dir(project_root.path())
.assert()
.success();
@ -235,14 +215,14 @@ pub fn new_post_can_build() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["init", "--trace"])
.args(&["-vv", "init"])
.current_dir(project_root.path())
.assert()
.success();
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["new", "--trace", "My New Special Post"])
.args(&["-vv", "new", "My New Special Post"])
.current_dir(project_root.path().join("posts"))
.assert()
.success();
@ -253,7 +233,7 @@ pub fn new_post_can_build() {
dest.assert(predicate::path::missing());
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace", "-d", "_dest", "--drafts"])
.args(&["-vv", "build", "-d", "_dest", "--drafts"])
.current_dir(project_root.path())
.assert()
.success();
@ -270,14 +250,14 @@ pub fn rename_page_can_build() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["init", "--trace"])
.args(&["-vv", "init"])
.current_dir(project_root.path())
.assert()
.success();
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["new", "--trace", "My New Special Page"])
.args(&["-vv", "new", "My New Special Page"])
.current_dir(project_root.path())
.assert()
.success();
@ -288,8 +268,8 @@ pub fn rename_page_can_build() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&[
"-vv",
"rename",
"--trace",
"my-new-special-page.md",
"New and Improved!",
])
@ -306,7 +286,7 @@ pub fn rename_page_can_build() {
dest.assert(predicate::path::missing());
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace", "-d", "_dest", "--drafts"])
.args(&["-vv", "build", "-d", "_dest", "--drafts"])
.current_dir(project_root.path())
.assert()
.success();
@ -323,14 +303,14 @@ pub fn rename_post_can_build() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["init", "--trace"])
.args(&["-vv", "init"])
.current_dir(project_root.path())
.assert()
.success();
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["new", "--trace", "My New Special Post"])
.args(&["-vv", "new", "My New Special Post"])
.current_dir(project_root.path().join("posts"))
.assert()
.success();
@ -341,8 +321,8 @@ pub fn rename_post_can_build() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&[
"-vv",
"rename",
"--trace",
"my-new-special-post.md",
"New and Improved!",
])
@ -359,7 +339,7 @@ pub fn rename_post_can_build() {
dest.assert(predicate::path::missing());
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace", "-d", "_dest", "--drafts"])
.args(&["-vv", "build", "-d", "_dest", "--drafts"])
.current_dir(project_root.path())
.assert()
.success();
@ -376,7 +356,7 @@ pub fn publish_post_can_build() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["init", "--trace"])
.args(&["-vv", "init"])
.current_dir(project_root.path())
.assert()
.success();
@ -397,7 +377,7 @@ posts:
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["new", "--trace", "My New Special Post"])
.args(&["-vv", "new", "My New Special Post"])
.current_dir(project_root.path().join("posts"))
.assert()
.success();
@ -407,7 +387,7 @@ posts:
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["publish", "--trace", "my-new-special-post.md"])
.args(&["-vv", "publish", "my-new-special-post.md"])
.current_dir(project_root.path().join("posts"))
.assert()
.success();
@ -418,7 +398,7 @@ posts:
dest.assert(predicate::path::missing());
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace", "-d", "_dest", "--drafts"])
.args(&["-vv", "build", "-d", "_dest", "--drafts"])
.current_dir(project_root.path())
.assert()
.success();
@ -435,7 +415,7 @@ pub fn publish_date_in_post() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["init", "--trace"])
.args(&["-vv", "init"])
.current_dir(project_root.path())
.assert()
.success();
@ -456,7 +436,7 @@ posts:
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["new", "--trace", "My New Special Post"])
.args(&["-vv", "new", "My New Special Post"])
.current_dir(project_root.path().join("posts"))
.assert()
.success();
@ -466,7 +446,7 @@ posts:
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["publish", "--trace", "my-new-special-post.md"])
.args(&["-vv", "publish", "my-new-special-post.md"])
.current_dir(project_root.path().join("posts"))
.assert()
.success();
@ -477,7 +457,7 @@ posts:
dest.assert(predicate::path::missing());
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace", "-d", "_dest", "--drafts"])
.args(&["-vv", "build", "-d", "_dest", "--drafts"])
.current_dir(project_root.path())
.assert()
.success();
@ -494,7 +474,7 @@ pub fn publish_draft_moves_dir() {
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["init", "--trace"])
.args(&["-vv", "init"])
.current_dir(project_root.path())
.assert()
.success();
@ -517,7 +497,7 @@ posts:
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["new", "--trace", "My New Special Post"])
.args(&["-vv", "new", "My New Special Post"])
.current_dir(project_root.path().join("_drafts"))
.assert()
.success();
@ -527,7 +507,7 @@ posts:
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["publish", "--trace", "my-new-special-post.md"])
.args(&["-vv", "publish", "my-new-special-post.md"])
.current_dir(project_root.path().join("_drafts"))
.assert()
.success();
@ -541,7 +521,7 @@ posts:
dest.assert(predicate::path::missing());
process::Command::cargo_bin("cobalt")
.unwrap()
.args(&["build", "--trace", "-d", "_dest", "--drafts"])
.args(&["-vv", "build", "-d", "_dest", "--drafts"])
.current_dir(project_root.path())
.assert()
.success();