GLI

A Ruby library for creating command-line tools with Git-like interfaces.

rubyclicommand-linegit-like

Framework

GLI

Overview

GLI is a Ruby library for creating command-line tools with Git-like interfaces. It's suitable for building complex CLI applications with subcommands and is chosen by developers who prefer Git-like interfaces. With rich functionality and flexible architecture, it supports professional CLI tool development.

Details

GLI (Git-Like Interface) is a library for building CLI applications with Git-like subcommand structures. You can easily create complex hierarchical structures that combine commands, subcommands, global options, and command-specific options.

Key Features

  • Git-like Interface: Subcommand-based hierarchical structure
  • DSL-based Design: Declarative and readable command definition
  • Rich Options: Supports global and command-specific options
  • Automatic Help Generation: Automatically generates beautifully organized help pages
  • Input Validation: Validation functionality for arguments and options
  • Configuration Files: Automatic configuration file management
  • Skeleton Generation: Automatic project template generation
  • Test Support: Testing functionality with Aruba integration

Pros and Cons

Pros

  • Git-like UI: Familiar subcommand structure
  • Rich Features: All necessary features for professional CLI development
  • Declarative API: Readable and maintainable code
  • Auto-generation: Automatic generation of help, config files, templates
  • Flexibility: Supports complex command structures
  • Testable: Comprehensive testing with Aruba integration
  • Documentation: Comprehensive official documentation

Cons

  • Learning Curve: Time needed to understand DSL
  • Overhead: May be heavy for simple tools
  • Ruby Dependency: Requires Ruby environment
  • Complexity: Excessive features for small projects

Key Links

Usage Examples

Installation

gem install gli

Basic Usage

#!/usr/bin/env ruby

require 'gli'

include GLI::App

program_desc 'File management tool'
version '1.0.0'

# Global options
desc 'Enable logging'
switch [:l, :log]

desc 'Enable debug mode'
switch [:d, :debug]

desc 'Configuration file path'
flag [:c, :config], default_value: '~/.filemanager'

# list command
desc 'Display file list'
long_desc <<-EOF
Display file list of specified directory.
By default, targets current directory.
EOF

command :list do |c|
  c.desc 'Show all files (including hidden files)'
  c.switch [:a, :all]
  
  c.desc 'Long format display'
  c.switch [:l, :long]
  
  c.desc 'Target directory'
  c.flag [:d, :directory], default_value: '.'
  
  c.action do |global_options, options, args|
    directory = options[:directory]
    show_all = options[:all]
    long_format = options[:long]
    
    puts "Directory: #{directory}"
    puts "Show all: #{show_all ? 'yes' : 'no'}"
    puts "Long format: #{long_format ? 'yes' : 'no'}"
    
    # Get and display file list
    pattern = show_all ? File.join(directory, '*') : File.join(directory, '[^.]*')
    files = Dir.glob(pattern)
    
    files.each do |file|
      if long_format
        stat = File.stat(file)
        puts "#{File.basename(file)}\t#{stat.size}bytes\t#{stat.mtime}"
      else
        puts File.basename(file)
      end
    end
  end
end

# copy command
desc 'Copy files'
arg_name 'source destination'
command :copy do |c|
  c.desc 'Force overwrite'
  c.switch [:f, :force]
  
  c.desc 'Recursive copy'
  c.switch [:r, :recursive]
  
  c.action do |global_options, options, args|
    if args.length != 2
      raise 'Please specify both source and destination'
    end
    
    source, destination = args
    force = options[:force]
    recursive = options[:recursive]
    
    puts "Copying: #{source} -> #{destination}"
    puts "Force mode: #{force ? 'yes' : 'no'}"
    puts "Recursive mode: #{recursive ? 'yes' : 'no'}"
    
    # Copy implementation
    if File.directory?(source) && recursive
      FileUtils.cp_r(source, destination, force: force)
    elsif File.file?(source)
      if force || !File.exist?(destination)
        FileUtils.cp(source, destination)
      else
        puts "Error: #{destination} already exists. Use --force."
        exit 1
      end
    else
      puts "Error: #{source} not found."
      exit 1
    end
    
    puts "Copy completed"
  end
end

# delete command
desc 'Delete files'
arg_name 'files...'
command :delete do |c|
  c.desc 'Delete without confirmation'
  c.switch [:f, :force]
  
  c.desc 'Recursive deletion'
  c.switch [:r, :recursive]
  
  c.action do |global_options, options, args|
    if args.empty?
      raise 'Please specify files to delete'
    end
    
    force = options[:force]
    recursive = options[:recursive]
    
    args.each do |file|
      unless File.exist?(file)
        puts "Warning: #{file} not found"
        next
      end
      
      # Confirmation prompt
      unless force
        print "Delete #{file}? [y/N]: "
        answer = STDIN.gets.chomp.downcase
        next unless answer == 'y' || answer == 'yes'
      end
      
      begin
        if File.directory?(file) && recursive
          FileUtils.rm_rf(file)
        elsif File.file?(file)
          FileUtils.rm(file)
        else
          puts "Error: #{file} is a directory. Use -r option."
          next
        end
        
        puts "Deleted: #{file}"
      rescue => e
        puts "Error: Failed to delete #{file} - #{e.message}"
      end
    end
  end
end

# pre/post hooks
pre do |global, command, options, args|
  if global[:log]
    puts "Executing command: #{command.name}"
    puts "Global options: #{global}"
    puts "Command options: #{options}"
    puts "Arguments: #{args}"
  end
  
  # Load configuration file
  config_file = File.expand_path(global[:config])
  if File.exist?(config_file)
    puts "Loading config file: #{config_file}" if global[:debug]
  end
  
  true # Continue processing
end

post do |global, command, options, args|
  puts "Command '#{command.name}' execution completed" if global[:log]
end

# Error handling
on_error do |exception|
  puts "An error occurred: #{exception.message}"
  true # Delegate error handling to GLI
end

exit run(ARGV)

Execution Examples

# Basic file operations
./filemanager list --all --long --directory /tmp
./filemanager copy source.txt destination.txt --force
./filemanager delete old_file.txt --force

# Project management
./projectmanager project create --name myapp --type web --template react
./projectmanager project list --json
./projectmanager project delete --name myapp

# Configuration management
./projectmanager config set database.host localhost
./projectmanager config get database.host
./projectmanager config show