💎 Hash CLI Directive in TuskLang for Ruby

Ruby Documentation

Hash CLI Directive in TuskLang for Ruby

Welcome to the command-line revolution with TuskLang's Hash CLI Directive! This is where we break free from the constraints of traditional CLI frameworks and embrace the power of declarative, configuration-driven command-line applications. In Ruby, this means combining TuskLang's elegant hash directives with Ruby's powerful CLI capabilities to create applications that are as expressive as they are powerful.

What is the Hash CLI Directive?

The Hash CLI Directive (#cli) is TuskLang's declaration of independence from traditional CLI frameworks. It allows you to define complete command-line applications, commands, options, and behaviors using simple hash configurations. In Ruby, this translates to powerful, declarative CLI definitions that can be processed, validated, and executed with minimal ceremony.

Basic CLI Directive Syntax

Basic CLI application definition

cli_app_config = { "#cli" => { "name" => "tusk-cli", "version" => "1.0.0", "description" => "A powerful CLI built with TuskLang", "commands" => { "init" => { "description" => "Initialize a new TuskLang project", "handler" => "InitCommand#execute", "options" => { "template" => { "type" => "string", "description" => "Project template to use", "default" => "default" }, "force" => { "type" => "boolean", "description" => "Force overwrite existing files", "default" => false } } }, "build" => { "description" => "Build the project", "handler" => "BuildCommand#execute", "options" => { "target" => { "type" => "string", "description" => "Build target", "required" => true } } } } } }

Ruby class to process the CLI directive

class CliDirectiveProcessor def initialize(config) @config = config @tusk_config = load_tusk_config end

def process_cli_directive cli_config = @config["#cli"] return nil unless cli_config

{ name: cli_config["name"], version: cli_config["version"], description: cli_config["description"], commands: process_commands(cli_config["commands"]), global_options: process_global_options(cli_config["global_options"]), help_template: cli_config["help_template"] || "default" } end

private

def process_commands(commands_config) commands_config.transform_values do |command_config| { description: command_config["description"], handler: parse_handler(command_config["handler"]), options: process_options(command_config["options"]), arguments: process_arguments(command_config["arguments"]), examples: command_config["examples"] || [], aliases: command_config["aliases"] || [] } end end

def process_options(options_config) return {} unless options_config

options_config.transform_values do |option_config| { type: option_config["type"], description: option_config["description"], required: option_config["required"] || false, default: option_config["default"], choices: option_config["choices"] || [] } end end

def process_arguments(arguments_config) return [] unless arguments_config

arguments_config.map do |arg_config| { name: arg_config["name"], description: arg_config["description"], required: arg_config["required"] || false, type: arg_config["type"] || "string" } end end

def parse_handler(handler_string) return nil unless handler_string command_class, method = handler_string.split("#") { class: command_class, method: method } end

def process_global_options(global_options_config) return {} unless global_options_config

process_options(global_options_config) end

def load_tusk_config TuskConfig.load("peanu.tsk") end end

Advanced CLI Configuration

Comprehensive CLI application configuration

advanced_cli_config = { "#cli" => { "name" => "advanced-tusk-cli", "version" => "2.0.0", "description" => "Advanced CLI with TuskLang features", "author" => "TuskLang Team", "global_options" => { "verbose" => { "type" => "boolean", "description" => "Enable verbose output", "short" => "v", "default" => false }, "config" => { "type" => "string", "description" => "Configuration file path", "short" => "c", "default" => "peanu.tsk" }, "log-level" => { "type" => "string", "description" => "Log level (debug, info, warn, error)", "choices" => ["debug", "info", "warn", "error"], "default" => "info" } }, "commands" => { "deploy" => { "description" => "Deploy application to various environments", "handler" => "DeployCommand#execute", "options" => { "environment" => { "type" => "string", "description" => "Target environment", "required" => true, "choices" => ["development", "staging", "production"] }, "force" => { "type" => "boolean", "description" => "Force deployment without confirmation", "short" => "f", "default" => false }, "parallel" => { "type" => "integer", "description" => "Number of parallel deployments", "default" => 1 } }, "arguments" => [ { "name" => "target", "description" => "Deployment target", "required" => true } ], "examples" => [ "tusk deploy production web-server", "tusk deploy staging api-server --force", "tusk deploy development all --parallel 3" ] }, "monitor" => { "description" => "Monitor application health and performance", "handler" => "MonitorCommand#execute", "subcommands" => { "status" => { "description" => "Check application status", "handler" => "MonitorCommand#status" }, "logs" => { "description" => "View application logs", "handler" => "MonitorCommand#logs", "options" => { "tail" => { "type" => "boolean", "description" => "Follow log output", "short" => "f", "default" => false }, "lines" => { "type" => "integer", "description" => "Number of lines to show", "default" => 100 } } } } } }, "themes" => { "default" => { "colors" => { "primary" => "blue", "success" => "green", "warning" => "yellow", "error" => "red" }, "formatting" => { "indent" => 2, "max_width" => 80 } }, "dark" => { "colors" => { "primary" => "cyan", "success" => "bright_green", "warning" => "bright_yellow", "error" => "bright_red" } } } } }

Ruby processor for advanced CLI configurations

class AdvancedCliProcessor < CliDirectiveProcessor def process_advanced_config base_config = process_cli_directive cli_config = @config["#cli"] base_config.merge({ author: cli_config["author"], themes: process_themes(cli_config["themes"]), plugins: process_plugins(cli_config["plugins"]), hooks: process_hooks(cli_config["hooks"]), completions: process_completions(cli_config["completions"]) }) end

private

def process_themes(themes_config) return {} unless themes_config

themes_config.transform_values do |theme_config| { colors: theme_config["colors"] || {}, formatting: theme_config["formatting"] || {}, styles: theme_config["styles"] || {} } end end

def process_plugins(plugins_config) return [] unless plugins_config

plugins_config.map do |plugin_config| { name: plugin_config["name"], path: plugin_config["path"], enabled: plugin_config["enabled"] || true, config: plugin_config["config"] || {} } end end

def process_hooks(hooks_config) return {} unless hooks_config

{ before_command: hooks_config["before_command"] || [], after_command: hooks_config["after_command"] || [], on_error: hooks_config["on_error"] || [] } end

def process_completions(completions_config) return {} unless completions_config

{ bash: completions_config["bash"], zsh: completions_config["zsh"], fish: completions_config["fish"] } end end

Command Execution and Argument Parsing

Command execution configuration with TuskLang features

command_execution_config = { "#cli" => { "commands" => { "process" => { "description" => "Process data with TuskLang pipelines", "handler" => "ProcessCommand#execute", "options" => { "input" => { "type" => "string", "description" => "Input file or data", "required" => true }, "output" => { "type" => "string", "description" => "Output file", "required" => true }, "pipeline" => { "type" => "string", "description" => "TuskLang pipeline configuration", "default" => "@default_pipeline" }, "format" => { "type" => "string", "description" => "Output format", "choices" => ["json", "yaml", "csv", "xml"], "default" => "json" } }, "arguments" => [ { "name" => "operation", "description" => "Operation to perform", "required" => true, "choices" => ["transform", "validate", "analyze", "export"] } ], "validation" => { "input_file_exists" => true, "output_directory_writable" => true, "pipeline_valid" => true } } } } }

Ruby command execution processor

class CommandExecutionProcessor def initialize(config) @config = config["#cli"] end

def execute_command(command_name, args, options) command_config = @config["commands"][command_name] return { success: false, error: "Command not found" } unless command_config

# Validate arguments and options validation_result = validate_command(command_name, args, options) return validation_result unless validation_result[:valid]

# Execute pre-hooks execute_hooks("before_command", command_name, args, options)

# Execute command result = execute_handler(command_config["handler"], args, options)

# Execute post-hooks execute_hooks("after_command", command_name, args, options, result)

result end

def validate_command(command_name, args, options) command_config = @config["commands"][command_name] # Validate required arguments required_args = command_config["arguments"]&.select { |arg| arg["required"] } || [] if args.length < required_args.length return { valid: false, error: "Missing required arguments: #{required_args.map { |arg| arg['name'] }.join(', ')}" } end

# Validate required options required_options = command_config["options"]&.select { |_, opt| opt["required"] } || {} missing_options = required_options.keys - options.keys if missing_options.any? return { valid: false, error: "Missing required options: #{missing_options.join(', ')}" } end

# Validate option choices choice_errors = validate_option_choices(command_config["options"], options) return choice_errors if choice_errors[:valid] == false

# Validate custom validation rules validation_errors = validate_custom_rules(command_config["validation"], args, options) return validation_errors if validation_errors[:valid] == false

{ valid: true } end

private

def validate_option_choices(options_config, options) return { valid: true } unless options_config

options_config.each do |option_name, option_config| next unless option_config["choices"] && options[option_name] unless option_config["choices"].include?(options[option_name]) return { valid: false, error: "Invalid value for #{option_name}. Must be one of: #{option_config['choices'].join(', ')}" } end end

{ valid: true } end

def validate_custom_rules(validation_config, args, options) return { valid: true } unless validation_config

validation_config.each do |rule, enabled| next unless enabled

case rule when "input_file_exists" input_file = options["input"] unless File.exist?(input_file) return { valid: false, error: "Input file does not exist: #{input_file}" } end when "output_directory_writable" output_file = options["output"] output_dir = File.dirname(output_file) unless File.writable?(output_dir) return { valid: false, error: "Output directory is not writable: #{output_dir}" } end when "pipeline_valid" pipeline = options["pipeline"] unless valid_pipeline?(pipeline) return { valid: false, error: "Invalid pipeline configuration: #{pipeline}" } end end end

{ valid: true } end

def execute_handler(handler_config, args, options) command_class = handler_config[:class].constantize command_instance = command_class.new command_instance.send(handler_config[:method], args, options) rescue => e { success: false, error: e.message, backtrace: e.backtrace.first(5) } end

def execute_hooks(hook_type, command_name, args, options, result = nil) hooks = @config["hooks"]&.dig(hook_type) || [] hooks.each do |hook| hook_class, hook_method = hook.split("#") hook_instance = hook_class.constantize.new hook_instance.send(hook_method, command_name, args, options, result) end end

def valid_pipeline?(pipeline) # Implementation would validate TuskLang pipeline syntax true end end

Interactive CLI Features

Interactive CLI configuration

interactive_cli_config = { "#cli" => { "interactive" => { "enabled" => true, "prompt" => "tusk> ", "history_file" => "~/.tusk_history", "completions" => { "enabled" => true, "provider" => "readline" }, "wizard" => { "enabled" => true, "steps" => { "project_setup" => { "title" => "Project Setup Wizard", "questions" => [ { "name" => "project_name", "type" => "input", "message" => "What is your project name?", "required" => true, "validation" => "string|min:1|max:50" }, { "name" => "template", "type" => "select", "message" => "Choose a project template:", "choices" => [ { "name" => "Web Application", "value" => "web" }, { "name" => "API Service", "value" => "api" }, { "name" => "CLI Tool", "value" => "cli" }, { "name" => "Library", "value" => "library" } ] }, { "name" => "features", "type" => "multiselect", "message" => "Select features to include:", "choices" => [ { "name" => "Database Integration", "value" => "database" }, { "name" => "Authentication", "value" => "auth" }, { "name" => "API Documentation", "value" => "docs" }, { "name" => "Testing Framework", "value" => "testing" } ] } ] } } } }, "commands" => { "wizard" => { "description" => "Interactive setup wizard", "handler" => "WizardCommand#execute", "options" => { "step" => { "type" => "string", "description" => "Start from specific step", "choices" => ["project_setup", "configuration", "deployment"] } } } } } }

Ruby interactive CLI processor

class InteractiveCliProcessor def initialize(config) @config = config["#cli"]["interactive"] @wizard_config = config["#cli"]["commands"]["wizard"] end

def start_interactive_mode return unless @config["enabled"]

setup_readline setup_history loop do begin input = Readline.readline(@config["prompt"], true) break if input.nil? || input.downcase == "exit" process_interactive_input(input) rescue Interrupt puts "\nExiting..." break rescue => e puts "Error: #{e.message}" end end end

def run_wizard(wizard_name, options = {}) wizard_config = @wizard_config["subcommands"][wizard_name] return { success: false, error: "Wizard not found" } unless wizard_config

answers = {} wizard_config["questions"].each do |question| answer = ask_question(question) answers[question["name"]] = answer end

# Execute wizard handler execute_wizard_handler(wizard_config["handler"], answers, options) end

private

def setup_readline return unless @config["completions"]["enabled"]

Readline.completion_proc = proc do |input| generate_completions(input) end end

def setup_history history_file = File.expand_path(@config["history_file"]) FileUtils.mkdir_p(File.dirname(history_file)) if File.exist?(history_file) File.readlines(history_file).each { |line| Readline::HISTORY << line.chomp } end end

def process_interactive_input(input) return if input.strip.empty?

# Parse command and arguments parts = input.split command = parts[0] args = parts[1..-1]

# Execute command result = execute_command(command, args, {}) if result[:success] puts result[:output] if result[:output] else puts "Error: #{result[:error]}" end end

def ask_question(question) case question["type"] when "input" ask_input_question(question) when "select" ask_select_question(question) when "multiselect" ask_multiselect_question(question) when "confirm" ask_confirm_question(question) else ask_input_question(question) end end

def ask_input_question(question) loop do print "#{question['message']} " answer = gets.chomp if validate_answer(answer, question["validation"]) return answer else puts "Invalid input. Please try again." end end end

def ask_select_question(question) puts question["message"] question["choices"].each_with_index do |choice, index| puts "#{index + 1}. #{choice['name']}" end loop do print "Enter your choice (1-#{question['choices'].length}): " choice = gets.chomp.to_i if choice.between?(1, question["choices"].length) return question["choices"][choice - 1]["value"] else puts "Invalid choice. Please try again." end end end

def ask_multiselect_question(question) puts question["message"] question["choices"].each_with_index do |choice, index| puts "#{index + 1}. #{choice['name']}" end puts "Enter choices separated by commas (e.g., 1,3,4):" loop do print "Your choices: " choices = gets.chomp.split(",").map(&:strip).map(&:to_i) if choices.all? { |c| c.between?(1, question["choices"].length) } return choices.map { |c| question["choices"][c - 1]["value"] } else puts "Invalid choices. Please try again." end end end

def ask_confirm_question(question) loop do print "#{question['message']} (y/n): " answer = gets.chomp.downcase case answer when "y", "yes" return true when "n", "no" return false else puts "Please answer 'y' or 'n'." end end end

def validate_answer(answer, validation) return true unless validation

validation_rules = validation.split("|") validation_rules.each do |rule| case rule when "required" return false if answer.blank? when /^min:(\d+)$/ min_length = $1.to_i return false if answer.length < min_length when /^max:(\d+)$/ max_length = $1.to_i return false if answer.length > max_length end end

true end

def generate_completions(input) # Generate command completions based on available commands commands = @config["commands"]&.keys || [] commands.select { |cmd| cmd.start_with?(input) } end

def execute_wizard_handler(handler_config, answers, options) handler_class, handler_method = handler_config.split("#") handler_instance = handler_class.constantize.new handler_instance.send(handler_method, answers, options) end end

Output Formatting and Themes

Output formatting configuration

output_config = { "#cli" => { "output" => { "format" => "table", "theme" => "default", "colors" => { "enabled" => true, "force" => false }, "tables" => { "style" => "ascii", "border" => true, "padding" => 1 }, "progress" => { "style" => "bar", "width" => 50, "characters" => { "filled" => "█", "empty" => "░", "current" => "█" } }, "spinners" => { "style" => "dots", "frames" => ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] } }, "themes" => { "default" => { "colors" => { "primary" => "blue", "success" => "green", "warning" => "yellow", "error" => "red", "info" => "cyan" }, "styles" => { "bold" => true, "underline" => false } }, "dark" => { "colors" => { "primary" => "bright_blue", "success" => "bright_green", "warning" => "bright_yellow", "error" => "bright_red", "info" => "bright_cyan" } } } } }

