module CTioga2::Utils

Various utilities

Constants

NaturalSubdivisions
UNCOMPRESSORS

A constant holding a relation extension -> command to decompress (to be fed to sprintf with the filename as argument)

Public Class Methods

average_vector(vect, istart, iend, n = 1) click to toggle source

Returns the nth first modes

# File lib/ctioga2/utils.rb, line 177
def self.average_vector(vect, istart, iend, n = 1)
  rv = [0] * n
  nb = 0
  istart.upto(iend) do |i|
    v = 1
    y = vect[i]
    n.times do |j|
      v *= y
      rv[j] += v
    end
    nb += 1
  end

  return rv.map do |v|
    v/nb
  end
end
chdir(dir, &blk) click to toggle source

An instrumentized version of Dir::chdir

# File lib/ctioga2/utils.rb, line 239
def self.chdir(dir, &blk)
  @current_dirs ||= []
  @current_dirs << Pathname.new(dir)
  Dir::chdir(dir, &blk)
  @current_dirs.pop
end
closest_subdivision(x, what = :closest) click to toggle source

Returns the closest element of the correct power of ten of NaturalSubdivisions above or below the given number.

If what is :below, returns the closest below. If what is :above, returns the closest above. Else, returns the one the closest between the two values.

# File lib/ctioga2/utils.rb, line 318
def self.closest_subdivision(x, what = :closest)
  fact = 10**(Math.log10(x).floor)

  normed_x = x/fact
  (NaturalSubdivisions.size()-1).times do |i|
    if normed_x == NaturalSubdivisions[i]
      return x
    end
    if (normed_x > NaturalSubdivisions[i]) && 
       (normed_x < NaturalSubdivisions[i+1])
      below = NaturalSubdivisions[i]*fact
      above = NaturalSubdivisions[i+1]*fact
      if what == :below
        return below
      elsif what == :above
        return above
      else
        if x*x/(below * above) > 1
          return above
        else
          return below
        end
      end
    end
  end
  raise "Should not reach this !"
end
cluster_by_value(list, funcall) click to toggle source

Cluster a series of objects by the values returned by the given funcall. It returns an array of arrays where the elements are in the same order, and in each sub-array, the functions all return the same value

@todo with block too ?

# File lib/ctioga2/utils.rb, line 500
def self.cluster_by_value(list, funcall)
  if list.size == 0
    return []
  end
  ret = [ [list[0]] ]
  cur = ret[0]
  last = cur.first.send(funcall)

  for o in list[1..-1]
    val = o.send(funcall)
    if last == val
      cur << o
    else
      cur = [o]
      ret << cur
      last = val
    end
  end

  return ret
end
cnk(n,k) click to toggle source

Binomial coefficients (for the smooth filter)

# File lib/ctioga2/utils.rb, line 126
def self.cnk(n,k)
  res = 1.0
  n.downto(n - k) { |i| res *= i}
  k.downto(1) {|i| res = res/i }
  return res
end
color_name_by_value(color) click to toggle source

Returns the Tioga name for the given color. Can be nil if the color does not match a Tioga named color.

# File lib/ctioga2/utils.rb, line 226
def self.color_name_by_value(color)
  return color_name_by_value_hsh()[Utils::color_to_html(color)]
end
color_name_by_value_hsh() click to toggle source
# File lib/ctioga2/utils.rb, line 212
def self.color_name_by_value_hsh
  if ! @@color_name_by_value
    @@color_name_by_value = {}
    colors = Tioga::ColorConstants::constants
    for c in colors
      color = Tioga::ColorConstants::const_get(c)
      @@color_name_by_value[Utils::color_to_html(color)] = c.to_s
    end
  end
  return @@color_name_by_value
end
color_to_html(color) click to toggle source

