Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/parser/arguments.rb
Views: 11623
# -*- coding: binary -*-1# frozen_string_literal: true2require 'shellwords'34module Rex5module Parser6###7#8# This class parses arguments in a getopt style format, kind of.9# Unfortunately, the default ruby getopt implementation will only10# work on ARGV, so we can't use it.11#12###13class Arguments14# e.g. '-x' or '-xyz'15SHORT_FLAG = /^-[a-zA-Z]+$/.freeze16private_constant :SHORT_FLAG17# e.g. '--verbose', '--very-verbose' or '--very-very-verbose'18LONG_FLAG = /^--([a-zA-Z]+)(-[a-zA-Z]+)*$/.freeze19private_constant :LONG_FLAG2021#22# Initializes the format list with an array of formats like:23#24# Arguments.new(25# '-z' => [ has_argument, "some text", "<argument_description>" ],26# '-b' => [ false, "some text" ],27# ['-b'] => [ false, "some text" ],28# ['-x', '--execute'] => [ true, "mixing long and short args" ],29# ['-t', '--test'] => [ true, "testing custom <opt> value", "<arg_to_test>" ],30# ['--long-flag'] => [ false, "sample long flag" ]31# )32#33def initialize(fmt)34normalised_fmt = fmt.map { |key, metadata| [Array(key), metadata] }.to_h35self.fmt = normalised_fmt36self.longest = normalised_fmt.each_pair.map { |key, value| key.flatten.join(', ') + (value[0] ? ' ' + value[2].to_s : '') }.max_by(&:length)37end3839#40# Takes a string and converts it into an array of arguments.41#42def self.from_s(str)43Shellwords.shellwords(str)44end4546#47# Parses the supplied arguments into a set of options.48#49def parse(args, &_block)50skip_next = 05152args.each_with_index do |arg, idx|53if skip_next > 054skip_next -= 155next56end5758param = nil59if arg =~ SHORT_FLAG60# parsing needs to take into account a couple requirements61# 1. longest `short` flag found in 'arg' should be extracted first62# * consider passing the short flag to a tokenizer that returns a list of tokens in order with any invalid tokens63# 2. any short flag arguments that need an option will consume the next option from the list64short_args_from_token(arg).each do |letter|65next unless include?("-#{letter}")6667if arg_required?("-#{letter}")68skip_next += 169param = args[idx + skip_next]70end7172yield "-#{letter}", idx, param73end74elsif arg =~ LONG_FLAG && include?(arg)75if arg_required?(arg)76skip_next = 177param = args[idx + skip_next]78end7980# Try to yield the short hand version of our argument if possible81# This will result in less areas of code that would need to be changed82to_return = short_arg_from_long_arg(arg)83if to_return.nil?84yield arg, idx, param85else86yield to_return, idx, param87end88else89# else treat the passed in flag as argument90yield nil, idx, arg91end92end93end9495#96# Returns usage information for this parsing context.97#98def usage99txt = ["\nOPTIONS:\n"]100101fmt.sort_by { |key, _metadata| key.to_s.downcase }.each do |key, val|102# if the arg takes in a parameter, get parameter string103opt = val[0] ? " #{val[2]}" : ''104105# Get all arguments for a command106output = key.join(', ')107output += opt108109# Left align the fmt options and <opt> string110aligned_option = " #{output.ljust(longest.length)}"111txt << "#{aligned_option} #{val[1]}"112end113114txt << ""115txt.join("\n")116end117118def include?(search)119fmt.keys.flatten.include?(search)120end121122def arg_required?(opt)123value = select_value_from_fmt_option(opt)124return false if value.nil?125126value.first127end128129def option_keys130fmt.keys.flatten131end132133# Return new Parser object featuring options from the base object and including the options hash that was passed in134def merge(to_merge)135return fmt unless to_merge.is_a?(Hash)136137Rex::Parser::Arguments.new(fmt.clone.merge(to_merge))138end139140private141142attr_accessor :fmt # :nodoc:143attr_accessor :longest # :nodoc:144145def select_value_from_fmt_option(option)146fmt_option = fmt.find { |key, value| value if key.include?(option) }147return if fmt_option.nil?148149fmt_option[1]150end151152# Returns the short-flag equivalent of any option passed in, if one exists153# Returns nil if one does not exist154def short_arg_from_long_arg(long_arg)155fmt_option = fmt.find { |key, value| value if key.include?(long_arg) }.first156# if fmt_option == [long_arg] that means that a short flag option for it does not exist157return if fmt_option.nil? || fmt_option == [long_arg]158159fmt_option.each { |opt| return opt if opt =~ SHORT_FLAG }160end161162# Parsing takes into account longest `short` flag found in 'arg' should as extracted first163#164# Returns Array of short arguments found in `arg`165def short_args_from_token(arg)166compare_arg = arg.dup[1..-1]167short_args = []168found_args = {}169fmt.keys.each do |keys|170if keys.first =~ SHORT_FLAG171short_args << keys.first[1..-1]172end173end174short_args.sort_by! { |value| value.downcase }.reverse!175short_args.each do |short_arg|176break if compare_arg.empty?177if compare_arg.include? short_arg178found_args[arg.index(short_arg)] = short_arg179compare_arg.gsub!(short_arg, '')180end181end182found_args.sort_by { |key, _value| key }.to_h.values183end184end185end186end187188189