Ruby output formatter

class OutputFormatter def initialize(config) @config = config["#cli"]["output"] @themes = config["#cli"]["themes"] @current_theme = @themes[@config["theme"] || "default"] end

def format_output(data, format = nil) format ||= @config["format"] case format when "table" format_table(data) when "json" format_json(data) when "yaml" format_yaml(data) when "csv" format_csv(data) when "list" format_list(data) else format_default(data) end end

def progress_bar(current, total, label = nil) return unless @config["progress"]["style"] == "bar"

width = @config["progress"]["width"] filled_char = @config["progress"]["characters"]["filled"] empty_char = @config["progress"]["characters"]["empty"] percentage = (current.to_f / total * 100).round(1) filled_width = (current.to_f / total * width).round bar = filled_char filled_width + empty_char (width - filled_width) output = "[#{bar}] #{percentage}%" output = "#{label}: #{output}" if label puts output end

def spinner(message = "Processing...") return unless @config["spinners"]["style"] == "dots"

frames = @config["spinners"]["frames"] frame_index = 0 loop do print "\r#{frames[frame_index]} #{message}" frame_index = (frame_index + 1) % frames.length sleep 0.1 end end

def success(message) colorize(message, @current_theme["colors"]["success"]) end

def error(message) colorize(message, @current_theme["colors"]["error"]) end

