mirror of
https://github.com/OpenNebula/one.git
synced 2026-11-09 05:27:20 +09:00
897 lines
25 KiB
Ruby
897 lines
25 KiB
Ruby
# -------------------------------------------------------------------------- #
|
|
# Copyright 2002-2020, OpenNebula Project, OpenNebula Systems #
|
|
# #
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
|
# not use this file except in compliance with the License. You may obtain #
|
|
# a copy of the License at #
|
|
# #
|
|
# http://www.apache.org/licenses/LICENSE-2.0 #
|
|
# #
|
|
# Unless required by applicable law or agreed to in writing, software #
|
|
# distributed under the License is distributed on an "AS IS" BASIS, #
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
|
# See the License for the specific language governing permissions and #
|
|
# limitations under the License. #
|
|
#--------------------------------------------------------------------------- #
|
|
|
|
require 'csv'
|
|
|
|
# CLI Helper
|
|
module CLIHelper
|
|
|
|
# Available operators for filtering operations
|
|
FILTER_OPS = %w[= != < <= > >= ~]
|
|
|
|
# CLI general options
|
|
LIST = {
|
|
:name => 'list',
|
|
:short => '-l x,y,z',
|
|
:large => '--list x,y,z',
|
|
:format => Array,
|
|
:description => 'Selects columns to display with list command'
|
|
}
|
|
|
|
LISTCONF = {
|
|
:name => 'listconf',
|
|
:short => '-c conf',
|
|
:large => '--listconf conf',
|
|
:format => String,
|
|
:description => 'Selects a predefined column list'
|
|
}
|
|
|
|
CSV_OPT = {
|
|
:name => 'csv',
|
|
:large => '--csv',
|
|
:description => 'Write table in csv format'
|
|
}
|
|
|
|
CSV_DEL = {
|
|
:name => 'csv_del',
|
|
:large => '--csv-del del',
|
|
:format => String,
|
|
:description => 'Set delimiter for csv output'
|
|
}
|
|
|
|
FILTER = {
|
|
:name => 'filter',
|
|
:short => '-f x,y,z',
|
|
:large => '--filter x,y,z',
|
|
:format => Array,
|
|
:description => "Filter data. An array is specified with\n" <<
|
|
' ' * 31 << 'column=value pairs.' <<
|
|
' ' * 31 << "Valid operators #{FILTER_OPS.join(',')}" <<
|
|
' ' * 31 << 'e.g. NAME=test (match name with test)' <<
|
|
' ' * 31 << 'NAME~test (match test, te, tes..)'
|
|
}
|
|
|
|
OPERATOR = {
|
|
:name => 'operator',
|
|
:large => '--operator operator',
|
|
:format => String,
|
|
:description => 'Logical operator used on filters: AND, OR. ' \
|
|
'Default: AND.'
|
|
}
|
|
|
|
NO_HEADER = {
|
|
:name => 'no_header',
|
|
:large => '--no-header',
|
|
:description => 'Hides the header of the table'
|
|
}
|
|
|
|
DELAY = {
|
|
:name => 'delay',
|
|
:short => '-d x',
|
|
:large => '--delay x',
|
|
:format => Integer,
|
|
:description => 'Sets the delay in seconds for top command'
|
|
}
|
|
|
|
NO_PAGER = {
|
|
:name => 'no_pager',
|
|
:large => '--no-pager',
|
|
:description => 'Disable pagination',
|
|
:proc => lambda {|_o, _options|
|
|
ENV['ONE_PAGER'] = 'cat' if File.exist?('/bin/cat')
|
|
}
|
|
}
|
|
|
|
ADJUST = {
|
|
:name => 'adjust',
|
|
:large => '--adjust x,y,z',
|
|
:format => Array,
|
|
:description => 'Adjust size to not truncate selected columns'
|
|
}
|
|
|
|
SIZE = {
|
|
:name => 'size',
|
|
:short => '-s x=size,y=size',
|
|
:large => '--size x=size,y=size',
|
|
:format => Array,
|
|
:description => 'Change the size of selected columns. ' \
|
|
'For example: ' \
|
|
'$ onevm list --size "name=20" ' \
|
|
'will make column name size 20.'
|
|
}
|
|
|
|
EXPAND = {
|
|
:name => 'expand',
|
|
:large => '--expand [x=prop,y=prop]',
|
|
:format => Array,
|
|
:description => 'Expands the columns size to fill the terminal. ' \
|
|
'For example: ' \
|
|
'$onevm list --expand name=0.4,group=0.6 ' \
|
|
'will expand name 40% and group 60%. ' \
|
|
'$onevm list --expand name,group ' \
|
|
'will expand name and group based on its size.' \
|
|
'$onevm list --expand will expand all columns.'
|
|
}
|
|
|
|
NO_EXPAND = {
|
|
:name => 'no_expand',
|
|
:large => '--no-expand',
|
|
:description => 'Disable expand'
|
|
}
|
|
|
|
OPTIONS = [LIST,
|
|
LISTCONF,
|
|
DELAY,
|
|
FILTER,
|
|
OPERATOR,
|
|
CSV_OPT,
|
|
CSV_DEL,
|
|
NO_PAGER,
|
|
NO_HEADER,
|
|
ADJUST,
|
|
SIZE,
|
|
EXPAND,
|
|
NO_EXPAND]
|
|
|
|
# Sets bold font
|
|
def self.scr_bold
|
|
print "\33[1m"
|
|
end
|
|
|
|
# Sets underline
|
|
def self.scr_underline
|
|
print "\33[4m"
|
|
end
|
|
|
|
# Restore normal font
|
|
def self.scr_restore
|
|
print "\33[0m"
|
|
end
|
|
|
|
# Clears screen
|
|
def self.scr_cls
|
|
print "\33[2J\33[H"
|
|
end
|
|
|
|
# Moves the cursor
|
|
#
|
|
# @param cord_x [Integer] Coordinate x
|
|
# @param cord_y [Integer] Coordinate y
|
|
def self.scr_move(cord_x, cord_y)
|
|
print "\33[#{cord_x};#{cord_y}H"
|
|
end
|
|
|
|
# CLI state colors
|
|
ANSI_RED = "\33[31m"
|
|
ANSI_GREEN = "\33[32m"
|
|
ANSI_RESET = "\33[0m"
|
|
ANSI_YELLOW = "\33[33m"
|
|
|
|
# CLI states
|
|
OK_STATES = %w[runn rdy on configured SUCCESS RUNNING]
|
|
BAD_STATES = %w[fail
|
|
err
|
|
error
|
|
ERROR
|
|
FAILED_DEPLOYING
|
|
FAILED_UNDEPLOYING
|
|
FAILED_SCALING]
|
|
REGULAR_STATES = %w[pending WARNING]
|
|
|
|
# Set state color
|
|
#
|
|
# @param stat [String] Current state
|
|
def self.color_state(state)
|
|
if $stdout.tty?
|
|
case state.strip
|
|
when *OK_STATES
|
|
ANSI_GREEN + state + ANSI_RESET
|
|
when *BAD_STATES
|
|
ANSI_RED + state + ANSI_RESET
|
|
when *REGULAR_STATES
|
|
ANSI_YELLOW + state + ANSI_RESET
|
|
else
|
|
state
|
|
end
|
|
else
|
|
state
|
|
end
|
|
end
|
|
|
|
# Print header
|
|
#
|
|
# @param str [String] String with header content
|
|
# @param underline [Boolean] True to underline the header
|
|
def self.print_header(str, underline = true)
|
|
if $stdout.tty?
|
|
print_tty_header(str, underline)
|
|
else
|
|
print str
|
|
end
|
|
|
|
puts
|
|
end
|
|
|
|
# Print pretty header
|
|
#
|
|
# @param str [String] String with header content
|
|
# @param underline [Boolean] True to underline the header
|
|
def self.print_tty_header(str, underline = true)
|
|
scr_bold
|
|
scr_underline if underline
|
|
print str
|
|
scr_restore
|
|
end
|
|
|
|
# Show error message and exit with error
|
|
#
|
|
# @param message [String] Error message to show
|
|
def self.fail(message)
|
|
STDERR.puts message
|
|
|
|
exit(-1)
|
|
end
|
|
|
|
# Check if value is in base64
|
|
#
|
|
# @param value [String] Value to check
|
|
#
|
|
# @return [Boolean] True if it's base64
|
|
def self.base64?(value)
|
|
re = %r(^([A-Za-z0-9+\/]{4})*([A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}==)?$)
|
|
|
|
!value.match(re).nil?
|
|
end
|
|
|
|
# Hash with search
|
|
module HashWithSearch
|
|
|
|
# Search inside path
|
|
#
|
|
# @param path [String] Path to search on
|
|
def dsearch(path)
|
|
stems = path.split('/')
|
|
hash = self
|
|
|
|
stems.delete_if {|s| s.nil? || s.empty? }
|
|
|
|
stems.each do |stem|
|
|
if hash.is_a? Hash
|
|
if hash[stem]
|
|
hash = hash[stem]
|
|
else
|
|
hash = nil
|
|
break
|
|
end
|
|
else
|
|
hash = nil
|
|
break
|
|
end
|
|
end
|
|
|
|
hash
|
|
end
|
|
|
|
end
|
|
|
|
# Show table
|
|
class ShowTable
|
|
|
|
require 'yaml'
|
|
|
|
attr_reader :default_columns
|
|
|
|
# Class constructor
|
|
#
|
|
# @param conf [String] Configuration file
|
|
# @param ext [Helper] Cli helper information
|
|
def initialize(conf = nil, ext = nil, &block)
|
|
@columns = {}
|
|
@default_columns = []
|
|
|
|
@ext = ext
|
|
@conf = conf
|
|
|
|
instance_eval(&block)
|
|
end
|
|
|
|
# Get the helper
|
|
def helper
|
|
@ext
|
|
end
|
|
|
|
# Fill column attributes
|
|
#
|
|
# @param name [String] Column name
|
|
# @param desc [String] Column description
|
|
# @param conf [Array] Configutation attributes
|
|
def column(name, desc, *conf, &block)
|
|
column = {}
|
|
|
|
column[:desc] = desc
|
|
column[:size] = 5
|
|
|
|
conf.each do |c|
|
|
case c
|
|
when Symbol
|
|
column[c] = true
|
|
when Hash
|
|
c.each do |key, value|
|
|
column[key] = value
|
|
end
|
|
end
|
|
end
|
|
|
|
column[:proc] = block
|
|
|
|
@columns[name.to_sym] = column
|
|
|
|
@default_columns << name
|
|
end
|
|
|
|
# Get default columns
|
|
#
|
|
# @param args [Array] Array with default columns
|
|
def default(*args)
|
|
args.map! {|a| a.to_sym }
|
|
|
|
@default_columns = args
|
|
end
|
|
|
|
# Show resource
|
|
#
|
|
# @param data [Hash/Object] Data to show
|
|
# @param options [Hash] Object with CLI user options
|
|
# @param top [Boolean] True to not update columns again
|
|
def show(data, options = {}, top = false)
|
|
if options[:list]
|
|
@cli_columns = options[:list].collect {|o| o.upcase.to_sym }
|
|
else
|
|
@cli_columns = @default_columns
|
|
end
|
|
|
|
update_columns(options) unless top
|
|
|
|
if data.is_a? Hash
|
|
@data = data
|
|
|
|
@data.extend(HashWithSearch)
|
|
|
|
pool = @data.keys.first
|
|
|
|
return print_table(data, options) unless pool
|
|
|
|
element = pool.split('_')[0..-2].join('_')
|
|
|
|
pool_data = @data.dsearch("#{pool}/#{element}")
|
|
|
|
if pool_data
|
|
pool_data = [pool_data].flatten
|
|
else
|
|
pool_data = []
|
|
end
|
|
|
|
print_table(pool_data, options)
|
|
else
|
|
data ||= []
|
|
|
|
print_table(data, options)
|
|
end
|
|
end
|
|
|
|
# Show resource continuosly
|
|
#
|
|
# @param options [Hash] Object with CLI user options
|
|
def top(options = {})
|
|
delay = options[:delay] || 1
|
|
top = false
|
|
|
|
begin
|
|
loop do
|
|
data = yield
|
|
|
|
CLIHelper.scr_cls
|
|
CLIHelper.scr_move(0, 0)
|
|
|
|
show(data, options, top)
|
|
|
|
sleep delay
|
|
|
|
top = true
|
|
end
|
|
rescue SystemExit, Interrupt, StandardError => e
|
|
CLIHelper.fail(e.message)
|
|
end
|
|
end
|
|
|
|
# Show column description
|
|
def describe_columns
|
|
str = '%-20s: %-20s'
|
|
|
|
@columns.each do |column, d|
|
|
puts format(str, column, d[:desc])
|
|
end
|
|
end
|
|
|
|
# Get maximum string lenght in column
|
|
#
|
|
# @param index [Integer] Column index to search
|
|
#
|
|
# @return [Integer] Maximum length
|
|
def max_size(index)
|
|
sizes = []
|
|
|
|
@res_data.each do |d|
|
|
sizes << d[index].size
|
|
end
|
|
|
|
sizes.max
|
|
end
|
|
|
|
# Get total size of all columns
|
|
#
|
|
# @param columns [Array] Array with columns name
|
|
#
|
|
# @return [Integer] Total size
|
|
def total_columns_size(columns)
|
|
size = 0
|
|
|
|
columns.each do |c|
|
|
size += @columns[c[:name]][:size]
|
|
end
|
|
|
|
size
|
|
end
|
|
|
|
# Get columns information
|
|
#
|
|
# @param columns [Array] Array with columns information
|
|
#
|
|
# @return [Array] Array with columns objects
|
|
def columns_info(columns)
|
|
ret = []
|
|
|
|
columns.each do |column|
|
|
data = column.to_s.split('=')
|
|
|
|
ret << { :name => data[0].upcase.to_sym, :prop => data[1] }
|
|
end
|
|
|
|
ret
|
|
end
|
|
|
|
# Print tty header
|
|
def print_tty_header
|
|
CLIHelper.print_tty_header(header_str)
|
|
|
|
puts
|
|
end
|
|
|
|
private
|
|
|
|
# Get header in string format
|
|
def header_str
|
|
@cli_columns.collect do |c|
|
|
if @columns[c]
|
|
format_str(c, c.to_s)
|
|
else
|
|
CLIHelper.fail("Column #{c} not defined.")
|
|
end
|
|
end.compact.join(' ')
|
|
end
|
|
|
|
# Print data in table format
|
|
#
|
|
# @param data [Array] Array with data to show
|
|
# @param options [Hash] Object with CLI user options
|
|
def print_table(data, options)
|
|
@res_data = data_array(data, options)
|
|
|
|
update_columns_size(options)
|
|
|
|
options[:csv_del] ? del = options[:csv_del] : del = ','
|
|
|
|
if !options[:csv] && (!options.key? :no_header)
|
|
CLIHelper.print_header(header_str)
|
|
elsif options[:csv] && (!options.key? :no_header)
|
|
puts CSV.generate_line(@cli_columns, :col_sep => del)
|
|
end
|
|
|
|
@res_data ? print_data(@res_data, options) : puts
|
|
end
|
|
|
|
# Print data
|
|
#
|
|
# @param data [Array] Array with data to show
|
|
# @param options [Hash] Object with CLI user options
|
|
def print_data(data, options)
|
|
if options[:csv]
|
|
print_csv_data(data, options[:csv_del])
|
|
else
|
|
print_normal_data(data, options[:stat_column])
|
|
end
|
|
rescue Errno::EPIPE => e
|
|
CLIHelper.fail(e.message)
|
|
end
|
|
|
|
# Print data in CSV format
|
|
#
|
|
# @param data [Array] Array with data to show
|
|
# @param del [Char] CSV delimiter
|
|
def print_csv_data(data, del)
|
|
del ? del = del : del = ','
|
|
|
|
data.each do |l|
|
|
result = []
|
|
|
|
@cli_columns.each do |col|
|
|
result << l[@default_columns.index(col)]
|
|
end
|
|
|
|
puts CSV.generate_line(result, :col_sep => del)
|
|
end
|
|
end
|
|
|
|
# Print data in normal format
|
|
#
|
|
# @param data [Array] Array with data to show
|
|
# @param stat_column [String] Name of the state column
|
|
def print_normal_data(data, stat_column)
|
|
if stat_column
|
|
stat = stat_column.upcase.to_sym
|
|
else
|
|
stat = :STAT
|
|
end
|
|
|
|
data.each do |l|
|
|
result = []
|
|
|
|
@cli_columns.each do |col|
|
|
i = @default_columns.index(col)
|
|
|
|
# Column might not exist
|
|
next unless i
|
|
|
|
dat = l[i]
|
|
|
|
str = format_str(col, dat)
|
|
|
|
str = CLIHelper.color_state(str) if col == stat
|
|
|
|
result << str
|
|
end
|
|
puts result.join(' ').rstrip
|
|
end
|
|
end
|
|
|
|
# Get data in array format
|
|
#
|
|
# @param data [Array] Array with data to show
|
|
# @param options [Hash] Object with CLI user options
|
|
#
|
|
# @return [Array] Array with selected columns information
|
|
def data_array(data, options)
|
|
# Take default table columns and desired ones by the user
|
|
cols = @default_columns
|
|
|
|
@cli_columns.each do |col|
|
|
next if @default_columns.include?(col)
|
|
|
|
@default_columns.insert(@cli_columns.index(col) + 1, col)
|
|
end
|
|
|
|
res_data = data.collect do |d|
|
|
cols.collect do |c|
|
|
@columns[c][:proc].call(d).to_s if @columns[c]
|
|
end
|
|
end
|
|
|
|
if options
|
|
filter_data!(res_data, options) if options[:filter]
|
|
end
|
|
|
|
res_data
|
|
end
|
|
|
|
# Format data as string
|
|
#
|
|
# @param field [Symbol] Name of the column to format
|
|
# @param data [String] Data to format
|
|
def format_str(field, data)
|
|
if @columns[field]
|
|
@columns[field][:left] ? minus = '-' : minus = ''
|
|
size = @columns[field][:size]
|
|
|
|
if @columns[field][:adjust]
|
|
format("%#{minus}#{size}s", data.to_s)
|
|
else
|
|
format("%#{minus}#{size}.#{size}s", data.to_s)
|
|
end
|
|
else
|
|
CLIHelper.fail("Column #{field} not defined.")
|
|
end
|
|
end
|
|
|
|
# Get expand information from configuration files
|
|
#
|
|
# @return [Array] Array with columns data
|
|
def config_expand_data
|
|
ret = []
|
|
|
|
@cli_columns.each do |column|
|
|
expand_c = @columns[column][:expand]
|
|
|
|
next unless expand_c
|
|
|
|
column = column.to_s.downcase
|
|
|
|
ret << "#{column}=#{expand_c}" if expand_c.is_a? Numeric
|
|
|
|
ret << column.to_s.downcase unless expand_c.is_a? Numeric
|
|
end
|
|
|
|
ret
|
|
end
|
|
|
|
# Get adjust information from configuration files
|
|
#
|
|
# @return [Array] Array with columns data
|
|
def config_adjust_data
|
|
ret = []
|
|
|
|
@cli_columns.each do |column|
|
|
next unless @columns[column][:adjust]
|
|
|
|
ret << column.to_s.downcase
|
|
end
|
|
|
|
ret
|
|
end
|
|
|
|
# Change the size of expand columns
|
|
#
|
|
# @param expand_columns [Array] Array with expand columns names
|
|
# @param all [Boolean] True to expand all columns
|
|
def expand_columns(expand_columns, all = false)
|
|
return if expand_columns.empty?
|
|
|
|
if $stdout.isatty
|
|
terminal_size = $stdout.winsize[1]
|
|
elsif IO.console && IO.console.tty?
|
|
terminal_size = IO.console.winsize[1]
|
|
else
|
|
terminal_size = nil
|
|
end
|
|
|
|
return if terminal_size.nil?
|
|
|
|
default_columns = columns_info(@cli_columns)
|
|
expand_columns = columns_info(expand_columns)
|
|
|
|
total_size = total_columns_size(default_columns)
|
|
columns_size = total_columns_size(expand_columns)
|
|
|
|
terminal_size -= (@cli_columns.size - 1)
|
|
left_size = terminal_size - total_size
|
|
remaining_size = left_size
|
|
|
|
return if left_size <= 0
|
|
|
|
expand_columns.each do |c|
|
|
column = c[:name]
|
|
prop = c[:prop]
|
|
|
|
if @columns[column].nil?
|
|
CLIHelper.fail("Unknown column #{column}")
|
|
end
|
|
|
|
if all
|
|
prop = @columns[column][:size] / total_size.to_f
|
|
elsif !prop
|
|
prop = @columns[column][:size] / columns_size.to_f
|
|
end
|
|
|
|
size = (left_size * prop.to_f).round
|
|
|
|
@columns[column][:adjust] = false
|
|
@columns[column][:size] += size
|
|
|
|
remaining_size -= size
|
|
end
|
|
|
|
if remaining_size > 0
|
|
# If there is some left space, add it to the last column
|
|
@columns[expand_columns[-1][:name]][:size] += remaining_size
|
|
end
|
|
|
|
return if terminal_size == total_columns_size(default_columns)
|
|
|
|
# If there is extra space, sub it from the last column
|
|
diff = total_columns_size(default_columns) - terminal_size
|
|
|
|
@columns[expand_columns[-1][:name]][:size] -= diff
|
|
end
|
|
|
|
# Change columns size
|
|
#
|
|
# @param options [Array] Array with size information for each column
|
|
def size_columns(options)
|
|
options[:size].each do |column|
|
|
data = column.split('=')
|
|
column = data[0].upcase.to_sym
|
|
size = data[1].to_i
|
|
|
|
@columns[column][:adjust] = false
|
|
@columns[column][:size] = size
|
|
end
|
|
end
|
|
|
|
# Update columns size information
|
|
#
|
|
# @param options [Hash] Object with CLI user options
|
|
def update_columns_size(options)
|
|
adjust = options[:adjust]
|
|
|
|
if adjust
|
|
adjust += config_adjust_data
|
|
else
|
|
adjust = config_adjust_data
|
|
end
|
|
|
|
expand_all = (options.key? :expand) && options[:expand].nil?
|
|
expand = !options[:expand].nil?
|
|
expand_data = []
|
|
|
|
if expand_all
|
|
expand_data = @cli_columns
|
|
elsif expand
|
|
expand_data += options[:expand]
|
|
end
|
|
|
|
expand_data += config_expand_data
|
|
|
|
c_size = options[:size]
|
|
|
|
# Update adjust attribute to not truncate the column
|
|
# If expand all columns adjust is ignored
|
|
unless expand_all
|
|
adjust.each do |column|
|
|
column = column.upcase.to_sym
|
|
size = max_size(@cli_columns.index(column))
|
|
|
|
if size && size > @columns[column][:size]
|
|
@columns[column][:adjust] = true
|
|
@columns[column][:size] = size
|
|
end
|
|
end
|
|
end
|
|
|
|
# Update size attribute if size
|
|
size_columns(options) if c_size
|
|
|
|
# Update size attribute if expand
|
|
expand_columns(expand_data) unless options.key? :no_expand
|
|
end
|
|
|
|
# Update columns information
|
|
#
|
|
# @param options [Hash] Object with CLI user options
|
|
def update_columns(options)
|
|
begin
|
|
if @conf
|
|
config = YAML.load_file(@conf)
|
|
else
|
|
config = {}
|
|
end
|
|
|
|
default = config.delete(:default) || {}
|
|
default_conf = config.delete(:default_conf) || {}
|
|
listconf = options[:listconf]
|
|
|
|
listconf = default_conf[listconf.to_sym] if listconf
|
|
|
|
if !listconf && ENV['ONE_LISTCONF']
|
|
listconf = default_conf[ENV['ONE_LISTCONF'].to_sym]
|
|
end
|
|
|
|
if listconf
|
|
@default_columns = listconf
|
|
else
|
|
@default_columns = default unless default.empty?
|
|
end
|
|
|
|
# Filter show options with available columns
|
|
@default_columns &= @columns.keys
|
|
|
|
@columns.merge!(config) do |_key, oldval, newval|
|
|
oldval.merge(newval)
|
|
end
|
|
rescue StandardError => e
|
|
CLIHelper.fail(e.message)
|
|
end
|
|
end
|
|
|
|
# Filter data
|
|
#
|
|
# @param data [Array] Array with data to filter
|
|
# @param options [Hash] Object with CLI user options
|
|
def filter_data!(data, options)
|
|
operators = /(#{FILTER_OPS.join('|')})/
|
|
filter = options[:filter]
|
|
|
|
if options.key?(:operator)
|
|
log_operator = options[:operator].upcase
|
|
else
|
|
log_operator = 'AND'
|
|
end
|
|
|
|
stems = filter.map do |s|
|
|
m = s.match(/^(.*?)#{operators}(.*?)$/)
|
|
|
|
if m
|
|
index = @default_columns.index(m[1].upcase.to_sym)
|
|
|
|
if index
|
|
{
|
|
:left => m[1],
|
|
:operator => m[2],
|
|
:right => m[3],
|
|
:index => index
|
|
}
|
|
else
|
|
CLIHelper.fail("Column '#{m[1]}' not found")
|
|
end
|
|
else
|
|
CLIHelper.fail("Expression '#{s}' incorrect")
|
|
end
|
|
end
|
|
|
|
data.select! do |d|
|
|
pass = true
|
|
|
|
stems.each do |s|
|
|
case s[:operator]
|
|
when '='
|
|
op = '=='
|
|
when '~'
|
|
op = 'include?'
|
|
else
|
|
op = s[:operator]
|
|
end
|
|
|
|
if d[s[:index]].public_send(op, s[:right])
|
|
if log_operator == 'OR'
|
|
pass = true
|
|
|
|
break
|
|
end
|
|
else
|
|
pass = false
|
|
|
|
break if log_operator == 'AND'
|
|
end
|
|
end
|
|
|
|
pass
|
|
end
|
|
end
|
|
|
|
# TODO: def sort_data!(data, options)
|
|
|
|
end
|
|
|
|
end
|