CLI design library. ESModule
npm install @nutsloop/ivy-input___
###### Ivy Framework to build CLI applications. esModule.
___
___
- Description
- Installation
- Usage
- simple implementation in one file
___
___
Framework to build CLI applications:
- spawn new thread for complex operation.
- [help][-h][--help] commands automatic generation.
- [version][-v][--version] commands automatic generation.
- callbacks and callbacks rest-parameters for commands, global flags and flags.
- callbacks can be async or sync for all commands, global flags and flags.
- callback for flags can return a value, that will be passed to the command callback.
- globals callback DO NOT return any value to the command and are executed before everything else.
- good for setting environment variables.
- pass extra data to callbacks for all commands, global flags and flags.
- flags and global-flags features:
- --flag=3 style.
- infer type from the value passed to flags & global-flags.
- string number boolean array kvp json
- flags callbacks can be prioritized.
- flags can be mandatory.
- global-flags must be called before the command.
- flags and global flags can have conflicts.
- accept key->value pair (kvp) as argument --flag='!key:value|key2:value2' exclamation mark is mandatory.
___
with this command you can initialize a boilerplate project.
``shell`
npx @nutsloop/ivy-input init \
--name='cli-app' \
--semver='1.0.0' \
--description='another cli app'
for a more detailed information, have a look at the ivyrun/input-boilerplate
with this command you can install the package and develop 'from scratch' the project.
`shell`
npm install @nutsloop/ivy-input
___
initialize a simple JavaScript project.
`shell
mkdir ./hello-world && cd ./hello-world
npm init -y
npm pkg set type="module"; # mandatory to specify esModule
npm pkg set name="hello-world"; # let's name the project
npm pkg set version="1.0.0"; # let's give it a version
npm pkg set description="another hello world cli app"; # let's give it a description
npm add @nutsloop/ivy-input # install the package
touch ./index.js
`
___
> ⚠ remember to give index.js file executable permission
`shell`
chmod u+x ./index.js
___
file index.js
This hello-world cli application has
- one command called say-it and--silent
- one flag called
It doesn't do anything special, it just prints a message to the console.
If the --silent flag is passed, the message will not be printed.
___
`javascript
#!/usr/bin/env -S node
import { cli, run, flag, command } from '@nutsloop/ivy-input';
// the logic function where all the commands and flags are defined.
async function logic(parsed_argv) {
// set the SILENCED environment variable to false.
process.env.SILENT = 'false'
await command('say-it', {
description: 'print the message "hello world!".',
usage: 'hello-world say-it',
has_flag: true,
cb: () => {
if(process.env.SILENT === 'false'){
console.log('hello world!');
}
}
});
await flag('--silent', {
alias: 'silent',
description: 'do not print the hello world message.',
usage: 'hello-world say-it --silent',
is_flag_of: 'say-it',
cb: {
fn: () => {
process.env.SILENT = 'true';
},
type: 'sync'
}
});
// cli method will be called when the run method parses the process.argvrun
await cli(parsed_argv);
}
// the method will parse the process.argv and call the logic function.`
// no need to remove the first two elements of the process.argv.
await run(process.argv, logic, 'hello-world');
initialize a simple JavaScript project.
`shell
mkdir ./read-that && cd ./read-that
npm init -y
npm pkg set type="module"; # mandatory to specify esModule
npm pkg set name="read-that"; # let's name the project
npm pkg set version="1.0.0"; # let's give it a version
npm pkg set description="read a Json file. and log the content."; # let's give it a description
npm add @nutsloop/ivy-input # install the package
echo '{"some": "Jason", "data":["0", "1"]}' >> ./data.json
touch ./index.js
`
___
> ⚠ remember to give index.js file executable permission
`shell`
chmod u+x ./index.js
___
file index.js
This read-that cli application has
- one command called data and--filename[-f]
- one flag called
It will read a json file and log the content to the console.
___
`javascript
#!/usr/bin/env -S node
import { access, readFile } from 'node:fs/promises'
import { command, run, cli, flag } from '@nutsloop/ivy-input'
import { extname, resolve } from 'node:path'
// The very entry of the read-that cli application.
const logic = async (parsed_argv) => {
// The read command callback function
const read_cb = async (data) => {
let exitCode = 0
// filename from the given flag is now absolute path.
const filename = data.get('filename');
// It reads the file, and if the readFile fails, it returns a json string with the error message given.
const content = await readFile(filename, { encoding: 'utf-8' }).catch(error => {
exitCode = 1
return {"error":"${ error.message }"}
})
// It converts to object the give json data.
let json_data
try{
json_data = JSON.parse(content)
}catch ( error ) {
json_data = {"error":"${error.message}"}
exitCode = 1
}
console.log(json_data)
process.exit(exitCode)
};
await command('data', {
description: 'read a Json file given from the flag --filename[-f]',
usage: npx read-that data --filename='data.json',required_flag
has_flag: true,
// to use the property, you need to use the alias given to the flag while defining it.
required_flag: [ 'filename' ],
cb: read_cb
});
// the filename callback function
const filename_cb = async (data) => {
// only json files are allowed
if(extname(data) !== '.json'){
console.error('only json file')
process.exit(1)
}
// check if the file exists
const file_exists = await access(resolve(process.cwd(), data)).then(() => true).catch(() => false)
if(!file_exists){
console.error('file does not exist')
process.exit(1)
}
return resolve(process.cwd(), data)
};
await flag(['--filename', '-f'], {
alias: 'filename',
description: the filename of the json file to read
ONLY relative path to the current working dir of the app, ⚠️ no initial slash or dot needed!,npx read-that --filename='data.json'
usage: ,
is_flag_of: 'data',
cb: {
type: 'async',
fn: filename_cb
},
type: 'string'
});
// cli method will be called when the run method parses the process.argv
await cli(parsed_argv).catch(console.error);
};
// - the run method will parse the process.argv and call the logic function.
await run(process.argv, logic, 'read-that').catch(console.error);
`
the Command class automatically creates two commands:
- help:
to access the doc entries, you need to pass:
- key->value (opts) to the --view flag to retrieve the doc for the flag
where key is the command-name & value is the flag-name./index.js help --view=read:--filename
- example to retrieve the flag doc./index.js help --view=read
- just the name of the command to the --view flag to retrieve the doc for the command
- example to retrieve the command doc
- version:
the command version relays to the package.json file entry version
let's run the app and all its functionalities
`shellusage
./index.js read --filename='test.json' # will print the object
./index.js read --filename='test.js' # will print 'only json files'
./index.js read --filename='no-file.json' # will print the ENOENT error message
``
___