def warning(message) colorize(message, @current_theme["colors"]["warning"]) end

def info(message) colorize(message, @current_theme["colors"]["info"]) end

private

def format_table(data) return "No data to display" if data.empty?

if data.is_a?(Array) && data.first.is_a?(Hash) format_hash_table(data) else format_simple_table(data) end end

def format_hash_table(data) headers = data.first.keys rows = data.map(&:values) table = Terminal::Table.new( headings: headers, rows: rows, style: { border: @config["tables"]["border"] } ) table.to_s end

def format_simple_table(data) if data.is_a?(Hash) table = Terminal::Table.new do |t| data.each do |key, value| t << [key.to_s, value.to_s] end end table.to_s else data.to_s end end

def format_json(data) JSON.pretty_generate(data) end

def format_yaml(data) data.to_yaml end

def format_csv(data) return "" if data.empty?

if data.is_a?(Array) && data.first.is_a?(Hash) headers = data.first.keys CSV.generate do |csv| csv << headers data.each { |row| csv << row.values } end else data.to_s end end

def format_list(data) if data.is_a?(Array) data.map.with_index { |item, index| "#{index + 1}. #{item}" }.join("\n") else data.to_s end end

def format_default(data) data.to_s end

def colorize(text, color) return text unless @config["colors"]["enabled"] color_code = get_color_code(color) "\e[#{color_code}m#{text}\e[0m" end

