commit ec363d2d4186899a6799efc2f4815b4b0a119b6a Author: Henry de Valence Date: Thu Aug 29 14:46:54 2019 -0700 Create workspace skeleton based on design.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..94c68ab7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Cargo files +/target/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..98e9659b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +members = [ + "zebra-chain", + "zebra-network", + "zebra-storage", + "zebra-script", + "zebra-consensus", + "zebra-rpc", + "zebra-client", + "zebra-reactor", + "zebrad", +] + diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml new file mode 100644 index 00000000..20580e65 --- /dev/null +++ b/zebra-chain/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zebra-chain" +version = "0.1.0" +authors = ["Henry de Valence "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/zebra-chain/src/lib.rs b/zebra-chain/src/lib.rs new file mode 100644 index 00000000..31e1bb20 --- /dev/null +++ b/zebra-chain/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/zebra-client/Cargo.toml b/zebra-client/Cargo.toml new file mode 100644 index 00000000..7b5000f1 --- /dev/null +++ b/zebra-client/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zebra-client" +version = "0.1.0" +authors = ["Henry de Valence "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/zebra-client/src/lib.rs b/zebra-client/src/lib.rs new file mode 100644 index 00000000..31e1bb20 --- /dev/null +++ b/zebra-client/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/zebra-consensus/Cargo.toml b/zebra-consensus/Cargo.toml new file mode 100644 index 00000000..5906f264 --- /dev/null +++ b/zebra-consensus/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zebra-consensus" +version = "0.1.0" +authors = ["Henry de Valence "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs new file mode 100644 index 00000000..31e1bb20 --- /dev/null +++ b/zebra-consensus/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/zebra-network/Cargo.toml b/zebra-network/Cargo.toml new file mode 100644 index 00000000..6e3a5589 --- /dev/null +++ b/zebra-network/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zebra-network" +version = "0.1.0" +authors = ["Henry de Valence "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/zebra-network/src/lib.rs b/zebra-network/src/lib.rs new file mode 100644 index 00000000..31e1bb20 --- /dev/null +++ b/zebra-network/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/zebra-reactor/Cargo.toml b/zebra-reactor/Cargo.toml new file mode 100644 index 00000000..7a23d459 --- /dev/null +++ b/zebra-reactor/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zebra-reactor" +version = "0.1.0" +authors = ["Henry de Valence "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/zebra-reactor/src/lib.rs b/zebra-reactor/src/lib.rs new file mode 100644 index 00000000..31e1bb20 --- /dev/null +++ b/zebra-reactor/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml new file mode 100644 index 00000000..a675cfe1 --- /dev/null +++ b/zebra-rpc/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zebra-rpc" +version = "0.1.0" +authors = ["Henry de Valence "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/zebra-rpc/src/lib.rs b/zebra-rpc/src/lib.rs new file mode 100644 index 00000000..31e1bb20 --- /dev/null +++ b/zebra-rpc/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/zebra-script/Cargo.toml b/zebra-script/Cargo.toml new file mode 100644 index 00000000..084ebfac --- /dev/null +++ b/zebra-script/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zebra-script" +version = "0.1.0" +authors = ["Henry de Valence "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/zebra-script/src/lib.rs b/zebra-script/src/lib.rs new file mode 100644 index 00000000..31e1bb20 --- /dev/null +++ b/zebra-script/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/zebra-storage/Cargo.toml b/zebra-storage/Cargo.toml new file mode 100644 index 00000000..c122a5e1 --- /dev/null +++ b/zebra-storage/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "zebra-storage" +version = "0.1.0" +authors = ["Henry de Valence "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/zebra-storage/src/lib.rs b/zebra-storage/src/lib.rs new file mode 100644 index 00000000..31e1bb20 --- /dev/null +++ b/zebra-storage/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml new file mode 100644 index 00000000..266b50e6 --- /dev/null +++ b/zebrad/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "zebrad" +authors = [] +version = "0.1.0" +edition = "2018" + +[dependencies] +abscissa_core = "0.3.0" +failure = "0.1" +gumdrop = "0.6" +lazy_static = "1" +serde = { version = "1", features = ["serde_derive"] } + +[dev-dependencies.abscissa_core] +version = "0.3.0" +features = ["testing"] + diff --git a/zebrad/README.md b/zebrad/README.md new file mode 100644 index 00000000..4dee5cbb --- /dev/null +++ b/zebrad/README.md @@ -0,0 +1,14 @@ +# Zebrad + +Zebrad is an application. + +## Getting Started + +This application is authored using [Abscissa], a Rust application framework. + +For more information, see: + +[Documentation] + +[Abscissa]: https://github.com/iqlusioninc/abscissa +[Documentation]: https://docs.rs/abscissa_core/ diff --git a/zebrad/src/application.rs b/zebrad/src/application.rs new file mode 100644 index 00000000..0d864bde --- /dev/null +++ b/zebrad/src/application.rs @@ -0,0 +1,111 @@ +//! Zebrad Abscissa Application + +use crate::{commands::ZebradCmd, config::ZebradConfig}; +use abscissa_core::{ + application, config, logging, Application, EntryPoint, FrameworkError, StandardPaths, +}; +use lazy_static::lazy_static; + +lazy_static! { + /// Application state + pub static ref APPLICATION: application::Lock = application::Lock::default(); +} + +/// Obtain a read-only (multi-reader) lock on the application state. +/// +/// Panics if the application state has not been initialized. +pub fn app_reader() -> application::lock::Reader { + APPLICATION.read() +} + +/// Obtain an exclusive mutable lock on the application state. +pub fn app_writer() -> application::lock::Writer { + APPLICATION.write() +} + +/// Obtain a read-only (multi-reader) lock on the application configuration. +/// +/// Panics if the application configuration has not been loaded. +pub fn app_config() -> config::Reader { + config::Reader::new(&APPLICATION) +} + +/// Zebrad Application +#[derive(Debug)] +pub struct ZebradApp { + /// Application configuration. + config: Option, + + /// Application state. + state: application::State, +} + +/// Initialize a new application instance. +/// +/// By default no configuration is loaded, and the framework state is +/// initialized to a default, empty state (no components, threads, etc). +impl Default for ZebradApp { + fn default() -> Self { + Self { + config: None, + state: application::State::default(), + } + } +} + +impl Application for ZebradApp { + /// Entrypoint command for this application. + type Cmd = EntryPoint; + + /// Application configuration. + type Cfg = ZebradConfig; + + /// Paths to resources within the application. + type Paths = StandardPaths; + + /// Accessor for application configuration. + fn config(&self) -> &ZebradConfig { + self.config.as_ref().expect("config not loaded") + } + + /// Borrow the application state immutably. + fn state(&self) -> &application::State { + &self.state + } + + /// Borrow the application state mutably. + fn state_mut(&mut self) -> &mut application::State { + &mut self.state + } + + /// Register all components used by this application. + /// + /// If you would like to add additional components to your application + /// beyond the default ones provided by the framework, this is the place + /// to do so. + fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> { + let components = self.framework_components(command)?; + self.state.components.register(components) + } + + /// Post-configuration lifecycle callback. + /// + /// Called regardless of whether config is loaded to indicate this is the + /// time in app lifecycle when configuration would be loaded if + /// possible. + fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError> { + // Configure components + self.state.components.after_config(&config)?; + self.config = Some(config); + Ok(()) + } + + /// Get logging configuration from command-line options + fn logging_config(&self, command: &EntryPoint) -> logging::Config { + if command.verbose { + logging::Config::verbose() + } else { + logging::Config::default() + } + } +} diff --git a/zebrad/src/bin/zebrad/main.rs b/zebrad/src/bin/zebrad/main.rs new file mode 100644 index 00000000..a105cbb5 --- /dev/null +++ b/zebrad/src/bin/zebrad/main.rs @@ -0,0 +1,11 @@ +//! Main entry point for Zebrad + +#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)] +#![forbid(unsafe_code)] + +use zebrad::application::APPLICATION; + +/// Boot Zebrad +fn main() { + abscissa_core::boot(&APPLICATION); +} diff --git a/zebrad/src/commands.rs b/zebrad/src/commands.rs new file mode 100644 index 00000000..11c18d75 --- /dev/null +++ b/zebrad/src/commands.rs @@ -0,0 +1,72 @@ +//! Zebrad Subcommands +//! +//! This is where you specify the subcommands of your application. +//! +//! The default application comes with two subcommands: +//! +//! - `start`: launches the application +//! - `version`: print application version +//! +//! See the `impl Configurable` below for how to specify the path to the +//! application's configuration file. + +mod start; +mod version; + +use self::{start::StartCmd, version::VersionCmd}; +use crate::config::ZebradConfig; +use abscissa_core::{ + config::Override, Command, Configurable, FrameworkError, Help, Options, Runnable, +}; +use std::path::PathBuf; + +/// Zebrad Configuration Filename +pub const CONFIG_FILE: &str = "zebrad.toml"; + +/// Zebrad Subcommands +#[derive(Command, Debug, Options, Runnable)] +pub enum ZebradCmd { + /// The `help` subcommand + #[options(help = "get usage information")] + Help(Help), + + /// The `start` subcommand + #[options(help = "start the application")] + Start(StartCmd), + + /// The `version` subcommand + #[options(help = "display version information")] + Version(VersionCmd), +} + +/// This trait allows you to define how application configuration is loaded. +impl Configurable for ZebradCmd { + /// Location of the configuration file + fn config_path(&self) -> Option { + // Check if the config file exists, and if it does not, ignore it. + // If you'd like for a missing configuration file to be a hard error + // instead, always return `Some(CONFIG_FILE)` here. + let filename = PathBuf::from(CONFIG_FILE); + + if filename.exists() { + Some(filename) + } else { + None + } + } + + /// Apply changes to the config after it's been loaded, e.g. overriding + /// values in a config file using command-line options. + /// + /// This can be safely deleted if you don't want to override config + /// settings from command-line options. + fn process_config( + &self, + config: ZebradConfig, + ) -> Result { + match self { + ZebradCmd::Start(cmd) => cmd.override_config(config), + _ => Ok(config), + } + } +} diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs new file mode 100644 index 00000000..137da820 --- /dev/null +++ b/zebrad/src/commands/start.rs @@ -0,0 +1,46 @@ +//! `start` subcommand - example of how to write a subcommand + +/// App-local prelude includes `app_reader()`/`app_writer()`/`app_config()` +/// accessors along with logging macros. Customize as you see fit. +use crate::prelude::*; + +use crate::config::ZebradConfig; +use abscissa_core::{config, Command, FrameworkError, Options, Runnable}; + +/// `start` subcommand +/// +/// The `Options` proc macro generates an option parser based on the struct +/// definition, and is defined in the `gumdrop` crate. See their documentation +/// for a more comprehensive example: +/// +/// +#[derive(Command, Debug, Options)] +pub struct StartCmd { + /// To whom are we saying hello? + #[options(free)] + recipient: Vec, +} + +impl Runnable for StartCmd { + /// Start the application. + fn run(&self) { + let config = app_config(); + println!("Hello, {}!", &config.hello.recipient); + } +} + +impl config::Override for StartCmd { + // Process the given command line options, overriding settings from + // a configuration file using explicit flags taken from command-line + // arguments. + fn override_config( + &self, + mut config: ZebradConfig, + ) -> Result { + if !self.recipient.is_empty() { + config.hello.recipient = self.recipient.join(" "); + } + + Ok(config) + } +} diff --git a/zebrad/src/commands/version.rs b/zebrad/src/commands/version.rs new file mode 100644 index 00000000..dac09957 --- /dev/null +++ b/zebrad/src/commands/version.rs @@ -0,0 +1,21 @@ +//! `version` subcommand + +#![allow(clippy::never_loop)] + +use super::ZebradCmd; +use abscissa_core::{Command, Options, Runnable}; + +/// `version` subcommand +#[derive(Command, Debug, Default, Options)] +pub struct VersionCmd {} + +impl Runnable for VersionCmd { + /// Print version message + fn run(&self) { + println!( + "{} {}", + ZebradCmd::name(), + ZebradCmd::version() + ); + } +} diff --git a/zebrad/src/config.rs b/zebrad/src/config.rs new file mode 100644 index 00000000..66ab61d6 --- /dev/null +++ b/zebrad/src/config.rs @@ -0,0 +1,46 @@ +//! Zebrad Config +//! +//! See instructions in `commands.rs` to specify the path to your +//! application's configuration file and/or command-line options +//! for specifying it. + +use abscissa_core::Config; +use serde::{Deserialize, Serialize}; + +/// Zebrad Configuration +#[derive(Clone, Config, Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ZebradConfig { + /// An example configuration section + pub hello: ExampleSection, +} + +/// Default configuration settings. +/// +/// Note: if your needs are as simple as below, you can +/// use `#[derive(Default)]` on ZebradConfig instead. +impl Default for ZebradConfig { + fn default() -> Self { + Self { + hello: ExampleSection::default(), + } + } +} + +/// Example configuration section. +/// +/// Delete this and replace it with your actual configuration structs. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ExampleSection { + /// Example configuration value + pub recipient: String, +} + +impl Default for ExampleSection { + fn default() -> Self { + Self { + recipient: "world".to_owned(), + } + } +} diff --git a/zebrad/src/error.rs b/zebrad/src/error.rs new file mode 100644 index 00000000..d9de7978 --- /dev/null +++ b/zebrad/src/error.rs @@ -0,0 +1,39 @@ +//! Error types + +use abscissa_core::err; +use failure::Fail; +use std::{fmt, io}; + +/// Error type +#[derive(Debug)] +pub struct Error(abscissa_core::Error); + +/// Kinds of errors +#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] +pub enum ErrorKind { + /// Error in configuration file + #[fail(display = "config error")] + Config, + + /// Input/output error + #[fail(display = "I/O error")] + Io, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl From> for Error { + fn from(other: abscissa_core::Error) -> Self { + Error(other) + } +} + +impl From for Error { + fn from(other: io::Error) -> Self { + err!(ErrorKind::Io, other).into() + } +} diff --git a/zebrad/src/lib.rs b/zebrad/src/lib.rs new file mode 100644 index 00000000..00ddc5bf --- /dev/null +++ b/zebrad/src/lib.rs @@ -0,0 +1,14 @@ +//! Zebrad +//! +//! Application based on the [Abscissa] framework. +//! +//! [Abscissa]: https://github.com/iqlusioninc/abscissa + +#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)] +#![forbid(unsafe_code)] + +pub mod application; +pub mod commands; +pub mod config; +pub mod error; +pub mod prelude; diff --git a/zebrad/src/prelude.rs b/zebrad/src/prelude.rs new file mode 100644 index 00000000..220548fa --- /dev/null +++ b/zebrad/src/prelude.rs @@ -0,0 +1,11 @@ +//! Application-local prelude: conveniently import types/functions/macros +//! which are generally useful and should be available everywhere. + +/// Application state accessors +pub use crate::application::{app_config, app_reader, app_writer}; + +/// Commonly used Abscissa traits +pub use abscissa_core::{Application, Command, Runnable}; + +/// Logging macros +pub use abscissa_core::log::{debug, error, info, log, log_enabled, trace, warn}; diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs new file mode 100644 index 00000000..d0c00fde --- /dev/null +++ b/zebrad/tests/acceptance.rs @@ -0,0 +1,85 @@ +//! Acceptance test: runs the application as a subprocess and asserts its +//! output for given argument combinations matches what is expected. +//! +//! Modify and/or delete these as you see fit to test the specific needs of +//! your application. +//! +//! For more information, see: +//! + +#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)] +#![forbid(unsafe_code)] + +use abscissa_core::testing::prelude::*; +use zebrad::config::ZebradConfig; +use lazy_static::lazy_static; + +lazy_static! { + /// Executes your application binary via `cargo run`. + /// + /// Storing this value in a `lazy_static!` ensures that all instances of + /// the runner acquire a mutex when executing commands and inspecting + /// exit statuses, serializing what would otherwise be multithreaded + /// invocations as `cargo test` executes tests in parallel by default. + pub static ref RUNNER: CmdRunner = CmdRunner::default(); +} + +/// Use `ZebradConfig::default()` value if no config or args +#[test] +fn start_no_args() { + let mut runner = RUNNER.clone(); + let mut cmd = runner.arg("start").capture_stdout().run(); + cmd.stdout().expect_line("Hello, world!"); + cmd.wait().unwrap().expect_success(); +} + +/// Use command-line argument value +#[test] +fn start_with_args() { + let mut runner = RUNNER.clone(); + let mut cmd = runner + .args(&["start", "acceptance", "test"]) + .capture_stdout() + .run(); + + cmd.stdout().expect_line("Hello, acceptance test!"); + cmd.wait().unwrap().expect_success(); +} + +/// Use configured value +#[test] +fn start_with_config_no_args() { + let mut config = ZebradConfig::default(); + config.hello.recipient = "configured recipient".to_owned(); + let expected_line = format!("Hello, {}!", &config.hello.recipient); + + let mut runner = RUNNER.clone(); + let mut cmd = runner.config(&config).arg("start").capture_stdout().run(); + cmd.stdout().expect_line(&expected_line); + cmd.wait().unwrap().expect_success(); +} + +/// Override configured value with command-line argument +#[test] +fn start_with_config_and_args() { + let mut config = ZebradConfig::default(); + config.hello.recipient = "configured recipient".to_owned(); + + let mut runner = RUNNER.clone(); + let mut cmd = runner + .config(&config) + .args(&["start", "acceptance", "test"]) + .capture_stdout() + .run(); + + cmd.stdout().expect_line("Hello, acceptance test!"); + cmd.wait().unwrap().expect_success(); +} + +/// Example of a test which matches a regular expression +#[test] +fn version_no_args() { + let mut runner = RUNNER.clone(); + let mut cmd = runner.arg("version").capture_stdout().run(); + cmd.stdout().expect_regex(r"\A\w+ [\d\.\-]+\z"); +}