Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up argument parsing. #5

Merged
merged 2 commits into from Aug 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
79 changes: 79 additions & 0 deletions rustfmt.toml
@@ -0,0 +1,79 @@
## This is copied from https://github.com/rust-bitcoin/rust-bitcoin/blob/master/rustfmt.toml

hard_tabs = false
tab_spaces = 4
newline_style = "Auto"
indent_style = "Block"

max_width = 100 # This is number of characters.
# `use_small_heuristics` is ignored if the granular width config values are explicitly set.
use_small_heuristics = "Max" # "Max" == All granular width settings same as `max_width`.
# # Granular width configuration settings. These are percentages of `max_width`.
# fn_call_width = 60
# attr_fn_like_width = 70
# struct_lit_width = 18
# struct_variant_width = 35
# array_width = 60
# chain_width = 60
# single_line_if_else_max_width = 50

wrap_comments = false
format_code_in_doc_comments = false
comment_width = 100 # Default 80
normalize_comments = false
normalize_doc_attributes = false
format_strings = false
format_macro_matchers = false
format_macro_bodies = true
hex_literal_case = "Preserve"
empty_item_single_line = true
struct_lit_single_line = true
fn_single_line = true # Default false
where_single_line = false
imports_indent = "Block"
imports_layout = "Mixed"
imports_granularity = "Module" # Default "Preserve"
group_imports = "StdExternalCrate" # Default "Preserve"
reorder_imports = true
reorder_modules = true
reorder_impl_items = false
type_punctuation_density = "Wide"
space_before_colon = false
space_after_colon = true
spaces_around_ranges = false
binop_separator = "Front"
remove_nested_parens = true
combine_control_expr = true
overflow_delimited_expr = false
struct_field_align_threshold = 0
enum_discrim_align_threshold = 0
match_arm_blocks = false # Default true
match_arm_leading_pipes = "Never"
force_multiline_blocks = false
fn_args_layout = "Tall"
brace_style = "SameLineWhere"
control_brace_style = "AlwaysSameLine"
trailing_semicolon = true
trailing_comma = "Vertical"
match_block_trailing_comma = false
blank_lines_upper_bound = 1
blank_lines_lower_bound = 0
edition = "2018"
version = "One"
inline_attribute_width = 0
format_generated_files = true
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true
condense_wildcard_suffixes = false
color = "Auto"
required_version = "1.5.1"
unstable_features = false
disable_all_formatting = false
skip_children = false
hide_parse_errors = false
error_on_line_overflow = false
error_on_unformatted = false
emit_mode = "Files"
make_backup = false
73 changes: 73 additions & 0 deletions src/args.rs
@@ -0,0 +1,73 @@
use std::{ffi::OsString, fmt, num::ParseIntError};

use crate::{ScheduledChannel, ScheduledPayJoin};

/// CLI argument errors.
#[derive(Debug)]
pub(crate) enum ArgError {
/// Argument not UTF-8
NotUTF8(OsString),
/// Parse feerate error
FeeRateError(ParseIntError),
/// Parse node address error
InvalidNodeAddress(ln_types::p2p_address::ParseError),
/// Parse bitcoin amount error
InvalidBitcoinAmount(bitcoin::util::amount::ParseAmountError),
/// Wallet amount error
InvalidWalletAmount(bitcoin::util::amount::ParseAmountError),
}

impl fmt::Display for ArgError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Do this properly.
write!(f, "invalid arguments: {:?}", self)
}
}

impl std::error::Error for ArgError {}

/// Parses arguments in `[fee_rate] [(<p2p_addr>, <sats_amount>)...] [wallet_amount]`
pub(crate) fn parse_args<A>(args: A) -> Result<Option<ScheduledPayJoin>, ArgError>
where
A: Iterator<Item = OsString>,
{
// ensure all args are utf8
let args = args
.map(|arg| arg.into_string())
.collect::<Result<Vec<_>, _>>()
.map_err(ArgError::NotUTF8)?;

// first argument is fee rate
let fee_rate = match args.get(0) {
Some(fee_rate_str) => fee_rate_str.parse::<u64>().map_err(ArgError::FeeRateError)?,
None => return Ok(None),
};

// return None if no remaining args.
let mut args = match args.get(1..) {
Some(args) => args.iter(),
None => return Ok(None),
};

// parse scheduled channel arguments: pairs of (addr, amount)
let mut channels = Vec::with_capacity(args.len() / 2);

// the remaining single argument is the wallet amount in satoshis (if any)
let wallet_amount = loop {
match (args.next(), args.next()) {
// we have a pair of arguments, interpret it as a scheduled channel (p2p addr, amount)
(Some(addr), Some(amount)) => {
channels.push(ScheduledChannel::from_args(addr, amount)?);
}
// if there is a remaining single argument, it is the wallet amount
(Some(amount), None) => {
break bitcoin::Amount::from_str_in(amount, bitcoin::Denomination::Satoshi)
.map_err(ArgError::InvalidWalletAmount)?
}
// if there is no remaining single argument, the wallet amount is 0
_ => break bitcoin::Amount::ZERO,
}
};

Ok(Some(ScheduledPayJoin { wallet_amount, channels, fee_rate }))
}