Skip to content

Commit 52ca774

Browse files
authored
Merge pull request #90 from AElfProject/feature/csv
feat: csv
2 parents 1d91276 + c6e1d8f commit 52ca774

File tree

16 files changed

+128
-36
lines changed

16 files changed

+128
-36
lines changed

.github/workflows/coverage.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ on:
44
push:
55
branches:
66
- master
7-
pull_request:
8-
branches:
9-
- master
107

118
jobs:
129
test:

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,17 @@
1111
</a>
1212
</p>
1313

14-
| Branch | Tests | Coverage |
15-
|--------------|-----------------|----------------|
14+
| Branch | Tests | Coverage |
15+
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
1616
| `master` | ![Tests](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/AElfProject/aelf-command/feature/badge-json/master-test-results.json) | ![Coverage](https://AElfProject.github.io/aelf-command/badges.svg) |
1717

18-
19-
20-
2118
## Descriptions
2219

2320
_A CLI tools built for AElf_
2421

2522
## Features
2623

27-
- Get or Set common configs, `endpoint`, `account`, `datadir`, `password`.
24+
- Get or Set common configs, `endpoint`, `account`, `datadir`, `password`, `csv`.
2825
- For new users who are not familiar with the CLI parameters, any missing parameters will be asked in a prompting way.
2926
- Create a new `account`.
3027
- Load an account from a given `private key` or `mnemonic`.
@@ -157,6 +154,7 @@ Options:
157154
-a, --account <account> The address of AElf wallet
158155
-p, --password <password> The password of encrypted keyStore
159156
-d, --datadir <directory> The directory that contains the AElf related files. Defaults to {home}/.local/share/aelf
157+
-c, --csv <csv> The location of the CSV file containing the parameters.
160158
-h, --help output usage information
161159

162160
Commands:
@@ -218,6 +216,7 @@ aelf-command console
218216
- `endpoint`: The endpoint for the RPC service.
219217
- `account`: The account to be used to interact with the blockchain `endpoint`.
220218
- `password`: The password for unlocking the given `account`.
219+
- `csv>`: The location of the CSV file containing the parameters.
221220
222221
You can specified options above in several ways, and the priority is in the order of low to high.
223222

jest.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default {
3333
coveragePathIgnorePatterns: ['/node_modules/', '/src/utils/constants.js', '/src/command/index.js'],
3434

3535
// A list of reporter names that Jest uses when writing coverage reports
36-
coverageReporters: ['text', 'json-summary'],
36+
coverageReporters: ['text', 'json-summary', 'html'],
3737

3838
// An object that configures minimum threshold enforcement for coverage results
3939
// coverageThreshold: null,
@@ -146,7 +146,7 @@ export default {
146146

147147
// The glob patterns Jest uses to detect test files
148148
testMatch: [
149-
// '**/test/utils/Logger.test.js'
149+
// '**/test/utils/utils.test.js'
150150
'**/test/command/dappServer/socket-sign.test.js',
151151
'**/test/**/?(*.)+(spec|test).[jt]s?(x)'
152152
// "**/?(*.)+(spec|test).[tj]s?(x)"

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "aelf-command",
3-
"version": "0.1.48",
3+
"version": "0.1.49-beta.0",
44
"description": "A CLI tools for AElf",
55
"main": "src/index.js",
66
"type": "module",
@@ -56,6 +56,7 @@
5656
"check-node-version": "^4.2.1",
5757
"columnify": "^1.6.0",
5858
"commander": "^12.1.0",
59+
"csv-parser": "^3.0.0",
5960
"elliptic": "^6.5.5",
6061
"inquirer": "^9.2.22",
6162
"inquirer-date-prompt": "^3.0.0",

src/command/baseSubCommand.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import inquirer from 'inquirer';
66
import ora from 'ora';
77
import { logger } from '../utils/myLogger.js';
88
import { camelCase } from '../utils/utils.js';
9-
import { globalOptionsPrompts, strictGlobalOptionValidatorDesc } from '../utils/constants.js';
9+
import { commonGlobalOptionValidatorDesc, globalOptionsPrompts, strictGlobalOptionValidatorDesc } from '../utils/constants.js';
1010

1111
// Schema.warning = () => {}; // TypeError: Cannot add property warning, object is not extensible
1212

@@ -123,7 +123,7 @@ class BaseSubCommand {
123123
*/
124124
static getUniConfig(commander) {
125125
const result = {};
126-
['password', 'endpoint', 'account', 'datadir'].forEach(v => {
126+
Object.keys(commonGlobalOptionValidatorDesc).forEach(v => {
127127
const options = commander.opts();
128128
if (options[v]) {
129129
result[v] = options[v];

src/command/call.js

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import AElf from 'aelf-sdk';
22
import inquirer from 'inquirer';
33
import chalk from 'chalk';
4+
import { createReadStream } from 'fs';
5+
import csv from 'csv-parser';
46
import BaseSubCommand from './baseSubCommand.js';
57
import { callCommandUsages, callCommandParameters } from '../utils/constants.js';
68
import {
@@ -9,7 +11,8 @@ import {
911
getMethod,
1012
promptTolerateSeveralTimes,
1113
getParams,
12-
parseJSON
14+
parseJSON,
15+
parseCSV
1316
} from '../utils/utils.js';
1417
import { getWallet } from '../utils/wallet.js';
1518
import { logger } from '../utils/myLogger.js';
@@ -64,6 +67,19 @@ class CallCommand extends BaseSubCommand {
6467
return contractAddress;
6568
}
6669

70+
/**
71+
* Calls a method with specified parameters.
72+
* @param {any} method The method to call.
73+
* @param {any} params The parameters for the method call.
74+
* @returns {Promise<any>} A promise that resolves with the result of the method call.
75+
*/
76+
async showRes(method, params) {
77+
const result = await this.callMethod(method, params);
78+
// @ts-ignore
79+
logger.info(`\nResult:\n${JSON.stringify(result, null, 2)}`);
80+
this.oraInstance.succeed('Succeed!');
81+
}
82+
6783
/**
6884
* Runs the command.
6985
* @param {Command} commander The Commander instance.
@@ -75,7 +91,7 @@ class CallCommand extends BaseSubCommand {
7591
// @ts-ignore
7692
const { options, subOptions } = await super.run(commander, ...args);
7793
const subOptionsLength = Object.keys(subOptions).length;
78-
const { endpoint, datadir, account, password } = options;
94+
const { endpoint, datadir, account, password, csv } = options;
7995
const aelf = new AElf(new AElf.providers.HttpProvider(endpoint));
8096
try {
8197
let { contractAddress, method, params } = subOptions;
@@ -109,13 +125,16 @@ class CallCommand extends BaseSubCommand {
109125
break;
110126
case 'params':
111127
contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
112-
113128
method = getMethod(method, contractAddress);
114-
115-
params = await getParams(method);
116-
params = typeof params === 'string' ? params : BaseSubCommand.normalizeConfig(params);
117-
if (Object.keys(params || {}).length > 0) {
118-
console.log(chalk.hex('#3753d3')(`The params you entered is:\n${JSON.stringify(params, null, 2)}`));
129+
if (csv) {
130+
const csvParams = await parseCSV(csv);
131+
params = csvParams;
132+
} else {
133+
params = await getParams(method);
134+
params = typeof params === 'string' ? params : BaseSubCommand.normalizeConfig(params);
135+
if (Object.keys(params || {}).length > 0) {
136+
console.log(chalk.hex('#3753d3')(`The params you entered is:\n${JSON.stringify(params, null, 2)}`));
137+
}
119138
}
120139
break;
121140
default:
@@ -124,15 +143,23 @@ class CallCommand extends BaseSubCommand {
124143
}
125144
}
126145
contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
127-
params = parseJSON(params);
146+
if (Array.isArray(params)) {
147+
params.forEach(param => parseJSON(param));
148+
} else {
149+
params = parseJSON(params);
150+
}
151+
128152
method = getMethod(method, contractAddress);
129153
if (method.inputTypeInfo && (Object.keys(method.inputTypeInfo.fields).length === 0 || !method.inputTypeInfo.fields)) {
130154
params = '';
131155
}
132-
const result = await this.callMethod(method, params);
133-
// @ts-ignore
134-
logger.info(`\nResult:\n${JSON.stringify(result, null, 2)}`);
135-
this.oraInstance.succeed('Succeed!');
156+
if (Array.isArray(params)) {
157+
for (const param of params) {
158+
await this.showRes(method, param);
159+
}
160+
} else {
161+
await this.showRes(method, params);
162+
}
136163
} catch (e) {
137164
this.oraInstance.fail('Failed!');
138165
// @ts-ignore

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ function init(options) {
5151
commander.option('-e, --endpoint <URI>', 'The URI of an AElf node. Eg: http://127.0.0.1:8000');
5252
commander.option('-a, --account <account>', 'The address of AElf wallet');
5353
commander.option('-p, --password <password>', 'The password of encrypted keyStore');
54-
5554
commander.option(
5655
'-d, --datadir <directory>',
5756
`The directory that contains the AElf related files. Default to be ${userHomeDir}/aelf`
5857
);
58+
commander.option('-c, --csv <csv>', 'The location of the CSV file containing the parameters.');
5959
const rc = new RC();
6060
Object.values(commands).forEach(Value => {
6161
const command = new Value(rc);

src/utils/constants.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,11 @@ const commonGlobalOptionValidatorDesc = {
366366
type: 'string',
367367
required: false,
368368
message: 'set a valid account address in global config file or passed by -a <address>'
369+
},
370+
csv: {
371+
type: 'string',
372+
required: false,
373+
message: 'set params in csv file by -c <csv>'
369374
}
370375
};
371376

@@ -374,8 +379,8 @@ const strictGlobalOptionValidatorDesc = /**@type {CommonGlobalOptionValidatorDes
374379
// @ts-ignore
375380
Object.entries(commonGlobalOptionValidatorDesc).forEach((/** @type {[CommonGlobalOptionKey, any]} */ [key, value]) => {
376381
strictGlobalOptionValidatorDesc[key] = {
377-
...value,
378-
required: true
382+
...value
383+
// required: true
379384
};
380385
});
381386

src/utils/utils.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import _camelCase from 'camelcase';
99
import inquirer from 'inquirer';
1010
import { plainLogger } from './myLogger.js';
1111
import protobuf from '@aelfqueen/protobufjs';
12+
import { createReadStream } from 'fs';
13+
import csv from 'csv-parser';
1214
const { load } = protobuf;
1315

1416
/**
@@ -462,6 +464,23 @@ async function deserializeLogs(aelf, logs = []) {
462464
return results;
463465
}
464466

467+
const parseCSV = async address => {
468+
let results = [];
469+
const stream = createReadStream(address).pipe(csv());
470+
for await (const data of stream) {
471+
const cleanData = {};
472+
for (const key in data) {
473+
const cleanKey = key.replace(/\n/g, '').trim();
474+
const cleanValue = typeof data[key] === 'string' ? data[key].replace(/\n/g, '').trim() : data[key];
475+
if (cleanValue !== '') {
476+
cleanData[cleanKey] = cleanValue;
477+
}
478+
}
479+
Object.keys(cleanData).length && results.push(cleanData);
480+
}
481+
return results;
482+
};
483+
465484
export {
466485
promisify,
467486
camelCase,
@@ -474,5 +493,6 @@ export {
474493
parseJSON,
475494
randomId,
476495
getParams,
477-
deserializeLogs
496+
deserializeLogs,
497+
parseCSV
478498
};

test/command/call.test.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { callCommandUsages, callCommandParameters } from '../../src/utils/consta
77
import { getContractInstance } from '../../src/utils/utils.js';
88
import { userHomeDir } from '../../src/utils/userHomeDir.js';
99
import { logger } from '../../src/utils/myLogger.js';
10-
import { endpoint as endPoint, account, password, dataDir } from '../constants.js';
10+
import { endpoint as endPoint, account, password, dataDir, csvDir } from '../constants.js';
1111

1212
const sampleRc = { getConfigs: jest.fn() };
1313
jest.mock('../../src/utils/myLogger');
@@ -102,6 +102,26 @@ describe('CallCommand', () => {
102102
expect(logger.info).toHaveBeenCalled();
103103
});
104104

105+
test('should run with csv', async () => {
106+
inquirer.prompt = questions =>
107+
Promise.resolve({
108+
symbol: 'ELF',
109+
owner: 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'
110+
});
111+
const commander = new Command();
112+
commander.option('-e, --endpoint <URI>', 'The URI of an AElf node. Eg: http://127.0.0.1:8000');
113+
commander.option('-a, --account <account>', 'The address of AElf wallet');
114+
commander.option('-p, --password <password>', 'The password of encrypted keyStore');
115+
commander.option(
116+
'-d, --datadir <directory>',
117+
`The directory that contains the AElf related files. Default to be ${userHomeDir}/aelf`
118+
);
119+
commander.option('-c, --csv <csv>', 'The location of the CSV file containing the parameters.');
120+
commander.parse([process.argv[0], '', 'call', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir, '-c', csvDir]);
121+
await callCommand.run(commander, 'AElf.ContractNames.Token', 'GetBalance');
122+
expect(logger.info).toHaveBeenCalled();
123+
});
124+
105125
test('should run with invalid parameters', async () => {
106126
inquirer.prompt = backup;
107127
callCommand = new CallCommand(sampleRc, 'call', 'Call a read-only method on a contract.', [

0 commit comments

Comments
 (0)