Home

Banner

Decaf's Utils Module

A comprehensive TypeScript utility library providing robust tools for command-line interfaces, input handling, output management, file system operations, HTTP requests, text processing, and more. This module serves as a foundation for building powerful CLI applications with standardized input parsing, flexible output formatting, and a rich set of utility functions.

Licence GitHub language count GitHub top language

Build & Test CodeQLSnyk Analysis Pages builder .github/workflows/release-on-tag.yaml

Open Issues Closed Issues Pull Requests Maintained

Forks Stars Watchers

Node Version NPM Version

Documentation available here

Description

The Decaf Utils module is a comprehensive TypeScript utility library designed to standardize APIs across repositories and provide a robust foundation for building command-line interface (CLI) applications. The library is organized into several key components:

CLI Module

The CLI module provides a structured framework for creating command-line applications:

  • Abstract Command class for implementing custom CLI commands with standardized input handling, logging, and execution flow
  • Command option handling with support for common flags like verbose, version, help, etc.
  • Standardized command execution flow with proper error handling

Input Module

The Input module offers tools for handling user input and command-line arguments:

  • UserInput class for creating interactive prompts with various input types (text, number, confirmation, etc.)
  • Support for input validation, formatting, and default values
  • Command-line argument parsing with type checking and validation
  • Methods for repeatedly asking for input until valid responses are received

Utils Module

The Utils module contains a wide range of utility functions:

  • File System Operations: Reading, writing, copying, and deleting files and directories
  • Package Management: Retrieving package information, managing dependencies, and version handling
  • HTTP Utilities: Simple client for downloading files from URLs
  • Text Processing: String interpolation, case conversion (camelCase, snake_case, etc.), and regular expression utilities
  • Environment Handling: Working with environment variables and configuration

Writers Module

The Writers module provides different strategies for handling command output:

  • OutputWriter interface defining a standard contract for output handling
  • StandardOutputWriter for handling standard command-line output with proper logging
  • RegexpOutputWriter for processing output with regular expressions and pattern matching

Output Module

The Output module offers utilities for formatting and displaying output:

  • Common output formatting functions
  • Banner display capabilities

This library serves as a light version of the Decaf CLI tool, providing all the essential utilities needed for building robust command-line applications with consistent input handling, output formatting, and error management.

How to Use

Examples

CLI Module

Creating a Custom Command

The Command class provides a foundation for creating custom CLI commands with standardized input handling and execution flow.

import { Command, CommandOptions } from '@decaf-ts/utils';

// Define input options interface
interface MyCommandOptions {
  filename: string;
  dryRun: boolean;
}

// Create a custom command class
class MyCommand extends Command<MyCommandOptions, string> {
  constructor() {
    // Define command options with their configurations
    const options: CommandOptions<MyCommandOptions> = {
      filename: {
        type: 'string',
        short: 'f',
        default: 'output.txt'
      },
      dryRun: {
        type: 'boolean',
        short: 'd',
        default: false
      }
    };

    super('my-command', options);
  }

  // Override the help method to provide custom help information
  protected help(): void {
    this.log.info(`
      Usage: my-command [options]

      Options:
        -f, --filename  Specify output filename (default: output.txt)
        -d, --dryRun    Run without making changes (default: false)
        -h, --help      Show help information
        -v, --version   Show version information
    `);
  }

  // Implement the run method to define command behavior
  protected async run(answers: { filename: string; dryRun: boolean }): Promise<string> {
    this.log.info(`Running command with filename: ${answers.filename}, dryRun: ${answers.dryRun}`);

    // Command implementation here

    return 'Command executed successfully';
  }
}

// Execute the command
async function main() {
  const command = new MyCommand();
  const result = await command.execute();
  console.log(result);
}

main().catch(console.error);

Input Module

Creating Interactive Prompts

The UserInput class allows you to create interactive prompts for collecting user input.

import { UserInput } from '@decaf-ts/utils';

async function collectUserInfo() {
  // Create a text input for name
  const nameInput = new UserInput('name')
    .setMessage('What is your name?')
    .setInitial('User');

  // Create a number input for age with validation
  const ageInput = new UserInput('age')
    .setType('number')
    .setMessage('How old are you?')
    .setMin(0)
    .setMax(120)
    .setValidate(value => {
      if (value < 18) return 'You must be at least 18 years old';
      return true;
    });

  // Create a confirmation input
  const confirmInput = new UserInput('confirm')
    .setType('confirm')
    .setMessage('Is this information correct?')
    .setInitial(true);

  // Ask for all inputs and get the answers
  const answers = await UserInput.ask([nameInput, ageInput, confirmInput]);

  console.log(`Hello ${answers.name}, you are ${answers.age} years old.`);
  console.log(`Information confirmed: ${answers.confirm ? 'Yes' : 'No'}`);

  return answers;
}

collectUserInfo().catch(console.error);

Using Convenience Methods for Input

UserInput provides static convenience methods for common input scenarios.

import { UserInput } from '@decaf-ts/utils';

