Skip to content

Commit

Permalink
Add support for mutually exclusive arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmisiak committed Oct 29, 2021
1 parent 47c0461 commit eb6b8d5
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 86 deletions.
55 changes: 26 additions & 29 deletions src/command-parser/commandParser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArgumentParser } from 'argparse'
import { ArgumentGroup, ArgumentParser, SubParser } from 'argparse'
import { ParsedArguments } from '../types'
import { parserConfig } from './parserConfig'

Expand All @@ -16,36 +16,28 @@ export enum CommandType {
CATALYST_VOTING_KEY_REGISTRATION_METADATA = 'catalyst.voting-key-registration-metadata',
}

const makeParser = () => {
const initParser = (parser: ArgumentParser, config: any) => {
const isCommand = (str: string) => !str.startsWith('--')
const commandType = (parent: string, current: string) => (parent ? `${parent}.${current}` : current)
parser.set_defaults({ parser })
const subparsers = parser.add_subparsers()
const initParser = (parser: ArgumentParser | ArgumentGroup, config: any): void => {
const isMutuallyExclusiveGroup = (str: string) => str.startsWith('_mutually-exclusive-group')
const isOneOfGroupRequired = (str: string) => str.endsWith('-required')
const isCommand = (str: string) => !str.startsWith('--') && !isMutuallyExclusiveGroup(str)
const commandType = (parent: string, current: string) => (parent ? `${parent}.${current}` : current)

Object.keys(config).reduce((acc, key) => {
if (isCommand(key)) {
const subparser = acc.add_parser(key)
subparser.set_defaults({ command: commandType(parser.get_default('command'), key) })
initParser(subparser, config[key])
} else {
parser.add_argument(key, config[key])
}
return acc
}, subparsers)

return parser
}

return initParser(new ArgumentParser(
{
description: 'Command line tool for ledger/trezor transaction signing',
prog: 'cardano-hw-cli',
},
), parserConfig)
const subparsers = 'add_subparsers' in parser ? parser.add_subparsers() : null
Object.keys(config).forEach((key) => {
if (isCommand(key)) {
const subparser = (subparsers as SubParser).add_parser(key)
subparser.set_defaults({ command: commandType(parser.get_default('command'), key) })
initParser(subparser, config[key])
} else if (isMutuallyExclusiveGroup(key)) {
const group = parser.add_mutually_exclusive_group({ required: isOneOfGroupRequired(key) })
initParser(group, config[key])
} else {
parser.add_argument(key, config[key])
}
})
}

const preProcessArgs = (inputArgs: string[]) => {
const preProcessArgs = (inputArgs: string[]): string[] => {
// First 2 args are node version and script name
const commandArgs = inputArgs.slice(2)
if (commandArgs[0] === 'shelley') {
Expand All @@ -55,6 +47,11 @@ const preProcessArgs = (inputArgs: string[]) => {
}

export const parse = (inputArgs: string[]): { parser: ArgumentParser, parsedArgs: ParsedArguments } => {
const { parser, ...parsedArgs } = makeParser().parse_args(preProcessArgs(inputArgs))
const parser = new ArgumentParser({
description: 'Command line tool for ledger/trezor transaction signing',
prog: 'cardano-hw-cli',
})
initParser(parser, parserConfig)
const { ...parsedArgs } = parser.parse_args(preProcessArgs(inputArgs))
return { parser, parsedArgs }
}
44 changes: 26 additions & 18 deletions src/command-parser/parserConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ const opCertSigningArgs = {
},
}

// If you want to define a group of mutually exclusive CLI arguments (eg. see address.show below),
// bundle these arguments under a key prefixed with '_mutually-exclusive-group'. Furthermore, if you
// want argparse to ensure that one of the arguments is present, add '-required' suffix.

// based on cardano-cli interface
// https://docs.cardano.org/projects/cardano-node/en/latest/reference/cardano-node-cli-reference.html
export const parserConfig = {
Expand Down Expand Up @@ -161,25 +165,29 @@ export const parserConfig = {
'key-gen': keyGenArgs,

'show': { // hw-specific subpath
'--payment-path': {
type: (path: string) => parseBIP32Path(path),
dest: 'paymentPath',
help: 'Payment derivation path. (specify only path or script hash)',
},
'--payment-script-hash': {
type: (hashHex: string) => parseScriptHashHex(hashHex),
dest: 'paymentScriptHash',
help: 'Payment derivation script hash in hex format.',
},
'--staking-path': {
type: (path: string) => parseBIP32Path(path),
dest: 'stakingPath',
help: 'Stake derivation path. (specify only path or script hash)',
'_mutually-exclusive-group-payment-required': {
'--payment-path': {
type: (path: string) => parseBIP32Path(path),
dest: 'paymentPath',
help: 'Payment derivation path. Either this or payment script hash has to be specified.',
},
'--payment-script-hash': {
type: (hashHex: string) => parseScriptHashHex(hashHex),
dest: 'paymentScriptHash',
help: 'Payment derivation script hash in hex format.',
},
},
'--staking-script-hash': {
type: (hashHex: string) => parseScriptHashHex(hashHex),
dest: 'stakingScriptHash',
help: 'Stake derivation script hash in hex format',
'_mutually-exclusive-group-staking-required': {
'--staking-path': {
type: (path: string) => parseBIP32Path(path),
dest: 'stakingPath',
help: 'Stake derivation path. Either this or staking script hash has to be specified.',
},
'--staking-script-hash': {
type: (hashHex: string) => parseScriptHashHex(hashHex),
dest: 'stakingScriptHash',
help: 'Stake derivation script hash in hex format',
},
},
'--address-file': {
required: true,
Expand Down
16 changes: 3 additions & 13 deletions src/commandExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,10 @@ const CommandExecutor = async () => {
// eslint-disable-next-line no-console
const printDeviceVersion = async () => console.log(await cryptoProvider.getVersion())

const showAddress = async (
{
paymentPath, paymentScriptHash, stakingPath, stakingScriptHash, address,
}: ParsedShowAddressArguments,
) => {
const showAddress = async (args: ParsedShowAddressArguments) => {
// eslint-disable-next-line no-console
console.log(`address: ${address}`)
return cryptoProvider.showAddress(
paymentPath,
paymentScriptHash,
stakingPath,
stakingScriptHash,
address,
)
console.log(`address: ${args.address}`)
return cryptoProvider.showAddress(args)
}

const createSigningKeyFile = async (
Expand Down
10 changes: 4 additions & 6 deletions src/crypto-providers/ledgerCryptoProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import {
StakeCredentialType,
} from '../transaction/types'
import {
Address,
BIP32Path,
HexString,
HwSigningData,
Expand All @@ -46,6 +45,7 @@ import {
NativeScriptHashKeyHex,
NativeScriptType,
Network,
ParsedShowAddressArguments,
SigningMode,
SigningParameters,
VotePublicKeyHex,
Expand Down Expand Up @@ -93,11 +93,9 @@ export const LedgerCryptoProvider: () => Promise<CryptoProvider> = async () => {
): boolean => LEDGER_VERSIONS[feature] && isDeviceVersionGTE(deviceVersion, LEDGER_VERSIONS[feature])

const showAddress = async (
paymentPath: BIP32Path,
paymentScriptHash: string,
stakingPath: BIP32Path,
stakingScriptHash: string,
address: Address,
{
paymentPath, paymentScriptHash, stakingPath, stakingScriptHash, address,
}: ParsedShowAddressArguments,
): Promise<void> => {
try {
const { addressType, networkId, protocolMagic } = getAddressAttributes(address)
Expand Down
26 changes: 12 additions & 14 deletions src/crypto-providers/trezorCryptoProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {
TxSigned,
} from '../transaction/transaction'
import {
Address,
BIP32Path,
HexString,
HwSigningData,
Expand All @@ -49,6 +48,7 @@ import {
NativeScriptType,
SigningMode,
SigningParameters,
ParsedShowAddressArguments,
} from '../types'
import {
encodeAddress,
Expand Down Expand Up @@ -128,22 +128,20 @@ const TrezorCryptoProvider: () => Promise<CryptoProvider> = async () => {
): boolean => TREZOR_VERSIONS[feature] && isDeviceVersionGTE(deviceVersion, TREZOR_VERSIONS[feature])

const showAddress = async (
paymentPath: BIP32Path,
paymentScriptHash: string,
stakingPath: BIP32Path,
stakingScriptHash: string,
address: Address,
{
paymentPath, paymentScriptHash, stakingPath, stakingScriptHash, address,
}: ParsedShowAddressArguments,
): Promise<void> => {
const { addressType, networkId, protocolMagic } = getAddressAttributes(address)
const addressParameters = {
addressType,
path: paymentPath,
paymentScriptHash: paymentScriptHash || '',
stakingPath,
stakingScriptHash: stakingScriptHash || '',
}

const response = await TrezorConnect.cardanoGetAddress({
addressParameters,
addressParameters: {
addressType,
path: paymentPath || '',
paymentScriptHash: paymentScriptHash || '',
stakingPath: stakingPath || '',
stakingScriptHash: stakingScriptHash || '',
},
networkId,
protocolMagic,
showOnTrezor: true,
Expand Down
8 changes: 2 additions & 6 deletions src/crypto-providers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,19 @@ import {
HwSigningData,
BIP32Path,
Network,
Address,
XPubKeyHex,
VotePublicKeyHex,
NativeScript,
NativeScriptHashKeyHex,
NativeScriptDisplayFormat,
SigningParameters,
ParsedShowAddressArguments,
} from '../types'

export type CryptoProvider = {
getVersion: () => Promise<string>,
showAddress: (
paymentPath: BIP32Path,
paymentScriptHash: string,
stakingPath: BIP32Path,
stakingScriptHash: string,
address: Address,
args: ParsedShowAddressArguments,
) => Promise<void>,
signTx: (
params: SigningParameters,
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export type ParsedDeviceVersionArguments = {
command: CommandType.DEVICE_VERSION,
}

// make sure only one of paymentPath vs. paymentScriptHash and stakingPath vs. stakingScriptHash
// is present (the result of parse() complies to this)
export type ParsedShowAddressArguments = {
command: CommandType.SHOW_ADDRESS,
paymentPath: BIP32Path,
Expand Down

0 comments on commit eb6b8d5

Please sign in to comment.