def get_color_code(color) case color when "red" then 31 when "green" then 32 when "yellow" then 33 when "blue" then 34 when "magenta" then 35 when "cyan" then 36 when "bright_red" then 91 when "bright_green" then 92 when "bright_yellow" then 93 when "bright_blue" then 94 when "bright_magenta" then 95 when "bright_cyan" then 96 else 0 end end end

Integration with Ruby CLI Frameworks

Thor integration example

class TuskThorApp < Thor include CliDirectiveProcessor

def initialize @tusk_config = load_tusk_config @cli_processor = CliDirectiveProcessor.new(@tusk_config) @output_formatter = OutputFormatter.new(@tusk_config) setup_thor_commands end

private

def setup_thor_commands cli_config = @cli_processor.process_cli_directive return unless cli_config

cli_config[:commands].each do |command_name, command_config| define_command_method(command_name, command_config) end end

def define_command_method(command_name, command_config) self.class.class_eval do desc command_config[:description] command_config[:options]&.each do |option_name, option_config| option option_name.to_sym, type: option_config[:type].to_sym, desc: option_config[:description], required: option_config[:required], default: option_config[:default] end define_method(command_name) do |*args| execute_tusk_command(command_name, args, options) end end end

def execute_tusk_command(command_name, args, options) command_config = @cli_processor.process_cli_directive[:commands][command_name] return unless command_config

