Create workspace skeleton based on design.md

This commit is contained in:
Henry de Valence 2019-08-29 14:46:54 -07:00
commit ec363d2d41
30 changed files with 631 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Cargo files
/target/
Cargo.lock

13
Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[workspace]
members = [
"zebra-chain",
"zebra-network",
"zebra-storage",
"zebra-script",
"zebra-consensus",
"zebra-rpc",
"zebra-client",
"zebra-reactor",
"zebrad",
]

9
zebra-chain/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "zebra-chain"
version = "0.1.0"
authors = ["Henry de Valence <hdevalence@hdevalence.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

7
zebra-chain/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

9
zebra-client/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "zebra-client"
version = "0.1.0"
authors = ["Henry de Valence <hdevalence@hdevalence.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

7
zebra-client/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

View File

@ -0,0 +1,9 @@
[package]
name = "zebra-consensus"
version = "0.1.0"
authors = ["Henry de Valence <hdevalence@hdevalence.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

9
zebra-network/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "zebra-network"
version = "0.1.0"
authors = ["Henry de Valence <hdevalence@hdevalence.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

7
zebra-network/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

9
zebra-reactor/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "zebra-reactor"
version = "0.1.0"
authors = ["Henry de Valence <hdevalence@hdevalence.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

7
zebra-reactor/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

9
zebra-rpc/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "zebra-rpc"
version = "0.1.0"
authors = ["Henry de Valence <hdevalence@hdevalence.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

7
zebra-rpc/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

9
zebra-script/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "zebra-script"
version = "0.1.0"
authors = ["Henry de Valence <hdevalence@hdevalence.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

7
zebra-script/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

9
zebra-storage/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "zebra-storage"
version = "0.1.0"
authors = ["Henry de Valence <hdevalence@hdevalence.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

7
zebra-storage/src/lib.rs Normal file
View File

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

17
zebrad/Cargo.toml Normal file
View File

@ -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"]

14
zebrad/README.md Normal file
View File

@ -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/

111
zebrad/src/application.rs Normal file
View File

@ -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<ZebradApp> = 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<ZebradApp> {
APPLICATION.read()
}
/// Obtain an exclusive mutable lock on the application state.
pub fn app_writer() -> application::lock::Writer<ZebradApp> {
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<ZebradApp> {
config::Reader::new(&APPLICATION)
}
/// Zebrad Application
#[derive(Debug)]
pub struct ZebradApp {
/// Application configuration.
config: Option<ZebradConfig>,
/// Application state.
state: application::State<Self>,
}
/// 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<ZebradCmd>;
/// 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> {
&self.state
}
/// Borrow the application state mutably.
fn state_mut(&mut self) -> &mut application::State<Self> {
&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<ZebradCmd>) -> logging::Config {
if command.verbose {
logging::Config::verbose()
} else {
logging::Config::default()
}
}
}

View File

@ -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);
}

72
zebrad/src/commands.rs Normal file
View File

@ -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<Self>),
/// 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<ZebradConfig> for ZebradCmd {
/// Location of the configuration file
fn config_path(&self) -> Option<PathBuf> {
// 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<ZebradConfig, FrameworkError> {
match self {
ZebradCmd::Start(cmd) => cmd.override_config(config),
_ => Ok(config),
}
}
}

View File

@ -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:
///
/// <https://docs.rs/gumdrop/>
#[derive(Command, Debug, Options)]
pub struct StartCmd {
/// To whom are we saying hello?
#[options(free)]
recipient: Vec<String>,
}
impl Runnable for StartCmd {
/// Start the application.
fn run(&self) {
let config = app_config();
println!("Hello, {}!", &config.hello.recipient);
}
}
impl config::Override<ZebradConfig> 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<ZebradConfig, FrameworkError> {
if !self.recipient.is_empty() {
config.hello.recipient = self.recipient.join(" ");
}
Ok(config)
}
}

View File

@ -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()
);
}
}

46
zebrad/src/config.rs Normal file
View File

@ -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(),
}
}
}

39
zebrad/src/error.rs Normal file
View File

@ -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<ErrorKind>);
/// 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<abscissa_core::Error<ErrorKind>> for Error {
fn from(other: abscissa_core::Error<ErrorKind>) -> Self {
Error(other)
}
}
impl From<io::Error> for Error {
fn from(other: io::Error) -> Self {
err!(ErrorKind::Io, other).into()
}
}

14
zebrad/src/lib.rs Normal file
View File

@ -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;

11
zebrad/src/prelude.rs Normal file
View File

@ -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};

View File

@ -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:
//! <https://docs.rs/abscissa_core/latest/abscissa_core/testing/index.html>
#![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");
}