Skip to content

Instantly share code, notes, and snippets.

@80sVectorz
Last active June 1, 2023 12:58
Show Gist options
  • Save 80sVectorz/606c4afd91e06d852393d4a66392646a to your computer and use it in GitHub Desktop.
Save 80sVectorz/606c4afd91e06d852393d4a66392646a to your computer and use it in GitHub Desktop.
Library script to support complex command line argument parsing.
/*
-- Parse Goblin --
VERSION 1.0
Bitburner script: https://github.com/bitburner-official/bitburner-src https://store.steampowered.com/app/1812820/Bitburner/
Library script to support complex command line argument parsing.
Features:
- full names with optional short forms: foo --bar / foo -b
- value argument: count --target 10
- boolean flags: cool_script --help
- group arguments: modify_targets --add n00dles foodnstuff
- and required arguments.
Install with the following path: /lib/parse_goblin.js
Created by 80sVectorz: https://gist.github.com/80sVectorz/606c4afd91e06d852393d4a66392646a
*/
export class Param{
constructor(long,defaultValue,{short=null,isFlag=false,isGrouped=false,isRequired=false}={}) {
/*
long: The long for param name E.G "param_a" which would trigger with --param_a ...
defaultValue: The default value for this parameter
*short: The short form param name E.G 'a' which would trigger with -a ...
*isFlag: If the param is a flag that does not need a specified value E.G --help
This would return the flipped default value of the param if included:
foo --help
returns: {"help":true}
foo
returns: {"help":false}
*isGrouped: This allows the param to recieve a array of values E.G count2 --range 1 10
returns: {"range":[1,10]}
*isRequired: If the param is required. An error message will show when it is missing
*/
if(typeof long != "string"){
throw Error("Argument long needs to be a string");
}
if(typeof short != "string" && short!=null){
throw Error("Argument short needs to be either a string or null");
}
if(typeof isFlag!="boolean"){
throw Error("Type of argument isFlag has to be a boolean");
}
if(typeof isGrouped!="boolean"){
throw Error("Type of argument isGrouped has to be a boolean");
}
if(typeof isRequired!="boolean"){
throw Error("Type of argument isRequired has to be a boolean");
}
if(short.length!=1){
throw Error("Argument short has to be a single character string");
}
if(isFlag && isGrouped) {
throw Error("Param can not be a flag and group at the same time.");
}
if(isFlag && typeof defaultValue!="boolean") {
throw Error("Param has to be of type bool to use isFlag");
}
if(!isGrouped && !["string","number","boolean"].includes(typeof defaultValue)){
throw Error("Type of argument defaultValue has to be one of: string, number or boolean");
} else if (isGrouped && !Array.isArray(defaultValue)) {
throw Error("Argument defaultValue has to be an array because to use isGrouped");
}
if (isGrouped){
let groupType = null;
for(var i=0;i<defaultValue.length;i++){
if(groupType && typeof defaultValue[i] != groupType){
throw Error("Types of grouped default value elements can not be mixed");
}
if(!["string","number","boolean"].includes(typeof defaultValue[i])){
throw Error("Type of argument grouped defaultValue element has to be one of: string, number or boolean");
} else if (!groupType){
groupType=typeof defaultValue[i];
}
}
}
this.long = long;
this.short = short;
this.defaultValue = defaultValue;
this.isFlag = isFlag;
this.isGrouped = isGrouped;
this.isRequired = isRequired;
}
}
export class Schema{
constructor(params,param_side){
if(!Array.isArray(params)){
throw Error("argument params has to be an array");
}
for(var i=0;i<params.length;i++){
if(!(params[i] instanceof Param)){
throw Error("argument params can only contain param instances");
}
}
this.params = params;
}
find(key){
for(var i=0;i<this.params.length;i++){
if(this.params[i].long == key || this.params[i].short == key){
return i;
}
}
return -1;
}
longOf(key){
return this.params[key].long;
}
shortOf(key){
return this.params[key].short;
}
defaultValueOf(key){
return this.params[key].defaultValue;
}
isFlagOf(key){
return this.params[key].isFlag;
}
isGroupedOf(key){
return this.params[key].isGrouped;
}
isRequiredOf(key){
return this.params[key].isRequired;
}
}
/** @param {NS} ns */
export function parse(ns,schema) {
let parsedParams = {};
let missingParams = new Set();
let passedArgs = ns.args;
for(var i = 0;i<schema.params.length;i++){
parsedParams[schema.longOf(i)] = schema.defaultValueOf(i);
if (schema.isRequiredOf(i)){
missingParams.add(schema.longOf(i));
}
}
let inGroup = false;
let openKey = -1;
for(var i = 0;i<passedArgs.length;i++){
var segment = passedArgs[i];
segment = segment.toString();
if (openKey!=-1 && inGroup && (segment.startsWith("--") || (segment.startsWith("-") && segment.length==2))){
inGroup=false;
missingParams.delete(schema.longOf(openKey));
openKey=-1;
segment = passedArgs[i];
}
segment = passedArgs[i];
if(openKey==-1){
switch(typeof segment){
case "string" :
if (segment.startsWith("--")){
var key = schema.find(segment.slice(2));
if(key!=-1){
if (schema.isFlagOf(key)){
parsedParams[schema.longOf(key)] = !schema.defaultValueOf(key);
break;
}
if (schema.isGroupedOf(key)){
inGroup=true;
parsedParams[schema.longOf(key)] = [];
}
openKey = key;
break;
} else {
ns.tprint(`Found invalid argument: ${segment}`);
ns.exit();
}
} else if (segment.startsWith("-")){
if(segment.length == 2){
var key = schema.find(segment.slice(1));
if(key!=-1){
if (schema.isFlagOf(key)){
parsedParams[schema.longOf(key)] = !schema.defaultValueOf(key);
break;
}
if (schema.isGroupedOf(key)){
inGroup=true;
parsedParams[schema.longOf(key)] = [];
}
openKey = key;
break;
}
}
ns.tprint(`Found invalid argument: ${segment}`);
ns.exit();
}
default:
if(inGroup){
break;
}
ns.tprint(`Recieved unexpected ${typeof segment}: ${segment}`);
ns.exit();
}
}
else {
segment = passedArgs[i];
if (typeof schema.defaultValueOf(openKey) == typeof segment || inGroup){
if(inGroup){
if (typeof segment == typeof schema.defaultValueOf(openKey)[0]){
parsedParams[schema.longOf(openKey)].push(segment);
} else {
ns.tprint(`Recieved incorrect value type for element of group ${schema.longOf(openKey)}: got ${typeof segment} expected ${typeof schema.defaultValueOf(openKey)[0]}`);
ns.exit();
}
} else {
parsedParams[schema.longOf(openKey)] = segment;
}
if(!inGroup){
openKey = -1;
}
} else {
ns.tprint(`Recieved incorrect value type for ${schema.longOf(openKey)}: got ${typeof segment} expected ${typeof schema.defaultValueOf(openKey)}`);
ns.exit();
}
}
}
if(missingParams.length > 0){
if(missingParams.length == 1){
ns.tprint(`Missing the following required argument: ${missingParams[0]}`);
} else {
ns.tprint(`Missing the following required arguments: ${missingParams.slice(missingParams.length-1).join(", ")} & ${missingParams[missingParams.length-1]}.`);
}
ns.exit();
}
return parsedParams;
}
export function check(ns){
ns.print("I'm the parse goblin Nyehehhehe!");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment