#!/usr/bin/env ruby require_relative 'lib/color' # Disallow sudo abort "Chromebrew should not be run as root.".lightred if Process.uid == 0 require 'find' require 'net/http' require 'uri' require 'digest/sha2' require 'json' require 'fileutils' require_relative 'lib/const' require_relative 'lib/util' # Add lib to LOAD_PATH $LOAD_PATH.unshift "#{CREW_LIB_PATH}lib" DOC = <<DOCOPT Chromebrew - Package manager for Chrome OS http://skycocker.github.io/chromebrew/ Usage: crew build [-k|--keep] [-v|--verbose] <name> ... crew download [-v|--verbose] <name> ... crew files <name> ... crew help [<command>] crew install [-k|--keep] [-s|--build-from-source] [-v|--verbose] <name> ... crew list (available|installed) crew remove [-v|--verbose] <name> ... crew search [-v|--verbose] [<name> ...] crew update crew upgrade [-k|--keep] [-s|--build-from-source] [-v|--verbose] [<name> ...] crew whatprovides <name> ... -k --keep Keep the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory. -s --build-from-source Build from source even if pre-compiled binary exists. -V --version Display the crew version. -v --verbose Show extra information. -h --help Show this screen. version #{CREW_VERSION} DOCOPT # Set XZ_OPT environment variable for build command. # If CREW_XZ_OPT is defined, use it by default. Use `-7e`, otherwise. if ENV["CREW_XZ_OPT"].to_s == '' ENV["XZ_OPT"] = "-7e -T #{CREW_NPROC}" else ENV["XZ_OPT"] = ENV["CREW_XZ_OPT"] end # Parse arguments using docopt require_relative 'lib/docopt' begin args = Docopt::docopt(DOC) rescue Docopt::Exit => e if ARGV[0] then if ARGV[0] == '-V' or ARGV[0] == '--version' then puts "#{CREW_VERSION}" exit 0 end if ARGV[0] != '-h' and ARGV[0] != '--help' then puts "Could not understand \"crew #{ARGV.join(' ')}\".".lightred cmds = ["build", "download", "files", "help", "install", "list", "remove", "search", "update", "upgrade", "whatprovides"] # Looking for similar commands if not cmds.include?(ARGV[0]) then similar = cmds.select {|word| edit_distance(ARGV[0], word) < 4} if not similar.empty? then puts "Did you mean?" similar.each {|sug| puts " #{sug}"} end end end end puts e.message exit 1 end @opt_keep = args["--keep"] @opt_verbose = args["--verbose"] @opt_src = args["--build-from-source"] @device = JSON.parse(File.read(CREW_CONFIG_PATH + 'device.json'), symbolize_names: true) #symbolize also values @device.each do |key, elem| @device[key] = @device[key].to_sym rescue @device[key] end def print_package(pkgName, extra = false) search pkgName, true print '(i) '.lightgreen if @device[:installed_packages].any? do |elem| elem[:name] == pkgName end print @pkg.name print ": #{@pkg.description}" if @pkg.description if extra puts "" puts @pkg.homepage if @pkg.homepage print "version #{@pkg.version}" end puts "" end def set_package (pkgName, silent = false) require CREW_LIB_PATH + 'packages/' + pkgName @pkg = Object.const_get(pkgName.capitalize) @pkg.name = pkgName print_package(pkgName, true) unless silent end def list_packages Find.find (CREW_LIB_PATH + 'packages') do |filename| if File.extname(filename) == '.rb' print_package File.basename filename, '.rb' end end end def list_available Find.find (CREW_LIB_PATH + 'packages') do |filename| @notInstalled = true Find.find(CREW_CONFIG_PATH + 'meta/') do |packageList| packageName = File.basename filename, '.rb' @notInstalled = nil if packageList == CREW_CONFIG_PATH + 'meta/' + packageName + '.filelist' end puts File.basename filename, '.rb' if File.extname(filename) == '.rb' if @notInstalled end end def list_installed Find.find (CREW_LIB_PATH + 'packages') do |filename| Find.find(CREW_CONFIG_PATH + 'meta/') do |packageList| packageName = File.basename filename, '.rb' if packageList == CREW_CONFIG_PATH + 'meta/' + packageName + '.filelist' puts File.basename filename, '.rb' if File.extname(filename) == '.rb' end end end end def search (pkgName, silent = false) Find.find (CREW_LIB_PATH + 'packages') do |filename| return set_package(pkgName, silent) if filename == CREW_LIB_PATH + 'packages/' + pkgName + '.rb' end abort "Package #{pkgName} not found. :(".lightred unless silent end def regexp_search(pkgName) results = Dir["#{CREW_LIB_PATH}packages/*.rb"].sort \ .select { |f| File.basename(f, '.rb') =~ Regexp.new(pkgName, true) } \ .collect { |f| File.basename(f, '.rb') } \ .each { |f| print_package(f, @opt_verbose) } if results.empty? Find.find ("#{CREW_LIB_PATH}packages/") do |packageName| if File.file? packageName package = File.basename packageName, '.rb' search package, true if ( @pkg.description =~ /#{pkgName}/i ) print_package(package, @opt_verbose) results.push(package) end end end end abort "Package #{pkgName} not found. :(".lightred unless results.length > 0 end def help (pkgName) case pkgName when "build" puts "Build package(s)." puts "Usage: crew build [-k|--keep] [-v|--verbose] <package1> [<package2> ...]" puts "Build package(s) from source and place the archive and checksum in the current working directory." puts "If `-k` or `--keep` is present, the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory will remain." puts "If `-v` or `--verbose` is present, extra information will be displayed." when "download" puts "Download package(s)." puts "Usage: crew download [-v|--verbose] <package1> [<package2> ...]" puts "Download package(s) to `CREW_BREW_DIR` (#{CREW_BREW_DIR}), but don't install." puts "If `-v` or `--verbose` is present, extra information will be displayed." when "files" puts "Display installed files of package(s)." puts "Usage: crew files <package1> [<package2> ...]" puts "The package(s) must be currently installed." when "install" puts "Install package(s)." puts "Usage: crew install [-k|--keep] [-s|--build-from-source] [-v|--verbose] <package1> [<package2> ...]" puts "The package(s) must have a valid name. Use `crew search <pattern>` to search for packages to install." puts "If `-k` or `--keep` is present, the `CREW_BREW_DIR` (#{CREW_BREW_DIR}) directory will remain." puts "If `-s` or `--build-from-source` is present, the package(s) will be compiled instead of installed via binary." puts "If `-v` or `--verbose` is present, extra information will be displayed." when "list" puts "List available or installed packages" puts "Usage: crew list (available|installed)" when "remove" puts "Remove package(s)." puts "Usage: crew remove [-v|--verbose] <package1> [<package2> ...]" puts "The package(s) must be currently installed." puts "If `-v` or `--verbose` is present, extra information will be displayed." when "search" puts "Look for package(s)." puts "Usage: crew search [-v|--verbose] [<pattern> ...]" puts "If <pattern> is omitted, all packages will be returned." puts "(i)".lightgreen + " in front of the name means the package is installed." puts "The <pattern> string can also contain regular expressions." puts "If `-v` or `--verbose` is present, homepage and version will be displayed." puts "Examples:" puts " crew search | grep '(i)'".lightblue + " will display all installed packages." puts " crew search | grep -v '(i)'".lightblue + " will display all available packages not already installed." puts " crew search ^lib".lightblue + " will display all packages that start with `lib`." puts " crew search audio".lightblue + " will display all packages with `audio` in the description." puts " crew search git -v".lightblue + " will display packages with `git` in the name along with homepage and version." when "update" puts "Update crew." puts "Usage: crew update" puts "This only updates crew itself. Use `crew upgrade` to update packages." when "upgrade" puts "Update package(s)." puts "Usage: crew upgrade [-v|--verbose] <package1> [<package2> ...]" puts "If package(s) are omitted, all packages will be updated. Otherwise, specific package(s) will be updated." puts "Use `crew update` to update crew itself." puts "If `-v` or `--verbose` is present, extra information will be displayed." when "whatprovides" puts "Determine which package(s) contains file(s)." puts "Usage: crew whatprovides <pattern> ..." puts "The <pattern> is a search string which can contain regular expressions." else puts "Available commands: build, download, files, install, list ,remove, search, update, upgrade, whatprovides" end end def files (pkgName) filelist = "#{CREW_PREFIX}/etc/crew/meta/#{pkgName}.filelist" if File.exists? "#{filelist}" system "sort #{filelist}" lines = `wc -l "#{filelist}"`.strip.split(' ')[0].to_i size = `du -ch $(cat #{filelist}) | tail -1 | cut -f 1` puts "Total found: #{lines}".lightgreen puts "Disk usage: #{size}".lightgreen else puts "Package #{pkgName} is not installed. :(".lightred end end def whatprovides (pkgName) fileArray = [] Find.find (CREW_CONFIG_PATH + 'meta/') do |packageList| if File.file? packageList if packageList[/\.filelist$/] packageName = File.basename packageList, '.filelist' File.readlines(packageList).each do |line| found = line[/#{Regexp.new(pkgName)}/] if found fileLine = packageName + ': ' + line if not fileArray.include? fileLine fileArray.push(fileLine) end end end end end end if not fileArray.empty? fileArray.sort.each do |item| puts item end puts "\nTotal found: #{fileArray.length}".lightgreen end end def update abort "`crew update` is used to update crew itself. Use `crew upgrade <package1> [<package2> ...]` to update specific packages.".orange if @pkgName #update package lists Dir.chdir CREW_LIB_PATH do system "git fetch origin master" system "git reset --hard origin/master" end puts "Package lists, crew, and library updated." #check for outdated installed packages puts "Checking for package updates..." puts "" canBeUpdated = 0 @device[:installed_packages].each do |package| search package[:name], true if package[:version] != @pkg.version canBeUpdated += 1 puts @pkg.name + " could be updated from " + package[:version] + " to " + @pkg.version end end if canBeUpdated > 0 puts "" puts "Run `crew upgrade` to update all packages or `crew upgrade <package1> [<package2> ...]` to update specific packages." else puts "Your software is up to date.".lightgreen end end def upgrade if @pkgName currentVersion = nil @device[:installed_packages].each do |package| if package[:name] == @pkg.name currentVersion = package[:version] end end if currentVersion != @pkg.version puts "Updating #{@pkg.name}..." @pkg.in_upgrade = true resolve_dependencies_and_install @pkg.in_upgrade = false else puts "#{@pkg.name} is already up to date.".lightgreen end else # Make a installed packages list belong to the dependency order dependencies = [] @device[:installed_packages].each do |package| # skip package if it is dependent other packages previously checked next if dependencies.include? package[:name] # add package itself dependencies = [ package[:name] ].concat(dependencies) # expand depencencies and add it to the dependencies list search package[:name], true exp_dep = expand_dependencies dependencies = exp_dep.concat(dependencies) end dependencies.uniq! # Check version number of installed package and make a target list toBeUpdated = [] dependencies.each do |dep| package = @device[:installed_packages].find {|pkg| pkg[:name] == dep} next unless package search package[:name], true if package[:version] != @pkg.version toBeUpdated.push(package[:name]) end end if toBeUpdated.length > 0 puts "Updating packages..." toBeUpdated.each do |package| search package puts "Updating " + @pkg.name + "..." if @opt_verbose @pkg.in_upgrade = true resolve_dependencies_and_install @pkg.in_upgrade = false end puts "Packages have been updated.".lightgreen else puts "Your software is already up to date.".lightgreen end end end def download url = @pkg.get_url(@device[:architecture]) source = @pkg.is_source?(@device[:architecture]) if !url abort "No precompiled binary or source is available for #{@device[:architecture]}.".lightred elsif !source puts "Precompiled binary available, downloading..." elsif @pkg.build_from_source puts "Downloading source..." else puts "No precompiled binary available for your platform, downloading source..." end uri = URI.parse url filename = File.basename(uri.path) if source sha256sum = @pkg.source_sha256 else sha256sum = @pkg.binary_sha256[@device[:architecture]] end Dir.chdir CREW_BREW_DIR do if @opt_verbose then system('curl', '-v', '-C', '-', '--insecure', '-L', '-#', url, '-o', filename) else system('curl', '-s', '-C', '-', '--insecure', '-L', '-#', url, '-o', filename) end abort 'Checksum mismatch. :/ Try again.'.lightred unless Digest::SHA256.hexdigest( File.read("./#{filename}") ) == sha256sum end puts "Archive downloaded".lightgreen return {source: source, filename: filename} end def unpack (meta) extract_dir = "#{meta[:filename]}.dir" target_dir = nil Dir.chdir CREW_BREW_DIR do puts "Unpacking archive, this may take awhile..." Dir.mkdir("#{extract_dir}") unless Dir.exist?("#{extract_dir}") if meta[:filename][-4,4] == ".zip" if @opt_verbose then system "unzip", "-v", "-d", "#{extract_dir}", meta[:filename] else system "unzip", "-qq", "-d", "#{extract_dir}", meta[:filename] end else if @opt_verbose then system "tar", "xvf", meta[:filename], "-C", "#{extract_dir}" else system "tar", "xf", meta[:filename], "-C", "#{extract_dir}" end end if meta[:source] == true # Check the number of directories in the archive entries = Dir["#{extract_dir}/*"] entries = Dir["#{extract_dir}/."] if entries.empty? if entries.length == 0 abort "Empty archive: #{meta[:filename]}".lightred elsif entries.length == 1 && File.directory?(entries.first) # Use `extract_dir/dir_in_archive` if there is only one directory. target_dir = entries.first else # Use `extract_dir` otherwise target_dir = extract_dir end else # Use `extract_dir` for binary distribution target_dir = extract_dir end end return CREW_BREW_DIR + target_dir end def build_and_preconfigure (target_dir) Dir.chdir target_dir do puts "Building from source, this may take a while..." # Remove /usr/local/lib64/*.la to resolve any "can not find /usr/local/*.la" compile errors when building the packages (only needed for x86_64). # https://gnunet.org/faq-la-files # https://stackoverflow.com/questions/42963653/libquadmath-la-is-not-a-valid-libtool-archive-when-configuring-openmpi-with-g if ARCH == 'x86_64' if @opt_verbose then system "rm -rvf #{CREW_LIB_PREFIX}/*.la" else system "rm -rf #{CREW_LIB_PREFIX}/*.la" end end @pkg.in_build = true @pkg.preinstall @pkg.patch @pkg.build @pkg.in_build = false # wipe crew destdir if @opt_verbose then system "rm -rvf #{CREW_DEST_DIR}/*" else system "rm -rf #{CREW_DEST_DIR}/*" end puts "Preconfiguring package..." @pkg.install end end def post_install (dest_dir) Dir.chdir dest_dir do puts "Performing post-install..." @pkg.postinstall end end def compress_doc (dir) # check whether crew should compress return if CREW_NOT_COMPRESS || !File.exist?("#{CREW_PREFIX}/bin/compressdoc") if Dir.exist? dir system "find #{dir} -type f ! -perm -200 | xargs -r chmod u+w" if @opt_verbose then system "compressdoc -v --gzip -9 #{dir}" else system "compressdoc --gzip -9 #{dir}" end end end def prepare_package (destdir) Dir.chdir destdir do # compress manual files compress_doc "#{destdir}#{CREW_PREFIX}/man" compress_doc "#{destdir}#{CREW_PREFIX}/info" compress_doc "#{destdir}#{CREW_PREFIX}/share/man" compress_doc "#{destdir}#{CREW_PREFIX}/share/info" # create directory list system "find . -type f > ../filelist" system "find . -type l >> ../filelist" system "cut -c2- ../filelist > filelist" # create file list system "find . -type d > ../dlist" system "cut -c2- ../dlist > dlistcut" system "tail -n +2 dlistcut > dlist" # remove temporary files if @opt_verbose then system "rm -vf dlistcut ../dlist ../filelist" else system "rm -f dlistcut ../dlist ../filelist" end end end def strip_find_files (find_cmd, strip_option = "") # check whether crew should strip return if CREW_NOT_STRIP || !File.exist?("#{CREW_PREFIX}/bin/strip") # run find_cmd and strip only ar or ELF files system "#{find_cmd} | xargs -r chmod u+w" system "#{find_cmd} | xargs -r sh -c 'for i in \"$0\" \"$@\"; do case \"$(head -c 4 $i)\" in ?ELF|\!?ar) echo \"$i\";; esac ; done' | xargs -r strip #{strip_option}" end def install_package (pkgdir) Dir.chdir pkgdir do FileUtils.mv 'dlist', CREW_CONFIG_PATH + "meta/#{@pkg.name}.directorylist" FileUtils.mv 'filelist', CREW_CONFIG_PATH + "meta/#{@pkg.name}.filelist" # Strip libraries with -S strip_find_files "find . -type f -name 'lib*.a' -print", "-S" strip_find_files "find . -type f -name 'lib*.so*' -print", "-S" # Strip binaries but not compressed archives strip_find_files "find . -type f ! -iname '*\.bz2' ! -iname '*\.gz' ! -iname '*\.lha' ! -iname '*\.rar' ! -iname '*\.tar' ! -iname '*\.tgz' ! -iname '*\.xz' ! -iname '*\.zip' -perm /111 -print | sed -e '/lib.*\.a$/d' -e '/lib.*\.so/d'" if @opt_verbose then system "tar cvf - ./usr/* | (cd /; tar xp --keep-directory-symlink -f -)" else system "tar cf - ./usr/* | (cd /; tar xp --keep-directory-symlink -f -)" end end end def resolve_dependencies_and_install begin origin = @pkg.name resolve_dependencies search origin, true install rescue InstallError => e abort "#{@pkg.name} failed to install: #{e.to_s}".lightred ensure # cleanup unless @opt_keep Dir.chdir CREW_BREW_DIR do if @opt_verbose then system "rm -rvf *" else system "rm -rf *" end system "mkdir dest" # this is a little ugly, feel free to find a better way end end end puts "#{@pkg.name.capitalize} installed!".lightgreen end def expand_dependencies @dependencies = [] def push_dependencies if @pkg.is_binary?(@device[:architecture]) || (!@pkg.in_upgrade && !@pkg.build_from_source && @device[:installed_packages].any? { |pkg| pkg[:name] == @pkg.name }) # retrieve name of dependencies that doesn't contain :build tag check_deps = @pkg.dependencies.select {|k, v| !v.include?(:build)}.map {|k, v| k} elsif @pkg.is_fake? # retrieve name of all dependencies check_deps = @pkg.dependencies.map {|k, v| k} else # retrieve name of all dependencies check_deps = @pkg.dependencies.map {|k, v| k} end # remove a dependent package which is equal to the target check_deps.select! {|name| @pkgName != name} # add new dependencies at the beginning of array @dependencies = check_deps.clone.concat(@dependencies) # check all dependencies recursively check_deps.each do |dep| search dep, true push_dependencies end end push_dependencies @dependencies.uniq end def resolve_dependencies dependencies = expand_dependencies # leave only not installed packages in dependencies dependencies.select! {|name| @device[:installed_packages].none? {|pkg| pkg[:name] == name}} return if dependencies.empty? puts "The following packages also need to be installed: " dependencies.each do |dep| print dep + " " end puts "" print "Do you agree? [Y/n] " response = STDIN.getc case response when "n" abort "No changes made." when "\n", "y", "Y" puts "Proceeding..." proceed = true else puts "I don't understand `#{response}`. :(".lightred abort "No changes made." end if proceed dependencies.each do |dep| search dep install end end end def install if !@pkg.in_upgrade && @device[:installed_packages].any? { |pkg| pkg[:name] == @pkg.name } puts "Package #{@pkg.name} already installed, skipping...".lightgreen return end unless @pkg.is_fake? meta = download target_dir = unpack meta if meta[:source] == true abort "You don't have a working C compiler. Run `crew install buildessential` to get one and try again.".lightred unless system("gcc", "--version") # build from source and place binaries at CREW_DEST_DIR # CREW_DEST_DIR contains usr/local/... hierarchy build_and_preconfigure target_dir # prepare filelist and dlist at CREW_DEST_DIR prepare_package CREW_DEST_DIR # use CREW_DEST_DIR dest_dir = CREW_DEST_DIR else # use extracted binary directory dest_dir = target_dir end # remove it just before the file copy if @pkg.in_upgrade puts "Removing since upgrade..." remove @pkg.name end # install filelist, dlist and binary files puts "Installing..." install_package dest_dir # perform post-install process post_install dest_dir end #add to installed packages @device[:installed_packages].push(name: @pkg.name, version: @pkg.version) File.open(CREW_CONFIG_PATH + 'device.json', 'w') do |file| output = JSON.parse @device.to_json file.write JSON.pretty_generate(output) end end def resolve_dependencies_and_build begin origin = @pkg.name # mark current package as which is required to compile from source @pkg.build_from_source = true resolve_dependencies search origin, true build_package Dir.pwd rescue InstallError => e abort "#{@pkg.name} failed to build: #{e.to_s}".lightred ensure #cleanup unless @opt_keep Dir.chdir CREW_BREW_DIR do if @opt_verbose then system "rm -rvf *" else system "rm -rf *" end system "mkdir dest" #this is a little ugly, feel free to find a better way end end end puts "#{@pkg.name} is built!".lightgreen end def build_package (pwd) abort "It is not possible to build a fake package".lightred if @pkg.is_fake? abort "It is not possible to build without source".lightred if !@pkg.is_source?(@device[:architecture]) # download source codes and unpack it meta = download target_dir = unpack meta # build from source and place binaries at CREW_DEST_DIR build_and_preconfigure target_dir # call check method here. this check method is called by this function only, # therefore it is possible place time consuming tests in the check method. if Dir.exist? target_dir Dir.chdir target_dir do puts "Checking..." @pkg.check end end # prepare filelist and dlist at CREW_DEST_DIR prepare_package CREW_DEST_DIR # build package from filelist, dlist and binary files in CREW_DEST_DIR puts "Archiving..." archive_package pwd end def archive_package (pwd) pkg_name = "#{@pkg.name}-#{@pkg.version}-chromeos-#{@device[:architecture]}.tar.xz" Dir.chdir CREW_DEST_DIR do if @opt_verbose then system "tar cJvf #{pwd}/#{pkg_name} *" else system "tar cJf #{pwd}/#{pkg_name} *" end end Dir.chdir pwd do system "sha256sum #{pkg_name} > #{pkg_name}.sha256" end end def remove (pkgName) #make sure the package is actually installed unless @device[:installed_packages].any? { |pkg| pkg[:name] == pkgName } puts "Package #{pkgName} isn't installed.".lightred return end #if the filelist exists, remove the files and directories installed by the package if File.file?("#{CREW_CONFIG_PATH}meta/#{pkgName}.filelist") Dir.chdir CREW_CONFIG_PATH do #remove all files installed by the package File.open("meta/#{pkgName}.filelist").each_line do |line| begin puts "Removing file " + line.chomp + "".lightred if @opt_verbose File.unlink line.chomp rescue => exception #swallow exception end end #remove all directories installed by the package File.readlines("meta/#{pkgName}.directorylist").reverse.each do |line| begin puts "Removing directory " + line.chomp + "".lightred if @opt_verbose Dir.rmdir line.chomp rescue => exception #swallow exception end end #remove the file and directory list File.unlink "meta/#{pkgName}.filelist" File.unlink "meta/#{pkgName}.directorylist" end end #remove from installed packages puts "Removing package " + pkgName + "".lightred if @opt_verbose @device[:installed_packages].each do |elem| @device[:installed_packages].delete elem if elem[:name] == pkgName end #update the device manifest File.open(CREW_CONFIG_PATH + 'device.json', 'w') do |file| out = JSON.parse @device.to_json file.write JSON.pretty_generate(out) end puts "#{pkgName.capitalize} removed!".lightgreen end def build_command (args) args["<name>"].each do |name| @pkgName = name search @pkgName resolve_dependencies_and_build end end def download_command (args) args["<name>"].each do |name| @pkgName = name search @pkgName download end end def files_command (args) args["<name>"].each do |name| @pkgName = name search @pkgName files name end end def help_command (args) if args["<command>"] help args["<command>"] else puts "Usage: crew help <command>" help nil end end def install_command (args) args["<name>"].each do |name| @pkgName = name search @pkgName @pkg.build_from_source = true if @opt_src resolve_dependencies_and_install end end def list_command (args) if args['available'] list_available elsif args['installed'] list_installed end end def remove_command (args) args["<name>"].each do |name| remove name end end def search_command (args) args["<name>"].each do |name| regexp_search name end.empty? and begin list_packages end end def update_command (args) update end def upgrade_command (args) args["<name>"].each do |name| @pkgName = name search @pkgName @pkg.build_from_source = true if @opt_src upgrade end.empty? and begin upgrade end end def whatprovides_command (args) args["<name>"].each do |name| whatprovides name end end def is_command (name) return false if name =~ /^[-<]/ return true end command_name = args.find { |k, v| v && is_command(k) } [0] function = command_name + "_command" send(function, args)