async function quickInputExample() {
  // Ask for text input
  const name = await UserInput.askText('name', 'What is your name?', undefined, 'User');

  // Ask for number input
  const age = await UserInput.askNumber('age', 'How old are you?', 0, 120);

  // Ask for confirmation
  const confirm = await UserInput.askConfirmation('confirm', 'Is this information correct?', true);

  // Ask for text with validation and retry until valid
  const email = await UserInput.insistForText(
    'email',
    'What is your email address?',
    (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
    undefined,
    undefined,
    false,
    3 // Maximum 3 attempts
  );

  console.log(`Name: ${name}, Age: ${age}, Email: ${email}, Confirmed: ${confirm}`);
}

quickInputExample().catch(console.error);

Parsing Command-Line Arguments

import { UserInput, ParseArgsOptionsConfig } from '@decaf-ts/utils';

// Define command-line options
const options: ParseArgsOptionsConfig = {
  verbose: {
    type: 'boolean',
    short: 'V',
    default: false
  },
  output: {
    type: 'string',
    short: 'o',
    default: 'output.txt'
  },
  count: {
    type: 'string',
    short: 'c',
    default: '10'
  }
};

// Parse command-line arguments
const args = UserInput.parseArgs(options);

console.log('Parsed arguments:', args.values);
console.log('Positional arguments:', args.positionals);

Utils Module

File System Operations

import { 
  readFile, 
  writeFile, 
  patchFile, 
  getAllFiles, 
  copyFile, 
  renameFile, 
  deletePath 
} from '@decaf-ts/utils';

// Read a file
const content = readFile('./config.json');
console.log('File content:', content);

// Write to a file
writeFile('./output.txt', 'Hello, world!');

// Patch a file with replacements
patchFile('./template.txt', {
  '{{name}}': 'John Doe',
  '{{date}}': new Date().toISOString()
});

// Get all files in a directory
const files = getAllFiles('./src', (file) => file.endsWith('.ts'));
console.log('TypeScript files:', files);

// Copy a file
copyFile('./source.txt', './destination.txt');

// Rename a file
renameFile('./old-name.txt', './new-name.txt');

// Delete a file or directory
deletePath('./temp');

Package Management

import { 
  getPackage, 
  getPackageVersion, 
  setPackageAttribute, 
  getDependencies, 
  installDependencies 
} from '@decaf-ts/utils';

// Get package information
const pkg = getPackage();
console.log('Package:', pkg);

// Get package version
const version = getPackageVersion();
console.log('Version:', version);

// Set a package attribute
setPackageAttribute('version', '1.0.1');

// Get dependencies
async function manageDependencies() {
  const deps = await getDependencies();
  console.log('Production dependencies:', deps.prod);
  console.log('Development dependencies:', deps.dev);
  console.log('Peer dependencies:', deps.peer);

  // Install dependencies
  await installDependencies({
    prod: ['lodash', 'axios'],
    dev: ['typescript', 'jest']
  });
}

manageDependencies().catch(console.error);

HTTP Utilities

import { HttpClient } from '@decaf-ts/utils';

async function downloadExample() {
  try {
    // Download a file from a URL
    const content = await HttpClient.downloadFile('https://example.com/api/data.json');
    console.log('Downloaded content:', content);

    // Parse JSON content
    const data = JSON.parse(content);
    console.log('Parsed data:', data);

    return data;
  } catch (error) {
    console.error('Download failed:', error);
    throw error;
  }
}

downloadExample().catch(console.error);

Text Processing

import { 
  padEnd, 
  patchPlaceholders, 
  patchString, 
  toCamelCase, 
  toSnakeCase, 
  toKebabCase, 
  toPascalCase, 
  toENVFormat 
} from '@decaf-ts/utils';

// Pad a string
const padded = padEnd('Hello', 10, '-');
console.log(padded); // 'Hello-----'

// Replace placeholders
const template = 'Hello, ${name}! Today is ${day}.';
const filled = patchPlaceholders(template, {
  name: 'Alice',
  day: 'Monday'
});
console.log(filled); // 'Hello, Alice! Today is Monday.'

// Replace strings
const patched = patchString('Hello, world!', {
  'world': 'universe'
});
console.log(patched); // 'Hello, universe!'

// Case conversion
const text = 'hello world';
console.log(toCamelCase(text));   // 'helloWorld'
console.log(toSnakeCase(text));   // 'hello_world'
console.log(toKebabCase(text));   // 'hello-world'
console.log(toPascalCase(text));  // 'HelloWorld'
console.log(toENVFormat(text));   // 'HELLO_WORLD'

Writers Module

Using StandardOutputWriter

import { StandardOutputWriter } from '@decaf-ts/utils';

// Create a promise executor
const executor = {
  resolve: (value: string) => console.log(`Command succeeded with: ${value}`),
  reject: (error: Error) => console.error(`Command failed with: ${error.message}`)
};

// Create a standard output writer
const writer = new StandardOutputWriter('ls -la', executor);

// Handle command output
writer.data('File list output...');
writer.data('file1.txt');
writer.data('file2.txt');

// Handle command completion
writer.exit(0, ['Command executed successfully']);

Using RegexpOutputWriter

import { RegexpOutputWriter } from '@decaf-ts/utils';

// Create a promise executor
const executor = {
  resolve: (value: string) => console.log(`Found version: ${value}`),
  reject: (error: Error) => console.error(`Error: ${error.message}`)
};

// Create a regexp output writer that matches version numbers
const writer = new RegexpOutputWriter('node --version', executor, /v(\d+\.\d+\.\d+)/);

// Process output that contains a version number
writer.data('v14.17.0');  // This will automatically resolve with "v14.17.0"

// Process error output
writer.error('Command not found: node');  // This will be logged as an error

Related

Readme Card

Social

LinkedIn

Languages

TypeScript JavaScript NodeJS ShellScript

Getting help

If you have bug reports, questions or suggestions please create a new issue.

Contributing

I am grateful for any contributions made to this project. Please read this to get started.

Supporting

The first and easiest way you can support it is by Contributing. Even just finding a typo in the documentation is important.

Financial support is always welcome and helps keep both me and the project alive and healthy.

So if you can, if this project in any way. either by learning something or simply by helping you save precious time, please consider donating.

License

This project is released under the MIT License.

By developers, for developers...