handler_config = command_config[:handler] command_class = handler_config[:class].constantize command_instance = command_class.new result = command_instance.send(handler_config[:method], args, options) if result[:success] formatted_output = @output_formatter.format_output(result[:data]) puts formatted_output else puts @output_formatter.error(result[:error]) end end

def load_tusk_config TuskConfig.load("peanu.tsk") end end

OptionParser integration example

class TuskOptionParser include CliDirectiveProcessor

def initialize @tusk_config = load_tusk_config @cli_processor = CliDirectiveProcessor.new(@tusk_config) @output_formatter = OutputFormatter.new(@tusk_config) end

def parse_arguments cli_config = @cli_processor.process_cli_directive return unless cli_config

parser = OptionParser.new do |opts| opts.banner = "Usage: #{cli_config[:name]} [command] [options]" opts.version = cli_config[:version] # Global options cli_config[:global_options]&.each do |option_name, option_config| define_option(opts, option_name, option_config) end opts.on("-h", "--help", "Show this help message") do puts opts exit end end

parser.parse! end

def execute_command(command_name, args, options) command_config = @cli_processor.process_cli_directive[:commands][command_name] return { success: false, error: "Command not found" } unless command_config

handler_config = command_config[:handler] command_class = handler_config[:class].constantize command_instance = command_class.new command_instance.send(handler_config[:method], args, options) end

