GLI
A Ruby library for creating command-line tools with Git-like interfaces.
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