Transforms a Tioga color into a HTML color string “xxxxxx” (without the leeading #)

# File lib/ctioga2/utils.rb, line 232
def self.color_to_html(color)
  return color.map do |i|
    "%02x" % (i*255.to_i)
  end.join('')
end
common_pow10(vect, method = :floor, tolerance = 1e-8) click to toggle source

Returns the smallest power of 10 within the given buffer (excluding 0 or anything really close). That is, if you multiply by 10 to the power what is returned, the smallest will be in the range 1-10.

# File lib/ctioga2/utils.rb, line 351
def self.common_pow10(vect, method = :floor, tolerance = 1e-8)
  a = vect.abs
  a.sort!
  while (a.size > 1) && (a.first < tolerance * a.last)
    a.shift
  end
  if a.first == 0
    return 0
  end

  return Math.log10(a.first).send(method)
end
finite_number?(flt) click to toggle source

Returns true if the argument is a finite number

# File lib/ctioga2/utils.rb, line 63
def self.finite_number?(flt)
  return (flt.is_a?(Numeric) and flt.to_f.finite?)
end
group_by_prefix(strings, pref_re) click to toggle source

Groups the given strings by prefixes

# File lib/ctioga2/utils.rb, line 197
def self.group_by_prefix(strings, pref_re)
  sets_by_prefix = {}
  for s in strings
    pref = s
    if s =~ pref_re
      pref = $1
    end
    sets_by_prefix[pref] ||= []
    sets_by_prefix[pref] << s
  end
  return sets_by_prefix
end
infinite_number?(flt) click to toggle source
# File lib/ctioga2/utils.rb, line 71
def self.infinite_number?(flt)
  return (flt.respond_to?(:infinite?) and flt.infinite?)
end
integer_subdivisions(bot, top, delta) click to toggle source

Returns the biggest vector of multiples of delta contained within bot and top

# File lib/ctioga2/utils.rb, line 402
def self.integer_subdivisions(bot, top, delta)
  if bot > top
    bot, top = top, bot
  end
  tn = (top/delta).floor
  bn = (bot/delta).ceil
  ret = Dobjects::Dvector.new()
  nb = (tn - bn).to_i + 1

  nb.times do |i|
    ret << (bn + i) * delta
  end
  return ret
end
mix_objects(a,b,r) click to toggle source

Takes two arrays of the same size (vectors) and mix them a * r + b * (1 - r)

# File lib/ctioga2/utils.rb, line 87
def self.mix_objects(a,b,r)
  ret = a.dup
  a.each_index do |i|
    ret[i] = a[i] * r + b[i] * (1 - r)
  end
  return ret
end
nan_number?(flt) click to toggle source
# File lib/ctioga2/utils.rb, line 67
def self.nan_number?(flt)
  return (flt.respond_to?(:nan?) and flt.nan?)
end
open(file) { |handle| ... } click to toggle source

This opens a file for reading, keeping track of the opened files and possibly transparently decompressing files when applicable. Returns the file object, or runs the given block with it, closing the file at the end.

# File lib/ctioga2/utils.rb, line 260
def self.open(file)
  if not File.readable?(file)
    # Try to find a compressed version
    for ext,method in UNCOMPRESSORS
      if File.readable? "#{file}#{ext}"
        info { "Using compressed file #{file}#{ext} in stead of #{file}" }
        ff = "#{file}#{ext}"
        handle = IO.popen(method % ff)
        break
      end
    end
  else
    for ext, method in UNCOMPRESSORS
      if file =~ /#{ext}$/
        info { "Taking file #{file} as a compressed file" }
        ff = file
        handle = IO.popen(method % file)
        break
      end
    end
  end
  if ! handle
    ff = file
    handle = File::open(file)
  end

  # Unwrap directory
  @used_files ||= {}

  @current_dirs ||= []
  rf = (@current_dirs + [file]).inject :+
  rff = (@current_dirs + [ff]).inject :+
  # The files referenced
  @used_files[rf] = rff
  if block_given?
    yield handle
    handle.close
  else
    return handle
  end
end
os() click to toggle source

Reliable OS detection, coming from:

stackoverflow.com/questions/11784109/detecting-operating-systems-in-ruby

# File lib/ctioga2/utils.rb, line 476
def self.os
  host_os = RbConfig::CONFIG['host_os']
  case host_os
  when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
    :windows
  when /darwin|mac os/
    :macosx
  when /linux/
    :linux
  when /solaris|bsd/
    :unix
  else
    warn {"Unknown os: #{host_os.inspect}"}
    :unknown
  end
end
parse_formula(formula, parameters = nil, header = nil) click to toggle source

This converts a text formula that can contain:

  • any litteral thing

  • references to columns in the form of $1 for column 1 (ie the second one)

  • references to named columns in the form $name$

  • references to parameters

The return value is ready to be passed to Dvector.compute_formula

# File lib/ctioga2/utils.rb, line 141
def self.parse_formula(formula, parameters = nil, header = nil)
  formula = formula.dup
  if parameters
    for k,v in parameters
      formula.gsub!(/\b#{k}\b/, v.to_s)
    end
  end
  formula.gsub!(/\$(\d+)/, 'column[\1]')
  if header
    for k,v in header
      formula.gsub!("$#{k}$", "column[#{v}]")
    end
  end
  if formula =~ /(\$[^$]+\$)/
    raise  "'#{$1}' looks like a column name, but there is no corresponding column of that name"
  end
  return formula
end
pdftex_quote_string(str) click to toggle source

Quotes a string so it can be included directly within a pdfinfo statement (for instance).

# File lib/ctioga2/utils.rb, line 97
def self.pdftex_quote_string(str)
  return str.gsub(/([%#])|([()])|([{}~_^])|\\/) do 
    if $1
      "\\#{$1}"
    elsif $2                  # Quoting (), as they can be quite nasty !!
      "\\string\\#{$2}"
    elsif $3
      "\\string#{$3}"
    else                      # Quoting \
      "\\string\\\\"
    end
  end
end
scale_segment(left, right, factor) click to toggle source

Returns the segment scaled about its center by the given factor

# File lib/ctioga2/utils.rb, line 365
def self.scale_segment(left, right, factor)
  dx = 0.5*(right - left)
  mid = 0.5*(right + left)
  return mid - factor * dx, mid + factor * dx
end
shell_quote_string(str) click to toggle source

Takes a string a returns a quoted version that should be able to go through shell expansion.

# File lib/ctioga2/utils.rb, line 49
def self.shell_quote_string(str)
  if str =~ /[\s"*$()\[\]{}';\\]/
    if str =~ /'/
      a = str.gsub(/(["$\\])/) { "\\#$1" }
      return "\"#{a}\""
    else 
      return "'#{str}'"
    end
  else
    return str
  end
end
sort_by_value(list, funcall) click to toggle source

Returns a hash value -> [elements] in which the elements are in the same order

# File lib/ctioga2/utils.rb, line 524
def self.sort_by_value(list, funcall)
  ret = {}
  
  for el in list
    val = el.send(funcall)
    ret[val] ||= []

    ret[val] << el
  end
  return ret
end
split_homogeneous_deltas(vect, tolerance = 1e-4) click to toggle source

Takes a vector, and splits it into a series of contiguous subvectors which

# File lib/ctioga2/utils.rb, line 419
def self.split_homogeneous_deltas(vect, tolerance = 1e-4)
  rv = []
  idx = 1
  dx = nil
  lst = nil
  while idx < vect.size
    cdx = vect[idx] - vect[idx - 1]
    if ! dx 
      dx = cdx
      lst = Dobjects::Dvector.new()
      rv << lst
      lst << vect[idx-1] << vect[idx]
    else
      if (cdx - dx).abs <= tolerance * dx
        # keep going
        lst << vect[idx]
      else
        dx = nil
      end
    end
    idx += 1
  end

  # Flush the last one if alone
  if ! dx
    nv = Dobjects::Dvector.new()
    nv << vect.last
    rv << nv
  end
  return rv
end
suffix_numeric_sort(strings) click to toggle source

Sorts strings according to their numeric suffix

# File lib/ctioga2/utils.rb, line 161
def self.suffix_numeric_sort(strings)
  strings.sort do |a,b|
    a =~ /.*?(\d+)$/
    a_i = $1 ? $1.to_i : nil
    b =~ /.*?(\d+)$/
    b_i = $1 ? $1.to_i : nil
    
    if a_i && b_i
      a_i <=> b_i
    else
      a <=> b
    end
  end
end
tex_quote_string(str) click to toggle source

Quotes a string so it can be included directly within a pdfinfo statement (for instance).

# File lib/ctioga2/utils.rb, line 113
def self.tex_quote_string(str)
  return str.gsub(/([%#])|([{}~_^])|\\/) do 
    if $1
      "\\#{$1}"
    elsif $2
      "\\string#{$2}"
    else                      # Quoting \
      "\\string\\\\"
    end
  end
end
transcode_until_found(file) click to toggle source

Transcodes the given string from all encodings into the target encoding until an encoding is found in which the named file exists.

Works around encoding problems on windows.

# File lib/ctioga2/utils.rb, line 377
def self.transcode_until_found(file)
  if File.exists? file
    return file
  end
  begin                     # We wrap in a begin/rescue block for
                            # Ruby 1.8
    for e in Encoding::constants
      e = Encoding.const_get(e)
      if e.is_a? Encoding
        begin
          ts = file.encode(Encoding::default_external, e)
          if File.exists? ts
            return ts
          end
        rescue
        end
      end
    end
  rescue
  end
  return file               # But that will fail later on.
end
txt_to_float(txt) click to toggle source

Converts a number to a float while trying to stay as lenient as possible

# File lib/ctioga2/utils.rb, line 77
def self.txt_to_float(txt)
  v = txt.to_f
  if v == 0.0
    return Float(txt)
  end
  return v
end
used_files() click to toggle source

Returns the list of files that were read by ctioga2

# File lib/ctioga2/utils.rb, line 304
def self.used_files()
  return @used_files || {}
end
which(cmd) click to toggle source

Cross-platform way of finding an executable in the $PATH.

This is adapted from stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby

# File lib/ctioga2/utils.rb, line 456
def self.which(cmd)
  return nil unless cmd
  exts = ['']
  if ENV['PATHEXT']
    exts += ENV['PATHEXT'].split(';')
  end

  ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
    exts.each { |ext|
      exe = File.join(path, "#{cmd}#{ext}")
      return exe if File.executable? exe
    }
  end
  return nil
end