private

def define_option(parser, option_name, option_config) short_flag = option_config[:short] ? "-#{option_config[:short]}" : nil long_flag = "--#{option_name}" parser.on(short_flag, long_flag, option_config[:description]) do |value| @options[option_name] = value || true end end

def load_tusk_config TuskConfig.load("peanu.tsk") end end

Best Practices and Patterns

Best practices for CLI directive usage

class CliDirectiveBestPractices def self.validate_config(config) errors = [] # Check required fields unless config["#cli"] errors << "Missing #cli directive" return errors end

cli_config = config["#cli"] # Validate name if cli_config["name"]&.empty? errors << "CLI app name cannot be empty" end

# Validate version unless cli_config["version"]&.match?(/^\d+\.\d+\.\d+$/) errors << "Version must be in semantic versioning format (e.g., 1.0.0)" end

# Validate commands unless cli_config["commands"]&.any? errors << "At least one command must be defined" end

# Validate command configurations cli_config["commands"]&.each do |command_name, command_config| command_errors = validate_command(command_name, command_config) errors.concat(command_errors) end

errors end

def self.optimize_config(config) cli_config = config["#cli"] # Set defaults cli_config["theme"] ||= "default" cli_config["output"] ||= {} cli_config["output"]["format"] ||= "table" cli_config["output"]["colors"] ||= {} cli_config["output"]["colors"]["enabled"] ||= true # Optimize commands cli_config["commands"]&.each do |command_name, command_config| command_config["aliases"] ||= [] command_config["examples"] ||= [] end

config end

def self.generate_documentation(config) cli_config = config["#cli"] { name: cli_config["name"], version: cli_config["version"], description: cli_config["description"], commands: cli_config["commands"]&.keys, global_options: cli_config["global_options"]&.keys, themes: cli_config["themes"]&.keys } end

private

def self.validate_command(command_name, command_config) errors = [] unless command_config["description"] errors << "Command #{command_name} must have a description" end unless command_config["handler"] errors << "Command #{command_name} must have a handler" end

# Validate handler format if command_config["handler"] && !command_config["handler"].include?("#") errors << "Handler must be in format 'Class#method'" end

errors end end

Usage example

config = { "#cli" => { "name" => "my-tusk-cli", "version" => "1.0.0", "description" => "A powerful CLI built with TuskLang", "commands" => { "init" => { "description" => "Initialize a new project", "handler" => "InitCommand#execute" } } } }

Validate and optimize

errors = CliDirectiveBestPractices.validate_config(config) if errors.empty? optimized_config = CliDirectiveBestPractices.optimize_config(config) documentation = CliDirectiveBestPractices.generate_documentation(config) puts "Configuration is valid!" puts "Documentation: #{documentation}" else puts "Configuration errors: #{errors}" end

Conclusion

The Hash CLI Directive in TuskLang represents a revolutionary approach to command-line application development. By combining declarative configuration with Ruby's powerful CLI capabilities, you can create sophisticated, maintainable command-line applications with minimal boilerplate code.

Key benefits: - Declarative Configuration: Define complete CLI applications in simple hash structures - Ruby Integration: Leverage Ruby's CLI frameworks and ecosystem - Interactive Features: Built-in wizard and interactive mode support - Output Formatting: Flexible output formatting with themes and colors - Argument Validation: Comprehensive argument and option validation - Command Execution: Powerful command execution with hooks and error handling - Performance Optimization: Built-in progress bars, spinners, and user feedback

Remember, TuskLang is about breaking free from conventions and embracing the power of declarative, configuration-driven development. The Hash CLI Directive is your gateway to building command-line applications that are as expressive as they are powerful!