diff --git a/.github/workflows/leo-login-logout.yml b/.github/workflows/leo-login-logout.yml
index 46883c4a87..95a17e42ed 100644
--- a/.github/workflows/leo-login-logout.yml
+++ b/.github/workflows/leo-login-logout.yml
@@ -43,6 +43,5 @@ jobs:
leo login -u "$USER" -p "$PASS"
leo add argus4130/xnor
leo remove xnor
- leo clean
leo logout
diff --git a/Cargo.lock b/Cargo.lock
index 5dd9b34569..29c100ee7f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1010,6 +1010,15 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+[[package]]
+name = "heck"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
+dependencies = [
+ "unicode-segmentation",
+]
+
[[package]]
name = "hermit-abi"
version = "0.1.17"
@@ -1388,6 +1397,7 @@ dependencies = [
name = "leo-lang"
version = "1.2.2"
dependencies = [
+ "anyhow",
"clap",
"colored",
"console",
@@ -1416,6 +1426,7 @@ dependencies = [
"snarkvm-gadgets",
"snarkvm-models",
"snarkvm-utilities",
+ "structopt",
"thiserror",
"toml",
"tracing",
@@ -2052,6 +2063,7 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.24",
"quote 1.0.7",
+ "syn 1.0.60",
"version_check",
]
@@ -2809,6 +2821,30 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+[[package]]
+name = "structopt"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2 1.0.24",
+ "quote 1.0.7",
+ "syn 1.0.60",
+]
+
[[package]]
name = "subtle"
version = "1.0.0"
@@ -3152,6 +3188,12 @@ dependencies = [
"tinyvec",
]
+[[package]]
+name = "unicode-segmentation"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
+
[[package]]
name = "unicode-width"
version = "0.1.8"
diff --git a/Cargo.toml b/Cargo.toml
index 4169d8a2d3..d5882916dc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -89,6 +89,12 @@ default-features = false
[dependencies.snarkvm-utilities]
version = "0.0.3"
+[dependencies.anyhow]
+version = "1.0"
+
+[dependencies.structopt]
+version = "0.3"
+
[dependencies.clap]
version = "2.33.3"
diff --git a/leo/api.rs b/leo/api.rs
new file mode 100644
index 0000000000..23e12b3578
--- /dev/null
+++ b/leo/api.rs
@@ -0,0 +1,202 @@
+// Copyright (C) 2019-2021 Aleo Systems Inc.
+// This file is part of the Leo library.
+
+// The Leo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// The Leo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with the Leo library. If not, see .
+
+use anyhow::{anyhow, Error, Result};
+use reqwest::{
+ blocking::{Client, Response},
+ Method,
+ StatusCode,
+};
+use serde::Serialize;
+
+/// Trait describes API Routes and Request bodies, struct which implements
+/// Route MUST also support Serialize to be usable in Api::run_route(r: Route)
+pub trait Route {
+ /// Whether to use bearer auth or not. Some routes may have additional
+ /// features for logged-in users, so authorization token should be sent
+ /// if it is created of course
+ const AUTH: bool;
+
+ /// HTTP method to use when requesting
+ const METHOD: Method;
+
+ /// URL path without first forward slash (e.g. v1/package/fetch)
+ const PATH: &'static str;
+
+ /// Output type for this route. For login it is simple - String
+ /// But for other routes may be more complex.
+ type Output;
+
+ /// Process reqwest Response and turn it into Output
+ fn process(&self, res: Response) -> Result;
+
+ /// Transform specific status codes into correct errors for this route.
+ /// For example 404 on package fetch should mean that 'Package is not found'
+ fn status_to_err(&self, _status: StatusCode) -> Error {
+ anyhow!("Unidentified API error")
+ }
+}
+
+/// REST API handler with reqwest::blocking inside
+#[derive(Clone, Debug)]
+pub struct Api {
+ host: String,
+ client: Client,
+ /// Authorization token for API requests
+ auth_token: Option,
+}
+
+impl Api {
+ /// Create new instance of API, set host and Client is going to be
+ /// created and set automatically
+ pub fn new(host: String, auth_token: Option) -> Api {
+ Api {
+ client: Client::new(),
+ auth_token,
+ host,
+ }
+ }
+
+ /// Get token for bearer auth, should be passed into Api through Context
+ pub fn auth_token(&self) -> Option {
+ self.auth_token.clone()
+ }
+
+ /// Set authorization token for future requests
+ pub fn set_auth_token(&mut self, token: String) {
+ self.auth_token = Some(token);
+ }
+
+ /// Run specific route struct. Turn struct into request body
+ /// and use type constants and Route implementation to get request params
+ pub fn run_route(&self, route: T) -> Result
+ where
+ T: Route,
+ T: Serialize,
+ {
+ let mut res = self.client.request(T::METHOD, &format!("{}{}", self.host, T::PATH));
+
+ // add body for POST and PUT requests
+ if T::METHOD == Method::POST || T::METHOD == Method::PUT {
+ res = res.json(&route);
+ };
+
+ // if Route::Auth is true and token is present - pass it
+ if T::AUTH && self.auth_token().is_some() {
+ res = res.bearer_auth(&self.auth_token().unwrap());
+ };
+
+ // only one error is possible here
+ let res = res.send().map_err(|_| anyhow!("Unable to connect to Aleo PM"))?;
+
+ // where magic begins
+ route.process(res)
+ }
+}
+
+// --------------------------------------------------
+// | Defining routes |
+// --------------------------------------------------
+
+/// Handler for 'fetch' route - fetch packages from Aleo PM
+/// Route: POST /v1/package/fetch
+#[derive(Serialize, Debug)]
+pub struct Fetch {
+ pub author: String,
+ pub package_name: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub version: Option,
+}
+
+impl Route for Fetch {
+ type Output = Response;
+
+ const AUTH: bool = true;
+ const METHOD: Method = Method::POST;
+ const PATH: &'static str = "api/package/fetch";
+
+ fn process(&self, res: Response) -> Result {
+ // check status code first
+ if res.status() != 200 {
+ return Err(self.status_to_err(res.status()));
+ };
+
+ Ok(res)
+ }
+
+ fn status_to_err(&self, status: StatusCode) -> Error {
+ match status {
+ StatusCode::BAD_REQUEST => anyhow!("Package is not found - check author and/or package name"),
+ // TODO: we should return 404 on not found author/package
+ // and return BAD_REQUEST if data format is incorrect or some of the arguments
+ // were not passed
+ StatusCode::NOT_FOUND => anyhow!("Package is hidden"),
+ _ => anyhow!("Unknown API error: {}", status),
+ }
+ }
+}
+
+/// Handler for 'login' route - send username and password and receive JWT
+/// Route: POST /v1/account/authenticate
+#[derive(Serialize)]
+pub struct Login {
+ pub email_username: String,
+ pub password: String,
+}
+
+impl Route for Login {
+ type Output = Response;
+
+ const AUTH: bool = false;
+ const METHOD: Method = Method::POST;
+ const PATH: &'static str = "api/account/authenticate";
+
+ fn process(&self, res: Response) -> Result {
+ if res.status() != 200 {
+ return Err(self.status_to_err(res.status()));
+ }
+
+ Ok(res)
+ }
+
+ fn status_to_err(&self, status: StatusCode) -> Error {
+ match status {
+ StatusCode::BAD_REQUEST => anyhow!("This username is not yet registered or the password is incorrect"),
+ // TODO: NOT_FOUND here should be replaced, this error code has no relation to what this route is doing
+ StatusCode::NOT_FOUND => anyhow!("Incorrect password"),
+ _ => anyhow!("Unknown API error: {}", status),
+ }
+ }
+}
+
+/// Handler for 'my_profile' route. Meant to be used to get profile details but
+/// in current application is used to check if user is logged in. Any non-200 response
+/// is treated as Unauthorized
+#[derive(Serialize)]
+pub struct Profile {}
+
+impl Route for Profile {
+ type Output = bool;
+
+ const AUTH: bool = true;
+ const METHOD: Method = Method::GET;
+ const PATH: &'static str = "api/account/my_profile";
+
+ fn process(&self, res: Response) -> Result {
+ // this may be extended for more precise error handling
+ Ok(res.status() == 200)
+ }
+}
diff --git a/leo/cli.rs b/leo/cli.rs
deleted file mode 100644
index fb224e06ec..0000000000
--- a/leo/cli.rs
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use crate::{cli_types::*, errors::CLIError, logger, updater::Updater};
-
-use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
-
-pub trait CLI {
- type Options;
- type Output;
-
- const ABOUT: AboutType;
- const ARGUMENTS: &'static [ArgumentType];
- const FLAGS: &'static [FlagType];
- const NAME: NameType;
- const OPTIONS: &'static [OptionType];
- const SUBCOMMANDS: &'static [SubCommandType];
-
- #[allow(clippy::new_ret_no_self)]
- #[cfg_attr(tarpaulin, skip)]
- fn new<'a, 'b>() -> App<'a, 'b> {
- let arguments = &Self::ARGUMENTS
- .iter()
- .map(|a| {
- let mut args = Arg::with_name(a.0).help(a.1).required(a.3).index(a.4);
- if !a.2.is_empty() {
- args = args.possible_values(a.2);
- }
- args
- })
- .collect::>>();
- let flags = &Self::FLAGS
- .iter()
- .map(|a| Arg::from_usage(a))
- .collect::>>();
- let options = &Self::OPTIONS
- .iter()
- .map(|a| match !a.2.is_empty() {
- true => Arg::from_usage(a.0)
- .conflicts_with_all(a.1)
- .possible_values(a.2)
- .requires_all(a.3),
- false => Arg::from_usage(a.0).conflicts_with_all(a.1).requires_all(a.3),
- })
- .collect::>>();
- let subcommands = Self::SUBCOMMANDS.iter().map(|s| {
- SubCommand::with_name(s.0)
- .about(s.1)
- .args(
- &s.2.iter()
- .map(|a| {
- let mut args = Arg::with_name(a.0).help(a.1).required(a.3).index(a.4);
- if !a.2.is_empty() {
- args = args.possible_values(a.2);
- }
- args
- })
- .collect::>>(),
- )
- .args(
- &s.3.iter()
- .map(|a| Arg::from_usage(a))
- .collect::>>(),
- )
- .args(
- &s.4.iter()
- .map(|a| match !a.2.is_empty() {
- true => Arg::from_usage(a.0)
- .conflicts_with_all(a.1)
- .possible_values(a.2)
- .requires_all(a.3),
- false => Arg::from_usage(a.0).conflicts_with_all(a.1).requires_all(a.3),
- })
- .collect::>>(),
- )
- .settings(s.5)
- });
-
- SubCommand::with_name(Self::NAME)
- .about(Self::ABOUT)
- .settings(&[
- AppSettings::ColoredHelp,
- AppSettings::DisableHelpSubcommand,
- AppSettings::DisableVersion,
- ])
- .args(arguments)
- .args(flags)
- .args(options)
- .subcommands(subcommands)
- }
-
- #[cfg_attr(tarpaulin, skip)]
- fn process(arguments: &ArgMatches) -> Result<(), CLIError> {
- // Set logging environment
- match arguments.is_present("debug") {
- true => logger::init_logger("leo", 2),
- false => logger::init_logger("leo", 1),
- }
-
- if arguments.subcommand().0 != "update" {
- Updater::print_cli();
- }
-
- let options = Self::parse(arguments)?;
- let _output = Self::output(options)?;
- Ok(())
- }
-
- #[cfg_attr(tarpaulin, skip)]
- fn parse(arguments: &ArgMatches) -> Result;
-
- #[cfg_attr(tarpaulin, skip)]
- fn output(options: Self::Options) -> Result;
-}
diff --git a/leo/cli_types.rs b/leo/cli_types.rs
deleted file mode 100644
index 5fac70df71..0000000000
--- a/leo/cli_types.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use clap::AppSettings;
-
-pub type NameType = &'static str;
-
-pub type AboutType = &'static str;
-
-pub type DescriptionType = &'static str;
-
-pub type RequiredType = bool;
-
-pub type PossibleValuesType = &'static [&'static str];
-
-pub type IndexType = u64;
-
-pub type ArgumentType = (NameType, DescriptionType, PossibleValuesType, RequiredType, IndexType);
-
-// Format
-// "[flag] -f --flag 'Add flag description here'"
-pub type FlagType = &'static str;
-
-// Format
-// (argument, conflicts, possible_values, requires)
-pub type OptionType = (
- &'static str,
- &'static [&'static str],
- &'static [&'static str],
- &'static [&'static str],
-);
-
-pub type SubCommandType = (
- NameType,
- AboutType,
- &'static [ArgumentType],
- &'static [FlagType],
- &'static [OptionType],
- &'static [AppSettings],
-);
diff --git a/leo/commands/add.rs b/leo/commands/add.rs
deleted file mode 100644
index d531057895..0000000000
--- a/leo/commands/add.rs
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-//
-// Usage:
-//
-// leo add -a author -p package_name -v version
-// leo add -a author -p package_name
-//
-
-use crate::{
- cli::CLI,
- cli_types::*,
- config::*,
- errors::{AddError::*, CLIError},
-};
-
-use leo_package::{
- imports::{ImportsDirectory, IMPORTS_DIRECTORY_NAME},
- root::Manifest,
-};
-
-use std::{
- collections::HashMap,
- convert::TryFrom,
- env::current_dir,
- fs::{create_dir_all, File},
- io::{Read, Write},
-};
-
-pub const ADD_URL: &str = "v1/package/fetch";
-
-#[derive(Debug)]
-pub struct AddCommand;
-
-impl CLI for AddCommand {
- // Format: author, package_name, version
- type Options = (Option, Option, Option);
- type Output = ();
-
- const ABOUT: AboutType = "Install a package from the Aleo Package Manager";
- const ARGUMENTS: &'static [ArgumentType] = &[
- // (name, description, possible_values, required, index)
- (
- "REMOTE",
- "Install a package from the Aleo Package Manager with the given remote",
- &[],
- false,
- 1u64,
- ),
- ];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "add";
- const OPTIONS: &'static [OptionType] = &[
- // (argument, conflicts, possible_values, requires)
- ("[author] -a --author= 'Specify a package author'", &[], &[], &[
- "package",
- ]),
- (
- "[package] -p --package= 'Specify a package name'",
- &[],
- &[],
- &["author"],
- ),
- (
- "[version] -v --version=[version] 'Specify a package version'",
- &[],
- &[],
- &["author", "package"],
- ),
- ];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
-
- fn parse(arguments: &clap::ArgMatches) -> Result {
- // TODO update to new package manager API without an author field
- if arguments.is_present("author") && arguments.is_present("package") {
- return Ok((
- arguments.value_of("author").map(|s| s.to_string()),
- arguments.value_of("package").map(|s| s.to_string()),
- arguments.value_of("version").map(|s| s.to_string()),
- ));
- }
-
- match arguments.value_of("REMOTE") {
- Some(remote) => {
- let values: Vec<&str> = remote.split('/').collect();
-
- if values.len() != 2 {
- return Err(InvalidRemote.into());
- }
-
- let author = values[0].to_string();
- let package = values[1].to_string();
-
- Ok((Some(author), Some(package), None))
- }
- None => Ok((None, None, None)),
- }
- }
-
- fn output(options: Self::Options) -> Result {
- // Begin "Adding" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Adding");
- let _enter = span.enter();
- let path = current_dir()?;
-
- // Enforce that the current directory is a leo package
- Manifest::try_from(path.as_path())?;
-
- let (response, package_name) = match options {
- (Some(author), Some(package_name), version) => {
- let client = reqwest::blocking::Client::new();
- let url = format!("{}{}", PACKAGE_MANAGER_URL, ADD_URL);
-
- let mut json = HashMap::new();
- json.insert("author", author);
- json.insert("package_name", package_name.clone());
-
- if let Some(version) = version {
- json.insert("version", version);
- }
-
- let result = match read_token() {
- Ok(token) => {
- tracing::info!("Logged in, using token to authorize");
- client.post(&url).bearer_auth(token)
- }
- Err(_) => client.post(&url),
- }
- .json(&json)
- .send();
-
- match result {
- Ok(response) => (response, package_name),
- //Cannot connect to the server
- Err(_error) => {
- return Err(
- ConnectionUnavailable("Could not connect to the Aleo Package Manager".into()).into(),
- );
- }
- }
- }
- _ => return Err(MissingAuthorOrPackageName.into()),
- };
-
- let mut path = current_dir()?;
- ImportsDirectory::create(&path)?;
- path.push(IMPORTS_DIRECTORY_NAME);
- path.push(package_name);
- create_dir_all(&path)?;
-
- let bytes = response.bytes()?;
- let reader = std::io::Cursor::new(bytes);
-
- let mut zip_arhive = match zip::ZipArchive::new(reader) {
- Ok(zip) => zip,
- Err(error) => return Err(ZipError(error.to_string().into()).into()),
- };
-
- for i in 0..zip_arhive.len() {
- let file = match zip_arhive.by_index(i) {
- Ok(file) => file,
- Err(error) => return Err(ZipError(error.to_string().into()).into()),
- };
-
- let file_name = file.name();
-
- let mut file_path = path.clone();
- file_path.push(file_name);
-
- if file_name.ends_with('/') {
- create_dir_all(file_path)?;
- } else {
- if let Some(parent_directory) = path.parent() {
- create_dir_all(parent_directory)?;
- }
-
- File::create(file_path)?.write_all(&file.bytes().map(|e| e.unwrap()).collect::>())?;
- }
- }
-
- tracing::info!("Successfully added a package\n");
- Ok(())
- }
-}
diff --git a/leo/commands/build.rs b/leo/commands/build.rs
index 35a11ecf43..ce108a854e 100644
--- a/leo/commands/build.rs
+++ b/leo/commands/build.rs
@@ -15,55 +15,50 @@
// along with the Leo library. If not, see .
use crate::{
- cli::*,
- cli_types::*,
- errors::CLIError,
+ commands::Command,
+ context::Context,
synthesizer::{CircuitSynthesizer, SerializedCircuit},
};
use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType};
use leo_package::{
inputs::*,
outputs::{ChecksumFile, CircuitFile, OutputsDirectory, OUTPUTS_DIRECTORY_NAME},
- root::Manifest,
source::{LibraryFile, MainFile, LIBRARY_FILENAME, MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
};
+use anyhow::Result;
use snarkvm_curves::{bls12_377::Bls12_377, edwards_bls12::Fq};
use snarkvm_models::gadgets::r1cs::ConstraintSystem;
+use std::convert::TryFrom;
+use structopt::StructOpt;
+use tracing::span::Span;
+
+/// Compile and build program command
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Build {}
+
+impl Build {
+ pub fn new() -> Build {
+ Build {}
+ }
+}
-use clap::ArgMatches;
-use std::{convert::TryFrom, env::current_dir, time::Instant};
-
-#[derive(Debug)]
-pub struct BuildCommand;
-
-impl CLI for BuildCommand {
- type Options = ();
+impl Command for Build {
+ type Input = ();
type Output = Option<(Compiler, bool)>;
- const ABOUT: AboutType = "Compile the current package as a program";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "build";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Build")
+ }
- #[cfg_attr(tarpaulin, skip)]
- fn parse(_arguments: &ArgMatches) -> Result {
+ fn prelude(&self) -> Result {
Ok(())
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(_options: Self::Options) -> Result {
- // Begin "Compiling" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Compiling");
- let enter = span.enter();
-
- let path = current_dir()?;
-
- // Get the package name
- let manifest = Manifest::try_from(path.as_path())?;
- let package_name = manifest.get_package_name();
+ fn apply(self, ctx: Context, _: Self::Input) -> Result {
+ let path = ctx.dir()?;
+ let package_name = ctx.manifest()?.get_package_name();
// Sanitize the package path to the root directory
let mut package_path = path.clone();
@@ -77,9 +72,6 @@ impl CLI for BuildCommand {
tracing::info!("Starting...");
- // Start the timer
- let start = Instant::now();
-
// Compile the package starting with the lib.leo file
if LibraryFile::exists_at(&package_path) {
// Construct the path to the library file in the source directory
@@ -185,21 +177,9 @@ impl CLI for BuildCommand {
tracing::info!("Complete");
- // Drop "Compiling" context for console logging
- drop(enter);
-
- // Begin "Done" context for console logging todo: @collin figure a way to get this output with tracing without dropping span
- tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
- tracing::info!("Finished in {} milliseconds\n", start.elapsed().as_millis());
- });
-
return Ok(Some((program, checksum_differs)));
}
- drop(enter);
-
- // Return None when compiling a package for publishing
- // The published package does not need to have a main.leo
Ok(None)
}
}
diff --git a/leo/commands/clean.rs b/leo/commands/clean.rs
index c7d950e3bb..91240f1be7 100644
--- a/leo/commands/clean.rs
+++ b/leo/commands/clean.rs
@@ -14,45 +14,40 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{cli::*, cli_types::*, errors::CLIError};
-use leo_package::{
- outputs::{ChecksumFile, ProofFile, ProvingKeyFile, VerificationKeyFile},
- root::Manifest,
-};
-
-use clap::ArgMatches;
+use crate::{commands::Command, context::Context};
use leo_compiler::OutputFile;
-use leo_package::outputs::CircuitFile;
-use std::{convert::TryFrom, env::current_dir};
+use leo_package::outputs::{ChecksumFile, CircuitFile, ProofFile, ProvingKeyFile, VerificationKeyFile};
+
+use anyhow::Result;
+use structopt::StructOpt;
+use tracing::span::Span;
+
+/// Clean outputs folder command
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Clean {}
-#[derive(Debug)]
-pub struct CleanCommand;
+impl Clean {
+ pub fn new() -> Clean {
+ Clean {}
+ }
+}
-impl CLI for CleanCommand {
- type Options = ();
+impl Command for Clean {
+ type Input = ();
type Output = ();
- const ABOUT: AboutType = "Clean the output directory";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "clean";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Cleaning")
+ }
- #[cfg_attr(tarpaulin, skip)]
- fn parse(_arguments: &ArgMatches) -> Result {
+ fn prelude(&self) -> Result {
Ok(())
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(_options: Self::Options) -> Result {
- // Begin "Clean" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Cleaning");
- let enter = span.enter();
-
- // Get the package name
- let path = current_dir()?;
- let package_name = Manifest::try_from(path.as_path())?.get_package_name();
+ fn apply(self, ctx: Context, _: Self::Input) -> Result {
+ let path = ctx.dir()?;
+ let package_name = ctx.manifest()?.get_package_name();
// Remove the checksum from the output directory
ChecksumFile::new(&package_name).remove(&path)?;
@@ -72,14 +67,6 @@ impl CLI for CleanCommand {
// Remove the proof from the output directory
ProofFile::new(&package_name).remove(&path)?;
- // Drop "Compiling" context for console logging
- drop(enter);
-
- // Begin "Done" context for console logging
- tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
- tracing::info!("Program workspace cleaned\n");
- });
-
Ok(())
}
}
diff --git a/leo/commands/deploy.rs b/leo/commands/deploy.rs
index 7cbd1d5b5a..f27da1dae5 100644
--- a/leo/commands/deploy.rs
+++ b/leo/commands/deploy.rs
@@ -14,65 +14,36 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{
- cli::*,
- cli_types::*,
- commands::BuildCommand,
- errors::{CLIError, RunError},
-};
-use leo_package::{
- root::Manifest,
- source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
-};
+use crate::{commands::Command, context::Context};
-use clap::ArgMatches;
-use std::{convert::TryFrom, env::current_dir};
+use anyhow::Result;
+use structopt::StructOpt;
+use tracing::span::Span;
-#[derive(Debug)]
-pub struct DeployCommand;
+/// Deploy Leo program to the network
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Deploy {}
-impl CLI for DeployCommand {
- type Options = ();
+impl Deploy {
+ pub fn new() -> Deploy {
+ Deploy {}
+ }
+}
+
+impl Command for Deploy {
+ type Input = ();
type Output = ();
- const ABOUT: AboutType = "Deploy the current package as a program to the network (*)";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "deploy";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Deploy")
+ }
- #[cfg_attr(tarpaulin, skip)]
- fn parse(_arguments: &ArgMatches) -> Result {
+ fn prelude(&self) -> Result {
Ok(())
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(options: Self::Options) -> Result {
- // Begin "Deploy" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Deploying");
- let _enter = span.enter();
-
- let path = current_dir()?;
-
- match BuildCommand::output(options)? {
- Some((_program, _checksum_differs)) => {
- // Get the package name
- let _package_name = Manifest::try_from(path.as_path())?.get_package_name();
-
- tracing::error!("Unimplemented - `leo deploy`");
-
- Ok(())
- }
- None => {
- let mut main_file_path = path;
- main_file_path.push(SOURCE_DIRECTORY_NAME);
- main_file_path.push(MAIN_FILENAME);
-
- Err(CLIError::RunError(RunError::MainFileDoesNotExist(
- main_file_path.into_os_string(),
- )))
- }
- }
+ fn apply(self, _: Context, _: Self::Input) -> Result {
+ unimplemented!("Deploy command has not been implemented yet");
}
}
diff --git a/leo/commands/init.rs b/leo/commands/init.rs
index bf0d4a956e..7ebe2a3214 100644
--- a/leo/commands/init.rs
+++ b/leo/commands/init.rs
@@ -14,58 +14,50 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{
- cli::*,
- cli_types::*,
- errors::{CLIError, InitError},
-};
+use crate::{commands::Command, context::Context};
use leo_package::LeoPackage;
-use clap::ArgMatches;
+use anyhow::{anyhow, Result};
use std::env::current_dir;
+use structopt::StructOpt;
+use tracing::span::Span;
-#[derive(Debug)]
-pub struct InitCommand;
+/// Init Leo project command within current directory
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Init {}
-impl CLI for InitCommand {
- type Options = bool;
- type Output = ();
+impl Init {
+ pub fn new() -> Init {
+ Init {}
+ }
+}
- const ABOUT: AboutType = "Create a new Leo package in an existing directory";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[("--lib"), ("--bin")];
- const NAME: NameType = "init";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
+impl Command for Init {
+ type Input = ();
+ type Output = ();
- #[cfg_attr(tarpaulin, skip)]
- fn parse(arguments: &ArgMatches) -> Result {
- Ok(arguments.is_present("lib"))
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Initializing")
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(options: Self::Options) -> Result {
- // Begin "Initializing" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Initializing");
- let _enter = span.enter();
+ fn prelude(&self) -> Result {
+ Ok(())
+ }
+ fn apply(self, _: Context, _: Self::Input) -> Result {
let path = current_dir()?;
-
- // Derive the package name
let package_name = path
.file_stem()
- .ok_or_else(|| InitError::ProjectNameInvalid(path.as_os_str().to_owned()))?
+ .ok_or_else(|| anyhow!("Project name invalid"))?
.to_string_lossy()
.to_string();
- // Verify the directory does not exist
if !path.exists() {
- return Err(InitError::DirectoryDoesNotExist(path.as_os_str().to_owned()).into());
+ return Err(anyhow!("Directory does not exist"));
}
- LeoPackage::initialize(&package_name, options, &path)?;
-
- tracing::info!("Successfully initialized package \"{}\"\n", package_name);
+ LeoPackage::initialize(&package_name, false, &path)?;
Ok(())
}
diff --git a/leo/commands/lint.rs b/leo/commands/lint.rs
index 8db6b7577b..0aeda51844 100644
--- a/leo/commands/lint.rs
+++ b/leo/commands/lint.rs
@@ -14,65 +14,36 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{
- cli::*,
- cli_types::*,
- commands::BuildCommand,
- errors::{CLIError, RunError},
-};
-use leo_package::{
- root::Manifest,
- source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
-};
+use crate::{commands::Command, context::Context};
-use clap::ArgMatches;
-use std::{convert::TryFrom, env::current_dir};
+use anyhow::Result;
+use structopt::StructOpt;
+use tracing::span::Span;
-#[derive(Debug)]
-pub struct LintCommand;
+/// Lint Leo code command
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Lint {}
-impl CLI for LintCommand {
- type Options = ();
+impl Lint {
+ pub fn new() -> Lint {
+ Lint {}
+ }
+}
+
+impl Command for Lint {
+ type Input = ();
type Output = ();
- const ABOUT: AboutType = "Lints the Leo files in the package (*)";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "lint";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Linting")
+ }
- #[cfg_attr(tarpaulin, skip)]
- fn parse(_arguments: &ArgMatches) -> Result {
+ fn prelude(&self) -> Result {
Ok(())
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(options: Self::Options) -> Result {
- // Begin "Linting" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Linting");
- let _enter = span.enter();
-
- let path = current_dir()?;
-
- match BuildCommand::output(options)? {
- Some((_program, _checksum_differs)) => {
- // Get the package name
- let _package_name = Manifest::try_from(path.as_path())?.get_package_name();
-
- tracing::error!("Unimplemented - `leo lint`");
-
- Ok(())
- }
- None => {
- let mut main_file_path = path;
- main_file_path.push(SOURCE_DIRECTORY_NAME);
- main_file_path.push(MAIN_FILENAME);
-
- Err(CLIError::RunError(RunError::MainFileDoesNotExist(
- main_file_path.into_os_string(),
- )))
- }
- }
+ fn apply(self, _: Context, _: Self::Input) -> Result {
+ unimplemented!("Lint command has not been implemented yet");
}
}
diff --git a/leo/commands/login.rs b/leo/commands/login.rs
deleted file mode 100644
index 1e29c3b1ff..0000000000
--- a/leo/commands/login.rs
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-//
-// Usage:
-//
-// leo login
-// leo login -u username -p password
-//
-
-use crate::{
- cli::CLI,
- cli_types::*,
- config::*,
- errors::{CLIError, LoginError::*},
-};
-
-use std::collections::HashMap;
-
-pub const LOGIN_URL: &str = "v1/account/authenticate";
-pub const PROFILE_URL: &str = "v1/account/my_profile";
-
-#[derive(Debug)]
-pub struct LoginCommand;
-
-impl CLI for LoginCommand {
- // Format: token, username, password
- type Options = (Option, Option, Option);
- type Output = String;
-
- const ABOUT: AboutType = "Login to the Aleo Package Manager";
- const ARGUMENTS: &'static [ArgumentType] = &[
- // (name, description, possible_values, required, index)
- (
- "NAME",
- "Sets the authentication token for login to the package manager",
- &[],
- false,
- 1u64,
- ),
- ];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "login";
- const OPTIONS: &'static [OptionType] = &[
- // (argument, conflicts, possible_values, requires)
- ("[username] -u --user=[username] 'Sets a username'", &[], &[], &[]),
- ("[password] -p --password=[password] 'Sets a password'", &[], &[], &[]),
- ];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
-
- fn parse(arguments: &clap::ArgMatches) -> Result {
- if arguments.is_present("username") && arguments.is_present("password") {
- return Ok((
- None,
- Some(arguments.value_of("username").unwrap().to_string()),
- Some(arguments.value_of("password").unwrap().to_string()),
- ));
- }
-
- match arguments.value_of("NAME") {
- Some(name) => Ok((Some(name.to_string()), None, None)),
- None => Ok((None, None, None)),
- }
- }
-
- fn output(options: Self::Options) -> Result {
- // Begin "Login" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Login");
- let _enter = span.enter();
-
- let token = match options {
- // Login using existing token
- (Some(token), _, _) => Some(token),
-
- // Login using username and password
- (None, Some(username), Some(password)) => {
- // prepare JSON data to be sent
- let mut json = HashMap::new();
- json.insert("email_username", username);
- json.insert("password", password);
-
- let client = reqwest::blocking::Client::new();
- let url = format!("{}{}", PACKAGE_MANAGER_URL, LOGIN_URL);
- let response: HashMap = match client.post(&url).json(&json).send() {
- Ok(result) => match result.json() {
- Ok(json) => json,
- Err(_error) => {
- return Err(WrongLoginOrPassword.into());
- }
- },
- //Cannot connect to the server
- Err(_error) => {
- return Err(NoConnectionFound.into());
- }
- };
-
- match response.get("token") {
- Some(token) => Some(token.clone()),
- None => {
- return Err(CannotGetToken.into());
- }
- }
- }
-
- // Login using stored JWT credentials.
- // TODO (raychu86) Package manager re-authentication from token
- (_, _, _) => {
- let token = read_token().map_err(|_| -> CLIError { NoCredentialsProvided.into() })?;
-
- Some(token)
- }
- };
-
- match token {
- Some(token) => {
- write_token(token.as_str())?;
-
- tracing::info!("success");
-
- Ok(token)
- }
- _ => {
- tracing::error!("Failed to login. Please run `leo login -h` for help.");
-
- Err(NoCredentialsProvided.into())
- }
- }
- }
-}
diff --git a/leo/commands/mod.rs b/leo/commands/mod.rs
index 96f2279e4a..8a6ca36171 100644
--- a/leo/commands/mod.rs
+++ b/leo/commands/mod.rs
@@ -14,53 +14,125 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-pub mod add;
-pub use self::add::*;
+use crate::context::{get_context, Context};
+use anyhow::Result;
+use std::time::Instant;
+use tracing::span::Span;
+
+// local program commands
pub mod build;
-pub use self::build::*;
+pub use build::Build;
pub mod clean;
-pub use self::clean::*;
+pub use clean::Clean;
pub mod deploy;
-pub use self::deploy::*;
+pub use deploy::Deploy;
pub mod init;
-pub use self::init::*;
+pub use init::Init;
pub mod lint;
-pub use self::lint::*;
-
-pub mod login;
-pub use self::login::*;
-
-pub mod logout;
-pub use self::logout::*;
+pub use lint::Lint;
pub mod new;
-pub use self::new::*;
+pub use new::New;
pub mod prove;
-pub use self::prove::*;
-
-pub mod publish;
-pub use self::publish::*;
+pub use prove::Prove;
pub mod run;
-pub use self::run::*;
+pub use run::Run;
pub mod setup;
-pub use self::setup::*;
+pub use setup::Setup;
pub mod test;
-pub use self::test::*;
-
-pub mod remove;
-pub use self::remove::*;
+pub use test::Test;
pub mod update;
-pub use self::update::*;
+pub use update::{Sub as UpdateAutomatic, Update};
pub mod watch;
-pub use self::watch::*;
+pub use watch::Watch;
+
+// Aleo PM related commands
+pub mod package;
+
+/// Base trait for Leo CLI, see methods and their documentation for details
+pub trait Command {
+ /// If current command requires running another command before
+ /// and needs its output results, this is the place to set.
+ /// Example: type Input: ::Out
+ type Input;
+
+ /// Define output of the command to be reused as an Input for another
+ /// command. If this command is not used as a prelude for another, keep empty
+ type Output;
+
+ /// Returns project context, currently keeping it simple but it is possible
+ /// that in the future leo will not depend on current directory, and we're keeping
+ /// option for extending current core
+ fn context(&self) -> Result {
+ get_context()
+ }
+
+ /// Add span to the logger tracing::span.
+ /// Due to specifics of macro implementation it is impossible to set
+ /// span name with non-literal i.e. dynamic variable even if this
+ /// variable is &'static str
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Leo")
+ }
+
+ /// Run prelude and get Input for current command. As simple as that.
+ /// But due to inability to pass default implementation of a type, this
+ /// method must be present in every trait implementation.
+ fn prelude(&self) -> Result
+ where
+ Self: std::marker::Sized;
+
+ /// Core of the execution - do what is necessary. This function is run within
+ /// context of 'execute' function, which sets logging and timers
+ fn apply(self, ctx: Context, input: Self::Input) -> Result
+ where
+ Self: std::marker::Sized;
+
+ /// Wrapper around apply function, sets up tracing, time tracking and context
+ fn execute(self) -> Result
+ where
+ Self: std::marker::Sized,
+ {
+ let input = self.prelude()?;
+
+ // create span for this command
+ let span = self.log_span();
+ let span = span.enter();
+
+ // calculate execution time for each run
+ let timer = Instant::now();
+
+ let context = self.context()?;
+ let out = self.apply(context, input);
+
+ drop(span);
+
+ // use done context to print time
+ tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
+ tracing::info!("Finished in {} milliseconds \n", timer.elapsed().as_millis());
+ });
+
+ out
+ }
+
+ /// Execute command but empty the result. Comes in handy where there's a
+ /// need to make match arms compatible while keeping implementation-specific
+ /// output possible. Errors however are all of the type Error
+ fn try_execute(self) -> Result<()>
+ where
+ Self: std::marker::Sized,
+ {
+ self.execute().map(|_| Ok(()))?
+ }
+}
diff --git a/leo/commands/new.rs b/leo/commands/new.rs
index bc42eb7477..2509e7563e 100644
--- a/leo/commands/new.rs
+++ b/leo/commands/new.rs
@@ -14,81 +14,56 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{
- cli::*,
- cli_types::*,
- errors::{CLIError, NewError},
-};
+use crate::{commands::Command, context::Context};
use leo_package::LeoPackage;
-use clap::ArgMatches;
+use anyhow::{anyhow, Result};
use std::{env::current_dir, fs};
+use structopt::StructOpt;
+use tracing::span::Span;
+
+/// Create new Leo project
+#[derive(StructOpt, Debug)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct New {
+ #[structopt(name = "NAME", help = "Set package name")]
+ name: String,
+}
-#[derive(Debug)]
-pub struct NewCommand;
+impl New {
+ pub fn new(name: String) -> New {
+ New { name }
+ }
+}
-impl CLI for NewCommand {
- type Options = (Option, bool);
+impl Command for New {
+ type Input = ();
type Output = ();
- const ABOUT: AboutType = "Create a new Leo package in a new directory";
- const ARGUMENTS: &'static [ArgumentType] = &[
- // (name, description, possible_values, required, index)
- (
- "NAME",
- "Sets the resulting package name, defaults to the directory name",
- &[],
- true,
- 1u64,
- ),
- ];
- const FLAGS: &'static [FlagType] = &[("--lib"), ("--bin")];
- const NAME: NameType = "new";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
-
- #[cfg_attr(tarpaulin, skip)]
- fn parse(arguments: &ArgMatches) -> Result {
- let is_lib = arguments.is_present("lib");
- match arguments.value_of("NAME") {
- Some(name) => Ok((Some(name.to_string()), is_lib)),
- None => Ok((None, is_lib)),
- }
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "New")
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(options: Self::Options) -> Result {
- // Begin "Initializing" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Initializing");
- let _enter = span.enter();
+ fn prelude(&self) -> Result {
+ Ok(())
+ }
+ fn apply(self, _: Context, _: Self::Input) -> Result {
let mut path = current_dir()?;
-
- // Derive the package name
- let package_name = match options.0 {
- Some(name) => name,
- None => path
- .file_stem()
- .ok_or_else(|| NewError::ProjectNameInvalid(path.as_os_str().to_owned()))?
- .to_string_lossy()
- .to_string(),
- };
+ let package_name = self.name;
// Derive the package directory path
path.push(&package_name);
// Verify the package directory path does not exist yet
if path.exists() {
- return Err(NewError::DirectoryAlreadyExists(path.as_os_str().to_owned()).into());
+ return Err(anyhow!("Directory already exists {:?}", path));
}
// Create the package directory
- fs::create_dir_all(&path)
- .map_err(|error| NewError::CreatingRootDirectory(path.as_os_str().to_owned(), error))?;
-
- LeoPackage::initialize(&package_name, options.1, &path)?;
+ fs::create_dir_all(&path).map_err(|err| anyhow!("Could not create directory {}", err))?;
- tracing::info!("Successfully initialized package \"{}\"\n", package_name);
+ LeoPackage::initialize(&package_name, false, &path)?;
Ok(())
}
diff --git a/leo/commands/package/add.rs b/leo/commands/package/add.rs
new file mode 100644
index 0000000000..6d2da368b4
--- /dev/null
+++ b/leo/commands/package/add.rs
@@ -0,0 +1,156 @@
+// Copyright (C) 2019-2021 Aleo Systems Inc.
+// This file is part of the Leo library.
+
+// The Leo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// The Leo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with the Leo library. If not, see .
+
+use crate::{api::Fetch, commands::Command, context::Context};
+use leo_package::imports::{ImportsDirectory, IMPORTS_DIRECTORY_NAME};
+
+use anyhow::{anyhow, Result};
+use std::{
+ fs::{create_dir_all, File},
+ io::{Read, Write},
+};
+use structopt::StructOpt;
+use tracing::Span;
+
+/// Add package from Aleo Package Manager
+#[derive(StructOpt, Debug)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Add {
+ #[structopt(name = "REMOTE")]
+ remote: Option,
+
+ #[structopt(name = "author", help = "Specify a package author", long = "author", short = "a")]
+ author: Option,
+
+ #[structopt(name = "package", help = "Specify a package name", long = "package", short = "p")]
+ package: Option,
+
+ #[structopt(name = "version", help = "Specify a package version", long = "version", short = "v")]
+ version: Option,
+}
+
+impl Add {
+ pub fn new(
+ remote: Option,
+ author: Option,
+ package: Option,
+ version: Option,
+ ) -> Add {
+ Add {
+ remote,
+ author,
+ package,
+ version,
+ }
+ }
+
+ /// Try to parse author/package string from self.remote
+ pub fn try_read_arguments(&self) -> Result<(String, String)> {
+ if let Some(val) = &self.remote {
+ let v: Vec<&str> = val.split('/').collect();
+ if v.len() == 2 {
+ Ok((v[0].to_string(), v[1].to_string()))
+ } else {
+ Err(anyhow!(
+ "Incorrect argument, please use --help for information on command use"
+ ))
+ }
+ } else if let (Some(author), Some(package)) = (&self.author, &self.package) {
+ Ok((author.clone(), package.clone()))
+ } else {
+ Err(anyhow!(
+ "Incorrect argument, please use --help for information on command use"
+ ))
+ }
+ }
+}
+
+impl Command for Add {
+ type Input = ();
+ type Output = ();
+
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Adding")
+ }
+
+ fn prelude(&self) -> Result {
+ Ok(())
+ }
+
+ fn apply(self, ctx: Context, _: Self::Input) -> Result {
+ // checking that manifest exists...
+ if ctx.manifest().is_err() {
+ return Err(anyhow!("Package Manifest not found, try running leo init or leo new"));
+ };
+
+ let (author, package_name) = match self.try_read_arguments() {
+ Ok((author, package)) => (author, package),
+ Err(err) => return Err(err),
+ };
+ let version = self.version;
+
+ // build request body (Options are skipped when sealizing)
+ let fetch = Fetch {
+ author,
+ package_name: package_name.clone(),
+ version,
+ };
+
+ let bytes = ctx.api.run_route(fetch)?.bytes()?;
+ let mut path = ctx.dir()?;
+
+ {
+ // setup directory structure since request was success
+ ImportsDirectory::create(&path)?;
+ path.push(IMPORTS_DIRECTORY_NAME);
+ path.push(package_name);
+ create_dir_all(&path)?;
+ };
+
+ let reader = std::io::Cursor::new(bytes);
+
+ let mut zip_archive = match zip::ZipArchive::new(reader) {
+ Ok(zip) => zip,
+ Err(error) => return Err(anyhow!(error)),
+ };
+
+ for i in 0..zip_archive.len() {
+ let file = match zip_archive.by_index(i) {
+ Ok(file) => file,
+ Err(error) => return Err(anyhow!(error)),
+ };
+
+ let file_name = file.name();
+
+ let mut file_path = path.clone();
+ file_path.push(file_name);
+
+ if file_name.ends_with('/') {
+ create_dir_all(file_path)?;
+ } else {
+ if let Some(parent_directory) = path.parent() {
+ create_dir_all(parent_directory)?;
+ }
+
+ File::create(file_path)?.write_all(&file.bytes().map(|e| e.unwrap()).collect::>())?;
+ }
+ }
+
+ tracing::info!("Successfully added a package");
+
+ Ok(())
+ }
+}
diff --git a/leo/commands/package/login.rs b/leo/commands/package/login.rs
new file mode 100644
index 0000000000..7e3950b03c
--- /dev/null
+++ b/leo/commands/package/login.rs
@@ -0,0 +1,115 @@
+// Copyright (C) 2019-2021 Aleo Systems Inc.
+// This file is part of the Leo library.
+
+// The Leo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// The Leo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with the Leo library. If not, see .
+
+use crate::{
+ api::{Login as LoginRoute, Profile as ProfileRoute},
+ commands::Command,
+ config::*,
+ context::Context,
+};
+
+use anyhow::{anyhow, Result};
+use std::collections::HashMap;
+use structopt::StructOpt;
+use tracing::Span;
+
+/// Login to Aleo PM and store credentials locally
+#[derive(StructOpt, Debug)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Login {
+ #[structopt(name = "AUTH_TOKEN", about = "Pass authorization token")]
+ token: Option,
+
+ #[structopt(short = "u", long = "user", about = "Username for Aleo PM")]
+ user: Option,
+
+ #[structopt(short = "p", long = "password", about = "Password for Aleo PM")]
+ pass: Option,
+}
+
+impl Login {
+ pub fn new(token: Option, user: Option, pass: Option) -> Login {
+ Login { token, user, pass }
+ }
+}
+
+impl Command for Login {
+ type Input = ();
+ type Output = String;
+
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Login")
+ }
+
+ fn prelude(&self) -> Result {
+ Ok(())
+ }
+
+ fn apply(self, ctx: Context, _: Self::Input) -> Result {
+ // quick hack to check if user is already logged in. ;)
+ if ctx.api.auth_token().is_some() {
+ tracing::info!("You are already logged in");
+ return Ok(ctx.api.auth_token().unwrap());
+ };
+
+ let mut api = ctx.api;
+
+ // ...or trying to use arguments to either get token or user-pass
+ let token = match (self.token, self.user, self.pass) {
+ // Login using existing token, use get_profile route for that
+ (Some(token), _, _) => {
+ tracing::info!("Token passed, checking...");
+
+ api.set_auth_token(token.clone());
+
+ let is_ok = api.run_route(ProfileRoute {})?;
+ if !is_ok {
+ return Err(anyhow!("Supplied token is incorrect"));
+ };
+
+ token
+ }
+
+ // Login using username and password
+ (None, Some(email_username), Some(password)) => {
+ let login = LoginRoute {
+ email_username,
+ password,
+ };
+
+ let res = api.run_route(login)?;
+ let mut res: HashMap = res.json()?;
+
+ let tok_opt = res.remove("token");
+ if tok_opt.is_none() {
+ return Err(anyhow!("Unable to get token"));
+ };
+
+ tok_opt.unwrap()
+ }
+
+ // In case token or login/pass were not passed as arguments
+ (_, _, _) => return Err(anyhow!("No credentials provided")),
+ };
+
+ // write token either after logging or if it was passed
+ write_token(token.as_str())?;
+
+ tracing::info!("Success! You are now logged in!");
+
+ Ok(token)
+ }
+}
diff --git a/leo/commands/logout.rs b/leo/commands/package/logout.rs
similarity index 63%
rename from leo/commands/logout.rs
rename to leo/commands/package/logout.rs
index 439570e051..0d844ff7ed 100644
--- a/leo/commands/logout.rs
+++ b/leo/commands/package/logout.rs
@@ -14,40 +14,37 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-//
-// Usage:
-//
-// leo logout
-//
+use crate::{commands::Command, config::remove_token, context::Context};
-#[derive(Debug)]
-pub struct LogoutCommand;
-
-use crate::{cli::CLI, cli_types::*, config::remove_token, errors::CLIError};
+use anyhow::Result;
use std::io::ErrorKind;
+use structopt::StructOpt;
+use tracing::Span;
+
+/// Remove credentials for Aleo PM from .leo directory
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Logout {}
-impl CLI for LogoutCommand {
- type Options = ();
+impl Logout {
+ pub fn new() -> Logout {
+ Logout {}
+ }
+}
+
+impl Command for Logout {
+ type Input = ();
type Output = ();
- const ABOUT: AboutType = "Logout from Aleo Package Manager";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "logout";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Logout")
+ }
- /// no options and no arguments for this buddy
- fn parse(_: &clap::ArgMatches) -> Result {
+ fn prelude(&self) -> Result {
Ok(())
}
- /// as simple as it could be - remove credentials file
- fn output(_: Self::Options) -> Result {
- // we gotta do something about this span issue :confused:
- let span = tracing::span!(tracing::Level::INFO, "Logout");
- let _ent = span.enter();
-
+ fn apply(self, _ctx: Context, _: Self::Input) -> Result {
// the only error we're interested here is NotFound
// however err in this case can also be of kind PermissionDenied or other
if let Err(err) = remove_token() {
diff --git a/leo/errors/mod.rs b/leo/commands/package/mod.rs
similarity index 77%
rename from leo/errors/mod.rs
rename to leo/commands/package/mod.rs
index 1370f5bef7..f3b292715f 100644
--- a/leo/errors/mod.rs
+++ b/leo/commands/package/mod.rs
@@ -14,11 +14,19 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-pub mod cli;
-pub use self::cli::*;
+pub mod add;
+pub use add::Add;
-pub mod commands;
-pub use self::commands::*;
+pub mod login;
+pub use login::Login;
-pub mod updater;
-pub use self::updater::*;
+pub mod logout;
+pub use logout::Logout;
+
+pub mod publish;
+pub use publish::Publish;
+
+pub mod remove;
+pub use remove::Remove;
+
+pub use super::*;
diff --git a/leo/commands/publish.rs b/leo/commands/package/publish.rs
similarity index 51%
rename from leo/commands/publish.rs
rename to leo/commands/package/publish.rs
index e25b4e7093..7b1fceb3a6 100644
--- a/leo/commands/publish.rs
+++ b/leo/commands/package/publish.rs
@@ -14,29 +14,20 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
+use super::build::Build;
use crate::{
- cli::*,
- cli_types::*,
- commands::{BuildCommand, LoginCommand},
- config::{read_token, PACKAGE_MANAGER_URL},
- errors::{
- commands::PublishError::{ConnectionUnavalaible, PackageNotPublished},
- CLIError,
- PublishError::{MissingPackageDescription, MissingPackageLicense, MissingPackageRemote},
- },
-};
-use leo_package::{
- outputs::OutputsDirectory,
- root::{Manifest, ZipFile},
+ commands::Command,
+ context::{Context, PACKAGE_MANAGER_URL},
};
+use leo_package::{outputs::OutputsDirectory, root::ZipFile};
-use clap::ArgMatches;
+use anyhow::{anyhow, Result};
use reqwest::{
blocking::{multipart::Form, Client},
header::{HeaderMap, HeaderValue},
};
use serde::Deserialize;
-use std::{convert::TryFrom, env::current_dir};
+use structopt::StructOpt;
pub const PUBLISH_URL: &str = "v1/package/publish";
@@ -45,54 +36,47 @@ struct ResponseJson {
package_id: String,
}
-#[derive(Debug)]
-pub struct PublishCommand;
-
-impl CLI for PublishCommand {
- type Options = ();
- type Output = Option;
+/// Publish package to Aleo Package Manager
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Publish {}
- const ABOUT: AboutType = "Publish the current package to the Aleo Package Manager";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "publish";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
-
- #[cfg_attr(tarpaulin, skip)]
- fn parse(_arguments: &ArgMatches) -> Result {
- Ok(())
+impl Publish {
+ pub fn new() -> Publish {
+ Publish {}
}
+}
- #[cfg_attr(tarpaulin, skip)]
- fn output(_options: Self::Options) -> Result {
- // Build all program files.
- let _output = BuildCommand::output(())?;
+impl Command for Publish {
+ type Input = ::Output;
+ type Output = Option;
- // Begin "Publishing" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Publishing");
- let _enter = span.enter();
+ /// Build program before publishing
+ fn prelude(&self) -> Result {
+ Build::new().execute()
+ }
+ fn apply(self, ctx: Context, _input: Self::Input) -> Result {
// Get the package manifest
- let path = current_dir()?;
- let package_manifest = Manifest::try_from(path.as_path())?;
-
- let package_name = package_manifest.get_package_name();
- let package_version = package_manifest.get_package_version();
-
- if package_manifest.get_package_description().is_none() {
- return Err(MissingPackageDescription.into());
- }
-
- if package_manifest.get_package_license().is_none() {
- return Err(MissingPackageLicense.into());
- }
-
- let package_remote = match package_manifest.get_package_remote() {
- Some(remote) => remote,
- None => return Err(MissingPackageRemote.into()),
+ let path = ctx.dir()?;
+ let manifest = ctx.manifest()?;
+
+ let package_name = manifest.get_package_name();
+ let package_version = manifest.get_package_version();
+
+ match (
+ manifest.get_package_description(),
+ manifest.get_package_license(),
+ manifest.get_package_remote(),
+ ) {
+ (None, _, _) => return Err(anyhow!("No package description")),
+ (_, None, _) => return Err(anyhow!("Missing package license")),
+ (_, _, None) => return Err(anyhow!("Missing package remote")),
+ (_, _, _) => (),
};
+ let package_remote = manifest.get_package_remote().unwrap();
+
// Create the output directory
OutputsDirectory::create(&path)?;
@@ -115,18 +99,9 @@ impl CLI for PublishCommand {
// Client for make POST request
let client = Client::new();
- // Get token to make an authorized request
- let token = match read_token() {
- Ok(token) => token,
-
- // If not logged in, then try logging in using JWT.
- Err(_error) => {
- tracing::warn!("You should be logged in before attempting to publish a package");
- tracing::info!("Trying to log in using JWT...");
- let options = (None, None, None);
-
- LoginCommand::output(options)?
- }
+ let token = match ctx.api.auth_token() {
+ Some(token) => token,
+ None => return Err(anyhow!("Login before publishing package: try leo login --help")),
};
// Headers for request to publish package
@@ -149,12 +124,12 @@ impl CLI for PublishCommand {
Ok(json) => json,
Err(error) => {
tracing::warn!("{:?}", error);
- return Err(PackageNotPublished("Package not published".into()).into());
+ return Err(anyhow!("Package not published"));
}
},
Err(error) => {
tracing::warn!("{:?}", error);
- return Err(ConnectionUnavalaible("Connection error".into()).into());
+ return Err(anyhow!("Connection unavailable"));
}
};
diff --git a/leo/commands/package/remove.rs b/leo/commands/package/remove.rs
new file mode 100644
index 0000000000..2d988fd55e
--- /dev/null
+++ b/leo/commands/package/remove.rs
@@ -0,0 +1,59 @@
+// Copyright (C) 2019-2021 Aleo Systems Inc.
+// This file is part of the Leo library.
+
+// The Leo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// The Leo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with the Leo library. If not, see .
+
+use crate::{commands::Command, context::Context};
+use leo_package::LeoPackage;
+
+use anyhow::Result;
+use structopt::StructOpt;
+use tracing::span::Span;
+
+/// Remove imported package
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Remove {
+ #[structopt(name = "PACKAGE")]
+ name: String,
+}
+
+impl Remove {
+ pub fn new(name: String) -> Remove {
+ Remove { name }
+ }
+}
+
+impl Command for Remove {
+ type Input = ();
+ type Output = ();
+
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Removing")
+ }
+
+ fn prelude(&self) -> Result {
+ Ok(())
+ }
+
+ fn apply(self, ctx: Context, _: Self::Input) -> Result {
+ let path = ctx.dir()?;
+ let package_name = self.name;
+
+ LeoPackage::remove_imported_package(&package_name, &path)?;
+ tracing::info!("Successfully removed package \"{}\"\n", package_name);
+
+ Ok(())
+ }
+}
diff --git a/leo/commands/prove.rs b/leo/commands/prove.rs
index 8a06b55e1d..bd240be342 100644
--- a/leo/commands/prove.rs
+++ b/leo/commands/prove.rs
@@ -14,73 +14,62 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{cli::*, cli_types::*, commands::SetupCommand, errors::CLIError};
-use leo_package::{outputs::ProofFile, root::Manifest};
-
+use super::setup::Setup;
+use crate::{commands::Command, context::Context};
+use leo_package::outputs::ProofFile;
use snarkvm_algorithms::snark::groth16::{Groth16, PreparedVerifyingKey, Proof};
use snarkvm_curves::bls12_377::{Bls12_377, Fr};
use snarkvm_models::algorithms::SNARK;
use snarkvm_utilities::bytes::ToBytes;
-use clap::ArgMatches;
+use anyhow::Result;
use rand::thread_rng;
-use std::{convert::TryFrom, env::current_dir, time::Instant};
+use structopt::StructOpt;
+use tracing::span::Span;
+
+/// Run the program and produce a proof
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Prove {
+ #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")]
+ skip_key_check: bool,
+}
-#[derive(Debug)]
-pub struct ProveCommand;
+impl Prove {
+ pub fn new(skip_key_check: bool) -> Prove {
+ Prove { skip_key_check }
+ }
+}
-impl CLI for ProveCommand {
- type Options = bool;
+impl Command for Prove {
+ type Input = ::Output;
type Output = (Proof, PreparedVerifyingKey);
- const ABOUT: AboutType = "Run the program and produce a proof";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[("--skip-key-check")];
- const NAME: NameType = "prove";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
-
- #[cfg_attr(tarpaulin, skip)]
- fn parse(arguments: &ArgMatches) -> Result {
- Ok(!arguments.is_present("skip-key-check"))
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Proving")
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(do_setup_check: Self::Options) -> Result {
- let (program, parameters, prepared_verifying_key) = SetupCommand::output(do_setup_check)?;
+ fn prelude(&self) -> Result {
+ Setup::new(self.skip_key_check).execute()
+ }
- // Begin "Proving" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Proving");
- let enter = span.enter();
+ fn apply(self, ctx: Context, input: Self::Input) -> Result {
+ let (program, parameters, prepared_verifying_key) = input;
// Get the package name
- let path = current_dir()?;
- let package_name = Manifest::try_from(path.as_path())?.get_package_name();
+ let path = ctx.dir()?;
+ let package_name = ctx.manifest()?.get_package_name();
tracing::info!("Starting...");
- // Start the timer
- let start = Instant::now();
-
let rng = &mut thread_rng();
let program_proof = Groth16::>::prove(¶meters, &program, rng)?;
- // Finish the timer
- let end = start.elapsed().as_millis();
-
// Write the proof file to the output directory
let mut proof = vec![];
program_proof.write(&mut proof)?;
ProofFile::new(&package_name).write_to(&path, &proof)?;
- // Drop "Proving" context for console logging
- drop(enter);
-
- // Begin "Done" context for console logging
- tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
- tracing::info!("Finished in {:?} milliseconds\n", end);
- });
-
Ok((program_proof, prepared_verifying_key))
}
}
diff --git a/leo/commands/remove.rs b/leo/commands/remove.rs
deleted file mode 100644
index 8be8ea1b1c..0000000000
--- a/leo/commands/remove.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use crate::{cli::*, cli_types::*, errors::CLIError};
-use leo_package::LeoPackage;
-
-use clap::ArgMatches;
-use std::env::current_dir;
-
-#[derive(Debug)]
-pub struct RemoveCommand;
-
-impl CLI for RemoveCommand {
- type Options = Option;
- type Output = ();
-
- const ABOUT: AboutType = "Uninstall a package from the current package";
- const ARGUMENTS: &'static [ArgumentType] = &[
- // (name, description, possible_values, required, index)
- (
- "NAME",
- "Removes the package from the current directory",
- &[],
- true,
- 1u64,
- ),
- ];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "remove";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
-
- #[cfg_attr(tarpaulin, skip)]
- fn parse(arguments: &ArgMatches) -> Result {
- Ok(match arguments.value_of("NAME") {
- Some(name) => Some(name.to_string()),
- None => unreachable!(),
- })
- }
-
- #[cfg_attr(tarpaulin, skip)]
- fn output(options: Self::Options) -> Result {
- // Begin "Removing" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Removing");
- let _enter = span.enter();
-
- let path = current_dir()?;
-
- if let Some(package_name) = options {
- LeoPackage::remove_imported_package(&package_name, &path)?;
- tracing::info!("Successfully removed package \"{}\"\n", package_name);
- }
-
- Ok(())
- }
-}
diff --git a/leo/commands/run.rs b/leo/commands/run.rs
index ee99079e26..ffb05896fa 100644
--- a/leo/commands/run.rs
+++ b/leo/commands/run.rs
@@ -14,48 +14,48 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{cli::*, cli_types::*, commands::ProveCommand, errors::CLIError};
+use super::prove::Prove;
+use crate::{commands::Command, context::Context};
use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType};
+use anyhow::Result;
use snarkvm_algorithms::snark::groth16::Groth16;
use snarkvm_curves::bls12_377::{Bls12_377, Fr};
use snarkvm_models::algorithms::SNARK;
+use structopt::StructOpt;
+use tracing::span::Span;
+
+/// Build, Prove and Run Leo program with inputs
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Run {
+ #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")]
+ skip_key_check: bool,
+}
-use clap::ArgMatches;
-use std::time::Instant;
-
-#[derive(Debug)]
-pub struct RunCommand;
+impl Run {
+ pub fn new(skip_key_check: bool) -> Run {
+ Run { skip_key_check }
+ }
+}
-impl CLI for RunCommand {
- type Options = bool;
+impl Command for Run {
+ type Input = ::Output;
type Output = ();
- const ABOUT: AboutType = "Run a program with input variables";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[("--skip-key-check")];
- const NAME: NameType = "run";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
-
- #[cfg_attr(tarpaulin, skip)]
- fn parse(arguments: &ArgMatches) -> Result {
- Ok(!arguments.is_present("skip-key-check"))
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Verifying")
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(do_setup_check: Self::Options) -> Result<(), CLIError> {
- let (proof, prepared_verifying_key) = ProveCommand::output(do_setup_check)?;
+ fn prelude(&self) -> Result {
+ Prove::new(self.skip_key_check).execute()
+ }
- // Begin "Verifying" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Verifying");
- let enter = span.enter();
+ fn apply(self, _ctx: Context, input: Self::Input) -> Result {
+ let (proof, prepared_verifying_key) = input;
tracing::info!("Starting...");
- // Start the timer
- let start = Instant::now();
-
// Run the verifier
let is_success = Groth16::, Vec>::verify(
&prepared_verifying_key,
@@ -63,23 +63,12 @@ impl CLI for RunCommand {
&proof,
)?;
- // End the timer
- let end = start.elapsed().as_millis();
-
// Log the verifier output
match is_success {
true => tracing::info!("Proof is valid"),
false => tracing::error!("Proof is invalid"),
};
- // Drop "Verifying" context for console logging
- drop(enter);
-
- // Begin "Done" context for console logging
- tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
- tracing::info!("Finished in {:?} milliseconds\n", end);
- });
-
Ok(())
}
}
diff --git a/leo/commands/setup.rs b/leo/commands/setup.rs
index 95b11c674b..550934f1e2 100644
--- a/leo/commands/setup.rs
+++ b/leo/commands/setup.rs
@@ -14,81 +14,71 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{
- cli::*,
- cli_types::*,
- commands::BuildCommand,
- errors::{CLIError, RunError},
-};
+use super::build::Build;
+use crate::{commands::Command, context::Context};
use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType};
use leo_package::{
outputs::{ProvingKeyFile, VerificationKeyFile},
- root::Manifest,
source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
};
+use anyhow::{anyhow, Result};
+use rand::thread_rng;
use snarkvm_algorithms::snark::groth16::{Groth16, Parameters, PreparedVerifyingKey, VerifyingKey};
use snarkvm_curves::bls12_377::{Bls12_377, Fr};
use snarkvm_models::algorithms::snark::SNARK;
+use structopt::StructOpt;
+use tracing::span::Span;
+
+/// Executes the setup command for a Leo program
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Setup {
+ #[structopt(long = "skip-key-check", help = "Skip key verification")]
+ skip_key_check: bool,
+}
-use clap::ArgMatches;
-use rand::thread_rng;
-use std::{convert::TryFrom, env::current_dir, time::Instant};
-
-#[derive(Debug)]
-pub struct SetupCommand;
+impl Setup {
+ pub fn new(skip_key_check: bool) -> Setup {
+ Setup { skip_key_check }
+ }
+}
-impl CLI for SetupCommand {
- type Options = bool;
+impl Command for Setup {
+ type Input = ::Output;
type Output = (
Compiler,
Parameters,
PreparedVerifyingKey,
);
- const ABOUT: AboutType = "Run a program setup";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[("--skip-key-check")];
- const NAME: NameType = "setup";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Setup")
+ }
- #[cfg_attr(tarpaulin, skip)]
- fn parse(arguments: &ArgMatches) -> Result {
- Ok(!arguments.is_present("skip-key-check"))
+ fn prelude(&self) -> Result {
+ Build::new().execute()
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(do_check: Self::Options) -> Result {
- // Get the package name
- let path = current_dir()?;
- let package_name = Manifest::try_from(path.as_path())?.get_package_name();
+ fn apply(self, ctx: Context, input: Self::Input) -> Result {
+ let path = ctx.dir()?;
+ let package_name = ctx.manifest()?.get_package_name();
- match BuildCommand::output(())? {
+ match input {
Some((program, checksum_differs)) => {
- // Begin "Setup" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Setup");
- let enter = span.enter();
-
// Check if a proving key and verification key already exists
let keys_exist = ProvingKeyFile::new(&package_name).exists_at(&path)
&& VerificationKeyFile::new(&package_name).exists_at(&path);
// If keys do not exist or the checksum differs, run the program setup
// If keys do not exist or the checksum differs, run the program setup
- let (end, proving_key, prepared_verifying_key) = if !keys_exist || checksum_differs {
+ let (proving_key, prepared_verifying_key) = if !keys_exist || checksum_differs {
tracing::info!("Starting...");
- // Start the timer for setup
- let setup_start = Instant::now();
-
// Run the program setup operation
let rng = &mut thread_rng();
let (proving_key, prepared_verifying_key) =
- Groth16::, Vec>::setup(&program, rng).unwrap();
-
- // End the timer
- let end = setup_start.elapsed().as_millis();
+ Groth16::, Vec>::setup(&program, rng)?;
// TODO (howardwu): Convert parameters to a 'proving key' struct for serialization.
// Write the proving key file to the output directory
@@ -107,20 +97,19 @@ impl CLI for SetupCommand {
let _ = verification_key_file.write_to(&path, &verification_key)?;
tracing::info!("Complete");
- (end, proving_key, prepared_verifying_key)
+ (proving_key, prepared_verifying_key)
} else {
tracing::info!("Detected saved setup");
- // Start the timer for setup
- let setup_start = Instant::now();
-
// Read the proving key file from the output directory
tracing::info!("Loading proving key...");
- if !do_check {
+
+ if self.skip_key_check {
tracing::info!("Skipping curve check");
}
let proving_key_bytes = ProvingKeyFile::new(&package_name).read_from(&path)?;
- let proving_key = Parameters::::read(proving_key_bytes.as_slice(), do_check)?;
+ let proving_key =
+ Parameters::::read(proving_key_bytes.as_slice(), !self.skip_key_check)?;
tracing::info!("Complete");
// Read the verification key file from the output directory
@@ -132,20 +121,9 @@ impl CLI for SetupCommand {
let prepared_verifying_key = PreparedVerifyingKey::::from(verifying_key);
tracing::info!("Complete");
- // End the timer
- let end = setup_start.elapsed().as_millis();
-
- (end, proving_key, prepared_verifying_key)
+ (proving_key, prepared_verifying_key)
};
- // Drop "Setup" context for console logging
- drop(enter);
-
- // Begin "Done" context for console logging
- tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
- tracing::info!("Finished in {:?} milliseconds\n", end);
- });
-
Ok((program, proving_key, prepared_verifying_key))
}
None => {
@@ -153,9 +131,7 @@ impl CLI for SetupCommand {
main_file_path.push(SOURCE_DIRECTORY_NAME);
main_file_path.push(MAIN_FILENAME);
- Err(CLIError::RunError(RunError::MainFileDoesNotExist(
- main_file_path.into_os_string(),
- )))
+ Err(anyhow!("Unable to build, check that main file exists"))
}
}
}
diff --git a/leo/commands/test.rs b/leo/commands/test.rs
index 6f8a18886e..f9bf89a003 100644
--- a/leo/commands/test.rs
+++ b/leo/commands/test.rs
@@ -14,67 +14,75 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{
- cli::*,
- cli_types::*,
- errors::{CLIError, TestError::ProgramFileDoesNotExist},
-};
+use crate::{commands::Command, context::Context};
use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType};
use leo_package::{
inputs::*,
outputs::{OutputsDirectory, OUTPUTS_DIRECTORY_NAME},
- root::Manifest,
- source::{LibraryFile, MainFile, LIBRARY_FILENAME, MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
+ source::{MainFile, MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
};
+use anyhow::{anyhow, Result};
use snarkvm_curves::edwards_bls12::Fq;
+use std::{convert::TryFrom, path::PathBuf, time::Instant};
+use structopt::StructOpt;
+use tracing::span::Span;
+
+/// Build program and run tests command
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Test {
+ #[structopt(short = "f", long = "file", name = "file")]
+ files: Vec,
+}
-use clap::ArgMatches;
-use std::{convert::TryFrom, env::current_dir, time::Instant};
-
-#[derive(Debug)]
-pub struct TestCommand;
+impl Test {
+ pub fn new(files: Vec) -> Test {
+ Test { files }
+ }
+}
-impl CLI for TestCommand {
- type Options = ();
+impl Command for Test {
+ type Input = ();
type Output = ();
- const ABOUT: AboutType = "Compile and run all tests in the current package";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "test";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Test")
+ }
- #[cfg_attr(tarpaulin, skip)]
- fn parse(_arguments: &ArgMatches) -> Result {
+ fn prelude(&self) -> Result {
Ok(())
}
- #[cfg_attr(tarpaulin, skip)]
- fn output(_options: Self::Options) -> Result {
- let path = current_dir()?;
-
+ fn apply(self, ctx: Context, _: Self::Input) -> Result {
// Get the package name
- let manifest = Manifest::try_from(path.as_path())?;
- let package_name = manifest.get_package_name();
+ let package_name = ctx.manifest()?.get_package_name();
// Sanitize the package path to the root directory
- let mut package_path = path;
+ let mut package_path = ctx.dir()?;
if package_path.is_file() {
package_path.pop();
}
- let mut file_path = package_path.clone();
- file_path.push(SOURCE_DIRECTORY_NAME);
+ let mut to_test: Vec = Vec::new();
- // Verify a main or library file exists
- if MainFile::exists_at(&package_path) {
+ // if -f flag was used, then we'll test only this files
+ if !self.files.is_empty() {
+ to_test.extend(self.files.iter().cloned());
+
+ // if args were not passed - try main file
+ } else if MainFile::exists_at(&package_path) {
+ let mut file_path = package_path.clone();
+ file_path.push(SOURCE_DIRECTORY_NAME);
file_path.push(MAIN_FILENAME);
- } else if LibraryFile::exists_at(&package_path) {
- file_path.push(LIBRARY_FILENAME);
+ to_test.push(file_path);
+
+ // when no main file and no files marked - error
} else {
- return Err(ProgramFileDoesNotExist(package_path.into()).into());
+ return Err(anyhow!(
+ "Program file does not exist {}",
+ package_path.to_string_lossy()
+ ));
}
// Construct the path to the output directory;
@@ -84,49 +92,45 @@ impl CLI for TestCommand {
// Create the output directory
OutputsDirectory::create(&package_path)?;
- // Begin "Test" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Test");
- let enter = span.enter();
-
- // Start the timer
- let start = Instant::now();
-
- // Parse the current main program file
- let program =
- Compiler::::parse_program_without_input(package_name, file_path, output_directory)?;
-
- // Parse all inputs as input pairs
- let pairs = InputPairs::try_from(package_path.as_path())?;
-
- // Run tests
- let temporary_program = program;
- let (passed, failed) = temporary_program.compile_test_constraints(pairs)?;
-
- // Drop "Test" context for console logging
- drop(enter);
-
- // Set the result of the test command to passed if no tests failed.
- if failed == 0 {
- // Begin "Done" context for console logging
- tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
+ // Finally test every passed file
+ for file_path in to_test {
+ tracing::info!("Running tests in file {:?}", file_path);
+
+ let input_pairs = match InputPairs::try_from(package_path.as_path()) {
+ Ok(pairs) => pairs,
+ Err(_) => {
+ tracing::warn!("Unable to find inputs, ignore this message or put them into /inputs folder");
+ InputPairs::new()
+ }
+ };
+
+ let timer = Instant::now();
+ let program = Compiler::::parse_program_without_input(
+ package_name.clone(),
+ file_path,
+ output_directory.clone(),
+ )?;
+
+ let temporary_program = program;
+ let (passed, failed) = temporary_program.compile_test_constraints(input_pairs)?;
+ let time_taken = timer.elapsed().as_millis();
+
+ if failed == 0 {
tracing::info!(
"Tests passed in {} milliseconds. {} passed; {} failed;\n",
- start.elapsed().as_millis(),
+ time_taken,
passed,
failed
);
- });
- } else {
- // Begin "Done" context for console logging
- tracing::span!(tracing::Level::ERROR, "Done").in_scope(|| {
+ } else {
tracing::error!(
"Tests failed in {} milliseconds. {} passed; {} failed;\n",
- start.elapsed().as_millis(),
+ time_taken,
passed,
failed
);
- });
- };
+ }
+ }
Ok(())
}
diff --git a/leo/commands/update.rs b/leo/commands/update.rs
index dad803c947..09e0ca640a 100644
--- a/leo/commands/update.rs
+++ b/leo/commands/update.rs
@@ -14,179 +14,97 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{cli::CLI, cli_types::*, config::Config, updater::Updater};
-
-use clap::AppSettings;
-
-#[derive(Debug)]
-pub struct UpdateCommand;
-
-impl CLI for UpdateCommand {
- // (show_all_versions, quiet)
- type Options = Option<(bool, bool, bool)>;
- type Output = ();
-
- const ABOUT: AboutType = "Update Leo to the latest version";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[
- "[list] -l --list 'List all available versions of Leo'",
- "[quiet] -q --quiet 'Suppress outputs to terminal'",
- "[studio] -s --studio 'For Aleo Studio only'",
- ];
- const NAME: NameType = "update";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[
- // (name, description, options, settings)
- (
- UpdateAutomatic::NAME,
- UpdateAutomatic::ABOUT,
- UpdateAutomatic::ARGUMENTS,
- UpdateAutomatic::FLAGS,
- &UpdateAutomatic::OPTIONS,
- &[
- AppSettings::ColoredHelp,
- AppSettings::DisableHelpSubcommand,
- AppSettings::DisableVersion,
- ],
- ),
- ];
-
- fn parse(arguments: &clap::ArgMatches) -> Result {
- if let ("automatic", Some(arguments)) = arguments.subcommand() {
- // Run the `automatic` subcommand
- let options = UpdateAutomatic::parse(arguments)?;
- let _output = UpdateAutomatic::output(options)?;
- return Ok(None);
- };
-
- let show_all_versions = arguments.is_present("list");
- let quiet = arguments.is_present("quiet");
- let studio = arguments.is_present("studio");
-
- Ok(Some((show_all_versions, quiet, studio)))
- }
-
- fn output(options: Self::Options) -> Result {
- // Begin "Updating" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Updating");
- let _enter = span.enter();
-
- let (show_all_versions, quiet, studio) = match options {
- Some(options) => options,
- None => return Ok(()),
- };
-
- match show_all_versions {
- true => match Updater::show_available_releases() {
- Ok(_) => return Ok(()),
- Err(e) => {
- tracing::error!("Could not fetch that latest version of Leo");
- tracing::error!("{}", e);
- }
- },
- false => {
- let config = Config::read_config()?;
+use crate::{commands::Command, config::Config, context::Context, updater::Updater};
+
+use anyhow::{anyhow, Result};
+use structopt::StructOpt;
+use tracing::span::Span;
+
+/// Setting for automatic updates of Leo
+#[derive(Debug, StructOpt, PartialEq)]
+pub enum Sub {
+ Automatic {
+ #[structopt(name = "bool", help = "Boolean value: true or false", parse(try_from_str))]
+ value: bool,
+ },
+}
- // If update is run with studio and the automatic update is off, finish quietly
- if studio && !config.update.automatic {
- return Ok(());
- }
+/// Update Leo to the latest version
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Update {
+ /// List all available versions of Leo
+ #[structopt(short, long)]
+ list: bool,
+
+ /// For Aleo Studio only
+ #[structopt(short, long)]
+ studio: bool,
+
+ /// Setting for automatic updates of Leo
+ #[structopt(subcommand)]
+ automatic: Option,
+}
- match Updater::update_to_latest_release(!quiet) {
- Ok(status) => {
- if !quiet {
- if status.uptodate() {
- tracing::info!("Leo is already on the latest version");
- } else if status.updated() {
- tracing::info!("Leo has successfully updated to version {}", status.version());
- }
- }
- return Ok(());
- }
- Err(e) => {
- if !quiet {
- tracing::error!("Could not update Leo to the latest version");
- tracing::error!("{}", e);
- }
- }
- }
- }
+impl Update {
+ pub fn new(list: bool, studio: bool, automatic: Option) -> Update {
+ Update {
+ list,
+ studio,
+ automatic,
}
- Ok(())
}
}
-//TODO (raychu86) Move this to dedicated file/module
-#[derive(Debug)]
-pub struct UpdateAutomatic;
-
-impl CLI for UpdateAutomatic {
- // (is_automatic, quiet)
- type Options = (Option, bool);
+impl Command for Update {
+ type Input = ();
type Output = ();
- const ABOUT: AboutType = "Setting for automatic updates of Leo";
- const ARGUMENTS: &'static [ArgumentType] = &[
- // (name, description, possible_values, required, index)
- (
- "automatic",
- "Enable or disable automatic updates",
- &["true", "false"],
- false,
- 1u64,
- ),
- ];
- const FLAGS: &'static [FlagType] = &["[quiet] -q --quiet 'Suppress outputs to terminal'"];
- const NAME: NameType = "automatic";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
-
- fn parse(arguments: &clap::ArgMatches) -> Result {
- let quiet = arguments.is_present("quiet");
-
- match arguments.value_of("automatic") {
- Some(automatic) => {
- // TODO enforce that the possible values is true or false
- let automatic = match automatic {
- "true" => Some(true),
- "false" => Some(false),
- _ => unreachable!(),
- };
-
- Ok((automatic, quiet))
- }
- None => Ok((None, quiet)),
- }
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Updating")
}
- fn output(options: Self::Options) -> Result {
- // Begin "Settings" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Settings");
- let enter = span.enter();
+ fn prelude(&self) -> Result {
+ Ok(())
+ }
- // If a boolean value is provided, update the saved
- // `automatic` configuration value to this boolean value.
- if let Some(automatic) = options.0 {
- Config::set_update_automatic(automatic)?;
+ fn apply(self, _: Context, _: Self::Input) -> Result {
+ // if --list is passed - simply list everything and exit
+ if self.list {
+ return Updater::show_available_releases().map_err(|e| anyhow!("Could not fetch versions: {}", e));
}
- // If --quiet is not enabled, log the output.
- if !options.1 {
- // Read the `automatic` value now.
- let automatic = Config::read_config()?.update.automatic;
+ // in case automatic subcommand was called
+ if let Some(Sub::Automatic { value }) = self.automatic {
+ Config::set_update_automatic(value)?;
- // Log the output.
- tracing::debug!("automatic = {}", automatic);
- match automatic {
- true => tracing::info!("Automatic updates are enabled. Leo will update as new versions are released."),
+ match value {
+ true => tracing::info!("Automatic updates are enabled. Leo will update as new versions are released"),
false => {
- tracing::info!("Automatic updates are disabled. Leo will not update as new versions are released.")
+ tracing::info!("Automatic updates are disabled. Leo will not update as new versions are released")
}
};
+
+ return Ok(());
+ }
+
+ let config = Config::read_config()?;
+ // If update is run with studio and the automatic update is off, finish quietly
+ if self.studio && !config.update.automatic {
+ return Ok(());
}
- // Drop "Settings" context for console logging.
- drop(enter);
+ match Updater::update_to_latest_release(true) {
+ Ok(status) => match (status.uptodate(), status.updated()) {
+ (true, _) => tracing::info!("Leo is already on the latest version"),
+ (_, true) => tracing::info!("Leo has successfully updated to version {}", status.version()),
+ (_, _) => (),
+ },
+ Err(e) => {
+ tracing::error!("Could not update Leo to the latest version");
+ tracing::error!("{}", e);
+ }
+ }
Ok(())
}
diff --git a/leo/commands/watch.rs b/leo/commands/watch.rs
index a4ef19067e..16ae65c2a5 100644
--- a/leo/commands/watch.rs
+++ b/leo/commands/watch.rs
@@ -14,42 +14,55 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{cli::CLI, cli_types::*, commands::BuildCommand, errors::CLIError};
-use clap::ArgMatches;
-use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
+use super::build::Build;
+use crate::{commands::Command, context::Context};
+
use std::{sync::mpsc::channel, time::Duration};
+use anyhow::{anyhow, Result};
+use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
+use structopt::StructOpt;
+use tracing::span::Span;
+
const LEO_SOURCE_DIR: &str = "src/";
-// Time interval for watching files, in seconds
-const INTERVAL: u64 = 3;
+/// Watch file changes in src/ directory and run Build Command
+#[derive(StructOpt, Debug, Default)]
+#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
+pub struct Watch {
+ /// Set up watch interval
+ #[structopt(short, long, default_value = "3")]
+ interval: u64,
+}
-pub struct WatchCommand;
+impl Watch {
+ pub fn new(interval: u64) -> Watch {
+ Watch { interval }
+ }
+}
-impl CLI for WatchCommand {
- type Options = ();
+impl Command for Watch {
+ type Input = ();
type Output = ();
- const ABOUT: AboutType = "Watch for changes of Leo source files";
- const ARGUMENTS: &'static [ArgumentType] = &[];
- const FLAGS: &'static [FlagType] = &[];
- const NAME: NameType = "watch";
- const OPTIONS: &'static [OptionType] = &[];
- const SUBCOMMANDS: &'static [SubCommandType] = &[];
+ fn log_span(&self) -> Span {
+ tracing::span!(tracing::Level::INFO, "Watching")
+ }
- #[cfg_attr(tarpaulin, skip)]
- fn parse(_arguments: &ArgMatches) -> Result {
+ fn prelude(&self) -> Result {
Ok(())
}
- fn output(_options: Self::Options) -> Result {
- // Begin "Watching" context for console logging
- let span = tracing::span!(tracing::Level::INFO, "Watching");
- let _enter = span.enter();
-
+ fn apply(self, _ctx: Context, _: Self::Input) -> Result {
let (tx, rx) = channel();
- let mut watcher = watcher(tx, Duration::from_secs(INTERVAL)).unwrap();
- watcher.watch(LEO_SOURCE_DIR, RecursiveMode::Recursive).unwrap();
+ let mut watcher = watcher(tx, Duration::from_secs(self.interval)).unwrap();
+
+ watcher.watch(LEO_SOURCE_DIR, RecursiveMode::Recursive).map_err(|e| {
+ anyhow!(
+ "Unable to watch, check that directory contains Leo.toml file. Error: {}",
+ e
+ )
+ })?;
tracing::info!("Watching Leo source code");
@@ -57,7 +70,7 @@ impl CLI for WatchCommand {
match rx.recv() {
// See changes on the write event
Ok(DebouncedEvent::Write(_write)) => {
- match BuildCommand::output(()) {
+ match Build::new().execute() {
Ok(_output) => {
tracing::info!("Built successfully");
}
diff --git a/leo/config.rs b/leo/config.rs
index 5fa4ddcedb..efc1ca4ed3 100644
--- a/leo/config.rs
+++ b/leo/config.rs
@@ -14,11 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::errors::CLIError;
-
-use dirs::home_dir;
-use lazy_static::lazy_static;
-use serde::{Deserialize, Serialize};
use std::{
fs::{self, create_dir_all, File},
io,
@@ -26,7 +21,10 @@ use std::{
path::{Path, PathBuf},
};
-pub const PACKAGE_MANAGER_URL: &str = "https://api.aleo.pm/";
+use anyhow::Error;
+use dirs::home_dir;
+use lazy_static::lazy_static;
+use serde::{Deserialize, Serialize};
pub const LEO_CREDENTIALS_FILE: &str = "credentials";
pub const LEO_CONFIG_FILE: &str = "config.toml";
@@ -75,7 +73,7 @@ impl Default for Config {
impl Config {
/// Read the config from the `config.toml` file
- pub fn read_config() -> Result {
+ pub fn read_config() -> Result {
let config_dir = LEO_CONFIG_DIRECTORY.clone();
let config_path = LEO_CONFIG_PATH.clone();
@@ -112,7 +110,7 @@ impl Config {
}
/// Update the `automatic` configuration in the `config.toml` file.
- pub fn set_update_automatic(automatic: bool) -> Result<(), CLIError> {
+ pub fn set_update_automatic(automatic: bool) -> Result<(), Error> {
let mut config = Self::read_config()?;
if config.update.automatic != automatic {
diff --git a/leo/context.rs b/leo/context.rs
new file mode 100644
index 0000000000..077f382428
--- /dev/null
+++ b/leo/context.rs
@@ -0,0 +1,72 @@
+// Copyright (C) 2019-2021 Aleo Systems Inc.
+// This file is part of the Leo library.
+
+// The Leo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// The Leo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with the Leo library. If not, see .
+
+use crate::{api::Api, config};
+use leo_package::root::Manifest;
+
+use anyhow::Result;
+use std::{convert::TryFrom, env::current_dir, path::PathBuf};
+
+pub const PACKAGE_MANAGER_URL: &str = "https://api.aleo.pm/";
+
+/// Project context, manifest, current directory etc
+/// All the info that is relevant in most of the commands
+#[derive(Clone)]
+pub struct Context {
+ /// Api client for Aleo PM
+ pub api: Api,
+
+ /// Path at which the command is called, None when default
+ pub path: Option,
+}
+
+impl Context {
+ pub fn dir(&self) -> Result {
+ match &self.path {
+ Some(path) => Ok(path.clone()),
+ None => Ok(current_dir()?),
+ }
+ }
+
+ /// Get package manifest for current context
+ pub fn manifest(&self) -> Result {
+ Ok(Manifest::try_from(self.dir()?.as_path())?)
+ }
+}
+
+/// Create a new context for the current directory.
+pub fn create_context(path: PathBuf) -> Result {
+ let token = match config::read_token() {
+ Ok(token) => Some(token),
+ Err(_) => None,
+ };
+
+ let api = Api::new(PACKAGE_MANAGER_URL.to_string(), token);
+
+ Ok(Context { api, path: Some(path) })
+}
+
+/// Returns project context.
+pub fn get_context() -> Result {
+ let token = match config::read_token() {
+ Ok(token) => Some(token),
+ Err(_) => None,
+ };
+
+ let api = Api::new(PACKAGE_MANAGER_URL.to_string(), token);
+
+ Ok(Context { api, path: None })
+}
diff --git a/leo/errors/cli.rs b/leo/errors/cli.rs
deleted file mode 100644
index 3d33adfef1..0000000000
--- a/leo/errors/cli.rs
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use crate::errors::*;
-use leo_compiler::errors::OutputFileError;
-use leo_package::errors::*;
-
-#[derive(Debug, Error)]
-pub enum CLIError {
- #[error("{}", _0)]
- AddError(AddError),
-
- #[error("{}", _0)]
- BuildError(BuildError),
-
- #[error("{}", _0)]
- ZipFileError(ZipFileError),
-
- #[error("{}", _0)]
- ChecksumFileError(ChecksumFileError),
-
- #[error("{}", _0)]
- CircuitFileError(CircuitFileError),
-
- #[error("{}: {}", _0, _1)]
- Crate(&'static str, String),
-
- #[error("{}", _0)]
- GitignoreError(GitignoreError),
-
- #[error("{}", _0)]
- InitError(InitError),
-
- #[error("{}", _0)]
- ImportsDirectoryError(ImportsDirectoryError),
-
- #[error("{}", _0)]
- InputsDirectoryError(InputsDirectoryError),
-
- #[error("{}", _0)]
- InputFileError(InputFileError),
-
- #[error("{}", _0)]
- LibraryFileError(LibraryFileError),
-
- #[error("{}", _0)]
- LoginError(LoginError),
-
- #[error("{}", _0)]
- MainFileError(MainFileError),
-
- #[error("{}", _0)]
- ManifestError(ManifestError),
-
- #[error("{}", _0)]
- NewError(NewError),
-
- #[error("{}", _0)]
- OutputFileError(OutputFileError),
-
- #[error("{}", _0)]
- OutputsDirectoryError(OutputsDirectoryError),
-
- #[error("{}", _0)]
- PackageError(PackageError),
-
- #[error("{}", _0)]
- ProofFileError(ProofFileError),
-
- #[error("{}", _0)]
- ProvingKeyFileError(ProvingKeyFileError),
-
- #[error("{}", _0)]
- PublishError(PublishError),
-
- #[error("{}", _0)]
- READMEError(READMEError),
-
- #[error("{}", _0)]
- RunError(RunError),
-
- #[error("{}", _0)]
- SNARKError(snarkvm_errors::algorithms::snark::SNARKError),
-
- #[error("{}", _0)]
- SourceDirectoryError(SourceDirectoryError),
-
- #[error("{}", _0)]
- StateFileError(StateFileError),
-
- #[error("{}", _0)]
- TestError(TestError),
-
- #[error("TomlSerError: {0}")]
- TomlSerError(#[from] toml::ser::Error),
-
- #[error("TomlDeError: {0}")]
- TomlDeError(#[from] toml::de::Error),
-
- #[error("{}", _0)]
- VerificationKeyFileError(VerificationKeyFileError),
-}
-
-macro_rules! impl_cli_error {
- ($($t:tt), +) => {
- $(impl From<$t> for CLIError {
- fn from(error: $t) -> Self {
- tracing::error!("{}\n", error);
-
- CLIError::$t(error)
- }
- })*
- }
-}
-
-impl_cli_error!(
- AddError,
- BuildError,
- CircuitFileError,
- ChecksumFileError,
- GitignoreError,
- ImportsDirectoryError,
- InitError,
- InputsDirectoryError,
- InputFileError,
- LibraryFileError,
- LoginError,
- MainFileError,
- ManifestError,
- NewError,
- OutputFileError,
- OutputsDirectoryError,
- PackageError,
- ProofFileError,
- ProvingKeyFileError,
- PublishError,
- READMEError,
- RunError,
- SourceDirectoryError,
- StateFileError,
- TestError,
- VerificationKeyFileError,
- ZipFileError
-);
-
-impl From for CLIError {
- fn from(error: clap::Error) -> Self {
- tracing::error!("{}\n", error);
- CLIError::Crate("clap", error.to_string())
- }
-}
-
-impl From for CLIError {
- fn from(error: leo_compiler::errors::CompilerError) -> Self {
- tracing::error!("{}\n", error);
- CLIError::Crate("leo-compiler", "Program failed due to previous error".into())
- }
-}
-
-impl From for CLIError {
- fn from(error: leo_input::errors::InputParserError) -> Self {
- tracing::error!("{}\n", error);
- CLIError::Crate("leo-input", "Program failed due to previous error".into())
- }
-}
-
-impl From for CLIError {
- fn from(error: reqwest::Error) -> Self {
- tracing::error!("{}\n", error);
- CLIError::Crate("rewquest", error.to_string())
- }
-}
-
-impl From for CLIError {
- fn from(error: snarkvm_errors::algorithms::snark::SNARKError) -> Self {
- tracing::error!("{}\n", error);
- CLIError::Crate("snarkvm_errors", error.to_string())
- }
-}
-
-impl From for CLIError {
- fn from(error: snarkvm_errors::gadgets::SynthesisError) -> Self {
- tracing::error!("{}\n", error);
- CLIError::Crate("snarkvm_errors", error.to_string())
- }
-}
-
-impl From for CLIError {
- fn from(error: serde_json::error::Error) -> Self {
- tracing::error!("{}\n", error);
- CLIError::Crate("serde_json", error.to_string())
- }
-}
-
-impl From for CLIError {
- fn from(error: std::io::Error) -> Self {
- tracing::error!("{}\n", error);
- CLIError::Crate("std::io", error.to_string())
- }
-}
diff --git a/leo/errors/commands/add.rs b/leo/errors/commands/add.rs
deleted file mode 100644
index bbcd33f45b..0000000000
--- a/leo/errors/commands/add.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use std::ffi::OsString;
-
-#[derive(Debug, Error)]
-pub enum AddError {
- #[error("Connection unavailable {:?}", _0)]
- ConnectionUnavailable(OsString),
-
- #[error("Missing author or package name: leo add author/package")]
- MissingAuthorOrPackageName,
-
- #[error("Invalid remote")]
- InvalidRemote,
-
- #[error("Not authorized in package manager. Use leo login to sign in")]
- NotAuthorized,
-
- #[error("{:?}", _0)]
- ZipError(OsString),
-}
diff --git a/leo/errors/commands/build.rs b/leo/errors/commands/build.rs
deleted file mode 100644
index 9d478b2c68..0000000000
--- a/leo/errors/commands/build.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use leo_package::errors::ManifestError;
-
-use std::ffi::OsString;
-
-#[derive(Debug, Error)]
-pub enum BuildError {
- #[error("main file {:?} does not exist", _0)]
- MainFileDoesNotExist(OsString),
-
- #[error("{}", _0)]
- ManifestError(#[from] ManifestError),
-}
diff --git a/leo/errors/commands/init.rs b/leo/errors/commands/init.rs
deleted file mode 100644
index caa2945bde..0000000000
--- a/leo/errors/commands/init.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use leo_package::errors::ManifestError;
-
-use std::{ffi::OsString, io};
-
-#[derive(Debug, Error)]
-pub enum InitError {
- #[error("root directory {:?} creating: {}", _0, _1)]
- CreatingRootDirectory(OsString, io::Error),
-
- #[error("directory {:?} does not exist", _0)]
- DirectoryDoesNotExist(OsString),
-
- #[error("{}", _0)]
- ManifestError(#[from] ManifestError),
-
- #[error("package name is missing - {:?}", _0)]
- ProjectNameInvalid(OsString),
-}
diff --git a/leo/errors/commands/login.rs b/leo/errors/commands/login.rs
deleted file mode 100644
index ce506b84e9..0000000000
--- a/leo/errors/commands/login.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-#[derive(Debug, Error)]
-pub enum LoginError {
- #[error("No token was provided in the response")]
- CannotGetToken,
-
- #[error("Could not connect to the package manager")]
- NoConnectionFound,
-
- #[error("No login credentials were provided")]
- NoCredentialsProvided,
-
- #[error("Wrong login or password")]
- WrongLoginOrPassword,
-}
diff --git a/leo/errors/commands/mod.rs b/leo/errors/commands/mod.rs
deleted file mode 100644
index 079c24deec..0000000000
--- a/leo/errors/commands/mod.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-pub mod add;
-pub use self::add::*;
-
-pub mod build;
-pub use self::build::*;
-
-pub mod init;
-pub use self::init::*;
-
-pub mod login;
-pub use self::login::*;
-
-pub mod new;
-pub use self::new::*;
-
-pub mod publish;
-pub use self::publish::*;
-
-pub mod run;
-pub use self::run::*;
-
-pub mod test;
-pub use self::test::*;
diff --git a/leo/errors/commands/new.rs b/leo/errors/commands/new.rs
deleted file mode 100644
index 9df2571e87..0000000000
--- a/leo/errors/commands/new.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use leo_package::errors::ManifestError;
-
-use std::{ffi::OsString, io};
-
-#[derive(Debug, Error)]
-pub enum NewError {
- #[error("root directory {:?} creating: {}", _0, _1)]
- CreatingRootDirectory(OsString, io::Error),
-
- #[error("directory {:?} already exists", _0)]
- DirectoryAlreadyExists(OsString),
-
- #[error("{}", _0)]
- ManifestError(#[from] ManifestError),
-
- #[error("package name is missing - {:?}", _0)]
- ProjectNameInvalid(OsString),
-}
diff --git a/leo/errors/commands/publish.rs b/leo/errors/commands/publish.rs
deleted file mode 100644
index 1a8026b088..0000000000
--- a/leo/errors/commands/publish.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use std::ffi::OsString;
-
-#[derive(Debug, Error)]
-pub enum PublishError {
- #[error("connection unavailable {:?}", _0)]
- ConnectionUnavalaible(OsString),
-
- #[error("package toml file is missing a description")]
- MissingPackageDescription,
-
- #[error("package toml file is missing a license")]
- MissingPackageLicense,
-
- #[error("package toml file is missing a remote")]
- MissingPackageRemote,
-
- #[error("package not published {:?}", _0)]
- PackageNotPublished(OsString),
-}
diff --git a/leo/errors/commands/run.rs b/leo/errors/commands/run.rs
deleted file mode 100644
index 98bfd7d28d..0000000000
--- a/leo/errors/commands/run.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-use leo_package::errors::ManifestError;
-
-use std::ffi::OsString;
-
-#[derive(Debug, Error)]
-pub enum RunError {
- #[error("main file {:?} does not exist", _0)]
- MainFileDoesNotExist(OsString),
-
- #[error("{}", _0)]
- ManifestError(#[from] ManifestError),
-}
diff --git a/leo/errors/commands/test.rs b/leo/errors/commands/test.rs
deleted file mode 100644
index 5ba1914378..0000000000
--- a/leo/errors/commands/test.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-use std::ffi::OsString;
-
-#[derive(Debug, Error)]
-pub enum TestError {
- #[error("could not find main or library file in {:?}", _0)]
- ProgramFileDoesNotExist(OsString),
-}
diff --git a/leo/errors/updater.rs b/leo/errors/updater.rs
deleted file mode 100644
index e885b47cbb..0000000000
--- a/leo/errors/updater.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2019-2021 Aleo Systems Inc.
-// This file is part of the Leo library.
-
-// The Leo library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// The Leo library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with the Leo library. If not, see .
-
-#[derive(Debug, Error)]
-pub enum UpdaterError {
- #[error("{}: {}", _0, _1)]
- Crate(&'static str, String),
-
- #[error("The current version {} is more recent than the release version {}", _0, _1)]
- OldReleaseVersion(String, String),
-}
-
-impl From for UpdaterError {
- fn from(error: self_update::errors::Error) -> Self {
- tracing::error!("{}\n", error);
- UpdaterError::Crate("self_update", error.to_string())
- }
-}
diff --git a/leo/lib.rs b/leo/lib.rs
index d419917b72..5c1c7c74e2 100644
--- a/leo/lib.rs
+++ b/leo/lib.rs
@@ -14,15 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-#[macro_use]
-extern crate thiserror;
-
-pub mod cli;
-pub mod cli_types;
+pub mod api;
pub mod commands;
-#[cfg_attr(tarpaulin, skip)]
pub mod config;
-pub mod errors;
+pub mod context;
pub mod logger;
pub mod synthesizer;
pub mod updater;
+
+#[cfg(test)]
+mod tests;
diff --git a/leo/logger.rs b/leo/logger.rs
index 4873c7781f..c6da4abdd8 100644
--- a/leo/logger.rs
+++ b/leo/logger.rs
@@ -14,8 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use colored::Colorize;
use std::fmt;
+
+use colored::Colorize;
use tracing::{event::Event, subscriber::Subscriber};
use tracing_subscriber::{
fmt::{format::*, time::*, FmtContext, FormattedFields},
diff --git a/leo/main.rs b/leo/main.rs
index af03a4fb3b..6fe3cf92ac 100644
--- a/leo/main.rs
+++ b/leo/main.rs
@@ -14,80 +14,197 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use leo_lang::{cli::*, commands::*, errors::CLIError, logger, updater::Updater};
-
-use clap::{App, AppSettings, Arg};
-
-#[cfg_attr(tarpaulin, skip)]
-fn main() -> Result<(), CLIError> {
- let app = App::new("leo")
- .version(env!("CARGO_PKG_VERSION"))
- .about("Leo compiler and package manager")
- .author("The Aleo Team ")
- .settings(&[
- AppSettings::ColoredHelp,
- AppSettings::DisableHelpSubcommand,
- AppSettings::DisableVersion,
- ])
- .args(&[Arg::with_name("debug")
- .short("d")
- .long("debug")
- .help("Enables debugging mode")
- .global(true)])
- .subcommands(vec![
- NewCommand::new().display_order(0),
- InitCommand::new().display_order(1),
- BuildCommand::new().display_order(2),
- WatchCommand::new().display_order(3),
- TestCommand::new().display_order(4),
- SetupCommand::new().display_order(5),
- ProveCommand::new().display_order(6),
- RunCommand::new().display_order(7),
- LoginCommand::new().display_order(8),
- AddCommand::new().display_order(9),
- RemoveCommand::new().display_order(10),
- PublishCommand::new().display_order(11),
- DeployCommand::new().display_order(12),
- CleanCommand::new().display_order(13),
- LintCommand::new().display_order(14),
- UpdateCommand::new().display_order(15),
- LogoutCommand::new().display_order(16),
- ])
- .set_term_width(0);
-
- let mut help = app.clone();
- let arguments = app.get_matches();
-
- match arguments.subcommand() {
- ("new", Some(arguments)) => NewCommand::process(arguments),
- ("init", Some(arguments)) => InitCommand::process(arguments),
- ("build", Some(arguments)) => BuildCommand::process(arguments),
- ("watch", Some(arguments)) => WatchCommand::process(arguments),
- ("test", Some(arguments)) => TestCommand::process(arguments),
- ("setup", Some(arguments)) => SetupCommand::process(arguments),
- ("prove", Some(arguments)) => ProveCommand::process(arguments),
- ("run", Some(arguments)) => RunCommand::process(arguments),
- ("login", Some(arguments)) => LoginCommand::process(arguments),
- ("add", Some(arguments)) => AddCommand::process(arguments),
- ("remove", Some(arguments)) => RemoveCommand::process(arguments),
- ("publish", Some(arguments)) => PublishCommand::process(arguments),
- ("deploy", Some(arguments)) => DeployCommand::process(arguments),
- ("clean", Some(arguments)) => CleanCommand::process(arguments),
- ("lint", Some(arguments)) => LintCommand::process(arguments),
- ("update", Some(arguments)) => UpdateCommand::process(arguments),
- ("logout", Some(arguments)) => LogoutCommand::process(arguments),
- _ => {
- // Set logging environment
- match arguments.is_present("debug") {
- true => logger::init_logger("leo", 2),
- false => logger::init_logger("leo", 1),
- }
-
- Updater::print_cli();
-
- help.print_help()?;
- println!();
- Ok(())
+pub mod api;
+pub mod commands;
+pub mod config;
+pub mod context;
+pub mod logger;
+pub mod synthesizer;
+pub mod updater;
+
+use commands::{
+ package::{Add, Login, Logout, Publish, Remove},
+ Build,
+ Clean,
+ Command,
+ Deploy,
+ Init,
+ Lint,
+ New,
+ Prove,
+ Run,
+ Setup,
+ Test,
+ Update,
+ Watch,
+};
+
+use anyhow::Error;
+use std::process::exit;
+use structopt::{clap::AppSettings, StructOpt};
+
+/// CLI Arguments entry point - includes global parameters and subcommands
+#[derive(StructOpt, Debug)]
+#[structopt(name = "leo", author = "The Aleo Team ", setting = AppSettings::ColoredHelp)]
+struct Opt {
+ #[structopt(short, long, help = "Print additional information for debugging")]
+ debug: bool,
+
+ #[structopt(short, long, help = "Suppress CLI output")]
+ quiet: bool,
+
+ #[structopt(subcommand)]
+ command: CommandOpts,
+}
+
+///Leo compiler and package manager
+#[derive(StructOpt, Debug)]
+#[structopt(setting = AppSettings::ColoredHelp)]
+enum CommandOpts {
+ #[structopt(about = "Create a new Leo package in an existing directory")]
+ Init {
+ #[structopt(flatten)]
+ command: Init,
+ },
+
+ #[structopt(about = "Create a new Leo package in a new directory")]
+ New {
+ #[structopt(flatten)]
+ command: New,
+ },
+
+ #[structopt(about = "Compile the current package as a program")]
+ Build {
+ #[structopt(flatten)]
+ command: Build,
+ },
+
+ #[structopt(about = "Run a program setup")]
+ Setup {
+ #[structopt(flatten)]
+ command: Setup,
+ },
+
+ #[structopt(about = "Run the program and produce a proof")]
+ Prove {
+ #[structopt(flatten)]
+ command: Prove,
+ },
+
+ #[structopt(about = "Run a program with input variables")]
+ Run {
+ #[structopt(flatten)]
+ command: Run,
+ },
+
+ #[structopt(about = "Clean the output directory")]
+ Clean {
+ #[structopt(flatten)]
+ command: Clean,
+ },
+
+ #[structopt(about = "Watch for changes of Leo source files")]
+ Watch {
+ #[structopt(flatten)]
+ command: Watch,
+ },
+
+ #[structopt(about = "Update Leo to the latest version")]
+ Update {
+ #[structopt(flatten)]
+ command: Update,
+ },
+
+ #[structopt(about = "Compile and run all tests in the current package")]
+ Test {
+ #[structopt(flatten)]
+ command: Test,
+ },
+
+ #[structopt(about = "Install a package from the Aleo Package Manager")]
+ Add {
+ #[structopt(flatten)]
+ command: Add,
+ },
+
+ #[structopt(about = "Login to the Aleo Package Manager")]
+ Login {
+ #[structopt(flatten)]
+ command: Login,
+ },
+
+ #[structopt(about = "Logout of the Aleo Package Manager")]
+ Logout {
+ #[structopt(flatten)]
+ command: Logout,
+ },
+
+ #[structopt(about = "Publish the current package to the Aleo Package Manager")]
+ Publish {
+ #[structopt(flatten)]
+ command: Publish,
+ },
+
+ #[structopt(about = "Uninstall a package from the current package")]
+ Remove {
+ #[structopt(flatten)]
+ command: Remove,
+ },
+
+ #[structopt(about = "Lints the Leo files in the package (*)")]
+ Lint {
+ #[structopt(flatten)]
+ command: Lint,
+ },
+
+ #[structopt(about = "Deploy the current package as a program to the network (*)")]
+ Deploy {
+ #[structopt(flatten)]
+ command: Deploy,
+ },
+}
+
+fn main() {
+ // read command line arguments
+ let opt = Opt::from_args();
+
+ if !opt.quiet {
+ // init logger with optional debug flag
+ logger::init_logger("leo", match opt.debug {
+ false => 1,
+ true => 2,
+ });
+ }
+
+ handle_error(match opt.command {
+ CommandOpts::Init { command } => command.try_execute(),
+ CommandOpts::New { command } => command.try_execute(),
+ CommandOpts::Build { command } => command.try_execute(),
+ CommandOpts::Setup { command } => command.try_execute(),
+ CommandOpts::Prove { command } => command.try_execute(),
+ CommandOpts::Test { command } => command.try_execute(),
+ CommandOpts::Run { command } => command.try_execute(),
+ CommandOpts::Clean { command } => command.try_execute(),
+ CommandOpts::Watch { command } => command.try_execute(),
+ CommandOpts::Update { command } => command.try_execute(),
+
+ CommandOpts::Add { command } => command.try_execute(),
+ CommandOpts::Login { command } => command.try_execute(),
+ CommandOpts::Logout { command } => command.try_execute(),
+ CommandOpts::Publish { command } => command.try_execute(),
+ CommandOpts::Remove { command } => command.try_execute(),
+
+ CommandOpts::Lint { command } => command.try_execute(),
+ CommandOpts::Deploy { command } => command.try_execute(),
+ });
+}
+
+fn handle_error(res: Result) -> T {
+ match res {
+ Ok(t) => t,
+ Err(err) => {
+ eprintln!("Error: {}", err);
+ exit(1);
}
}
}
diff --git a/leo/synthesizer/serialized_circuit.rs b/leo/synthesizer/serialized_circuit.rs
index 6527a5277c..6f52560e1d 100644
--- a/leo/synthesizer/serialized_circuit.rs
+++ b/leo/synthesizer/serialized_circuit.rs
@@ -14,8 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::synthesizer::{CircuitSynthesizer, SerializedField, SerializedIndex};
+use std::convert::TryFrom;
+use serde::{Deserialize, Serialize};
use snarkvm_curves::bls12_377::Bls12_377;
use snarkvm_errors::curves::FieldError;
use snarkvm_models::{
@@ -23,8 +24,7 @@ use snarkvm_models::{
gadgets::r1cs::{ConstraintSystem, Index},
};
-use serde::{Deserialize, Serialize};
-use std::convert::TryFrom;
+use crate::synthesizer::{CircuitSynthesizer, SerializedField, SerializedIndex};
#[derive(Serialize, Deserialize)]
pub struct SerializedCircuit {
diff --git a/leo/synthesizer/serialized_field.rs b/leo/synthesizer/serialized_field.rs
index 6e1cbc9aa1..8c75dbae7e 100644
--- a/leo/synthesizer/serialized_field.rs
+++ b/leo/synthesizer/serialized_field.rs
@@ -14,12 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use snarkvm_errors::curves::FieldError;
-use snarkvm_models::curves::{Field, Fp256, Fp256Parameters};
+use std::{convert::TryFrom, str::FromStr};
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};
-use std::{convert::TryFrom, str::FromStr};
+use snarkvm_errors::curves::FieldError;
+use snarkvm_models::curves::{Field, Fp256, Fp256Parameters};
#[derive(Serialize, Deserialize)]
pub struct SerializedField(pub String);
diff --git a/leo/synthesizer/serialized_index.rs b/leo/synthesizer/serialized_index.rs
index 283309530c..029edef18a 100644
--- a/leo/synthesizer/serialized_index.rs
+++ b/leo/synthesizer/serialized_index.rs
@@ -14,9 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use snarkvm_models::gadgets::r1cs::Index;
-
use serde::{Deserialize, Serialize};
+use snarkvm_models::gadgets::r1cs::Index;
#[derive(Serialize, Deserialize)]
pub enum SerializedIndex {
diff --git a/leo/tests/mod.rs b/leo/tests/mod.rs
new file mode 100644
index 0000000000..9ecbf11522
--- /dev/null
+++ b/leo/tests/mod.rs
@@ -0,0 +1,130 @@
+// Copyright (C) 2019-2021 Aleo Systems Inc.
+// This file is part of the Leo library.
+
+// The Leo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// The Leo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with the Leo library. If not, see .
+
+use std::path::PathBuf;
+
+use anyhow::Result;
+
+use crate::{
+ commands::{
+ package::{Login, Logout},
+ Build,
+ Command,
+ Prove,
+ Run,
+ Setup,
+ Test,
+ Update,
+ UpdateAutomatic,
+ },
+ context::{create_context, Context},
+};
+
+/// Path to the only complex Leo program that we have
+/// - relative to source dir - where Cargo.toml is located
+const PEDERSEN_HASH_PATH: &str = "./examples/pedersen-hash/";
+
+#[test]
+pub fn build_pedersen_hash() -> Result<()> {
+ Build::new().apply(ctx()?, ())?;
+ Ok(())
+}
+
+#[test]
+pub fn setup_pedersen_hash() -> Result<()> {
+ let build = Build::new().apply(ctx()?, ())?;
+ Setup::new(false).apply(ctx()?, build.clone())?;
+ Setup::new(true).apply(ctx()?, build)?;
+ Ok(())
+}
+
+#[test]
+pub fn prove_pedersen_hash() -> Result<()> {
+ let build = Build::new().apply(ctx()?, ())?;
+ let setup = Setup::new(false).apply(ctx()?, build)?;
+ Prove::new(false).apply(ctx()?, setup.clone())?;
+ Prove::new(true).apply(ctx()?, setup)?;
+ Ok(())
+}
+
+#[test]
+pub fn run_pedersen_hash() -> Result<()> {
+ let build = Build::new().apply(ctx()?, ())?;
+ let setup = Setup::new(false).apply(ctx()?, build)?;
+ let prove = Prove::new(false).apply(ctx()?, setup)?;
+ Run::new(false).apply(ctx()?, prove.clone())?;
+ Run::new(true).apply(ctx()?, prove)?;
+ Ok(())
+}
+
+#[test]
+pub fn test_pedersen_hash() -> Result<()> {
+ let mut main_file = PathBuf::from(PEDERSEN_HASH_PATH);
+ main_file.push("src/main.leo");
+
+ Test::new(Vec::new()).apply(ctx()?, ())?;
+ Test::new(vec![main_file]).apply(ctx()?, ())?;
+ Ok(())
+}
+
+#[test]
+pub fn test_logout() -> Result<()> {
+ Logout::new().apply(ctx()?, ())?;
+ Ok(())
+}
+
+// Decided to not go all-in on error messages since they might change in the future
+// So this test only tells that error cases are errors
+#[test]
+pub fn login_incorrect_credentials_or_token() -> Result<()> {
+ // no credentials passed
+ let login = Login::new(None, None, None).apply(ctx()?, ());
+ assert!(login.is_err());
+
+ // incorrect token
+ let login = Login::new(Some("none".to_string()), None, None).apply(ctx()?, ());
+ assert!(login.is_err());
+
+ // only user, no pass
+ let login = Login::new(None, Some("user".to_string()), None).apply(ctx()?, ());
+ assert!(login.is_err());
+
+ // no user, only pass
+ let login = Login::new(None, None, Some("pass".to_string())).apply(ctx()?, ());
+ assert!(login.is_err());
+
+ Ok(())
+}
+
+#[test]
+pub fn leo_update_and_update_automatic() -> Result<()> {
+ Update::new(true, true, None).apply(ctx()?, ())?;
+ Update::new(false, true, None).apply(ctx()?, ())?;
+ Update::new(false, false, None).apply(ctx()?, ())?;
+
+ Update::new(false, false, Some(UpdateAutomatic::Automatic { value: true })).apply(ctx()?, ())?;
+ Update::new(false, false, Some(UpdateAutomatic::Automatic { value: false })).apply(ctx()?, ())?;
+
+ Ok(())
+}
+
+/// Create context for Pedersen Hash example
+fn ctx() -> Result {
+ let path = PathBuf::from(&PEDERSEN_HASH_PATH);
+ let ctx = create_context(path)?;
+
+ Ok(ctx)
+}
diff --git a/leo/updater.rs b/leo/updater.rs
index fe50b3ba90..cc5c8972da 100644
--- a/leo/updater.rs
+++ b/leo/updater.rs
@@ -13,8 +13,10 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see .
-use crate::{config::Config, errors::UpdaterError};
+use crate::config::Config;
+
+use anyhow::{anyhow, Result};
use colored::Colorize;
use self_update::{backends::github, version::bump_is_greater, Status};
@@ -27,7 +29,7 @@ impl Updater {
const LEO_REPO_OWNER: &'static str = "AleoHQ";
/// Show all available releases for `leo`.
- pub fn show_available_releases() -> Result<(), UpdaterError> {
+ pub fn show_available_releases() -> Result<()> {
let releases = github::ReleaseList::configure()
.repo_owner(Self::LEO_REPO_OWNER)
.repo_name(Self::LEO_REPO_NAME)
@@ -46,7 +48,7 @@ impl Updater {
}
/// Update `leo` to the latest release.
- pub fn update_to_latest_release(show_output: bool) -> Result {
+ pub fn update_to_latest_release(show_output: bool) -> Result {
let status = github::Update::configure()
.repo_owner(Self::LEO_REPO_OWNER)
.repo_name(Self::LEO_REPO_NAME)
@@ -62,7 +64,7 @@ impl Updater {
}
/// Check if there is an available update for `leo` and return the newest release.
- pub fn update_available() -> Result {
+ pub fn update_available() -> Result {
let updater = github::Update::configure()
.repo_owner(Self::LEO_REPO_OWNER)
.repo_name(Self::LEO_REPO_NAME)
@@ -76,7 +78,11 @@ impl Updater {
if bump_is_greater(¤t_version, &latest_release.version)? {
Ok(latest_release.version)
} else {
- Err(UpdaterError::OldReleaseVersion(current_version, latest_release.version))
+ Err(anyhow!(
+ "Old release version {} {}",
+ current_version,
+ latest_release.version
+ ))
}
}