Class: SciYAG::Backends::Backend

Inherits:
Object
  • Object
show all
Extended by:
MetaBuilder::DescriptionExtend
Includes:
MetaBuilder::DescriptionInclude
Defined in:
lib/SciYAG/Backends/backend.rb

Overview

This class provides the infrastructure for accessing data sets. It shouldn’t be used directly, but rather subclassed and reimplemented. The aim of this class is to provide any software which is interested to retrive data from some source with a consistent way to do so, independent the kind of source accessed.

Subclasses should:

  • provide a consistent method for creating themselves, with as much information as necessary, including options and default parameters. Actually, their initialize function should take no value bu on the other side, the BackendDescription associated with it should make it easy to set all the parameters necessary to get one set of data.

  • provide a way to fill an OptionParser with their own parameters

  • provide a way to retrieve the data via named ‘sets’ (either 2D or 3D data, depending on the subclass)

  • provide a way to obtain all meta-informations on one dataset, such as the date, the meaning of the columns (if any), and so on.

  • provide a way to know which named sets are available, or at least a subset (or nothing if we don’t know a thing).

  • wether the actual reading of the data is done at initialization time or at query time is left to the implementor ;-) !

Constant Summary collapse

@@log =
nil

Class Method Summary collapse

Instance Method Summary collapse

Methods included from MetaBuilder::DescriptionExtend

base_description, create_factory, describe, description, factory_class, factory_description, factory_description_hash, factory_description_list, group, has_factory?, inherit_parameters, param, param_accessor, param_reader, param_writer, register_class, set_description

Methods included from MetaBuilder::DescriptionInclude

#description, #get_param, #get_param_raw, #long_name, #option_parser_banner, #option_parser_fill, #option_parser_options, #parameter, #restore_state, #save_state, #set_param, #set_param_raw

Constructor Details

#initializeBackend

Sets up a few things, such as the filters.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/SciYAG/Backends/backend.rb', line 84

def initialize()
  @xy_filters = []
  @xy_filters_apply = false

  @xyz_filters = []
  @xyz_filters_apply = false

  # If set, uses this set as a baseline for all the others.
  @base_line = ""
  @base_line_cache = false

  # Filters are classes that have one method apply, which takes a
  # pair/triplet of vectors and return the same number of stuff,
  # modified or not.


  # We create a cache by default. Doesn't take up much space anyway:
  @cache = Cache.new
end

Class Method Details

.default_state(name) ⇒ Object

Returns the default state of the named backend, or nil if it wasn’t found.



344
345
346
347
348
349
350
351
# File 'lib/SciYAG/Backends/backend.rb', line 344

def self.default_state(name)
  desc = factory_description_hash[name]
  if desc
    return desc.default_state
  else
    return nil
  end
end

.describe(name, longname, desc, register = true) ⇒ Object

Creates a description object with the given texts and associates it with the class. It is necessary to have this statement before any parameter declaration. If you don’t set any description, you will not be able to benefit from the plugin system. To be used in Backend subclasses, simply this way:

describe "biniou", "Biniou backend", "A backend to deal with Binious"


129
130
131
132
# File 'lib/SciYAG/Backends/backend.rb', line 129

def Backend.describe(name, longname, desc, register = true)
  d = BackendDescription.new(self, name, longname, desc, register)
  set_description(d)
end

.list_backendsObject

Returns a hash containing the description of all available backends



135
136
137
# File 'lib/SciYAG/Backends/backend.rb', line 135

def Backend.list_backends
  return factory_description_hash
end

.list_descriptionsObject



139
140
141
142
# File 'lib/SciYAG/Backends/backend.rb', line 139

def Backend.list_descriptions
  warn "Backend.list_descriptions should not be used, use Backend.list_backends instead"
  list_backends
end

.logger=(logger) ⇒ Object

Set the logger for Backends



356
357
358
# File 'lib/SciYAG/Backends/backend.rb', line 356

def self.logger= (logger)
  @@log = logger
end

Instance Method Details

#base_line=(str) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/SciYAG/Backends/backend.rb', line 153

def base_line=(str)
  if str =~ /^no$/ or str.empty?
    @base_line = ""
  else
    @base_line = expand_sets(str)[0]
    # Fill the cache.
    ary = query_xy_data(@base_line)
    @base_line_cache = if ary.is_a?(Array)
                         ary[0]
                       else
                         ary
                       end
  end
end

#clear_xy_filtersObject

Removes all filters on the list.



115
116
117
118
# File 'lib/SciYAG/Backends/backend.rb', line 115

def clear_xy_filters
  @xy_filters = []
  @xy_filters_apply = false
end

#expand_sets(spec) ⇒ Object

When converting a user input into a set, a program should always use this function, unless it has really good reasons for that.

The default implementation is to expand 2##4 to 2, 3, 4. Can be useful even for mathematical stuff.

Another thing is recognised and expanded: #<2<i*2>,5> runs the code i*2 with the values from 2 to 5 and returns the result. The code in the middle is a Ruby block, and therefore should be valid !

A third expansion is now available: #<a = 2<a * sin(x)>10> will expand into 2*sin(x) , 3*sin(x) … 10*sin(x) it is different than the previous in the sense that the code in the middle is not a Ruby code, but a mere string, which means there won’t be compilation problems.

Unless your backend can’t accomodate for that, all redefinitions of this function should check for their specific signatures first and call this function if they fail. This way, they will profit from improvements in this code while keeping old stuff working.



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/SciYAG/Backends/backend.rb', line 271

def expand_sets(spec)
  if m = /(\d+)##(\d+)/.match(spec)
    debug "Using expansion rule #1"
    a = m[1].to_i
    b = m[2].to_i
    ret = []
    a.upto(b) do |i|
      ret << m.pre_match + i.to_s + m.post_match
    end
    return ret
  elsif m = /\#<(\d+)<(.*?)>(\d+)>/.match(spec)
    debug "Using expansion rule #2"
    from = m[1].to_i
    to = m[3].to_i
    debug "Ruby code used for expansion: {|i| #{m[2]} }"
    code = eval "proc {|i| #{m[2]} }"
    ret = []
    from.upto(to) do |i|
      ret << m.pre_match + code.call(i).to_s + m.post_match
    end
    return ret
  elsif m = /\#<\s*(\w+)\s*=\s*(\d+)\s*<(.*?)>\s*(\d+)\s*>/.match(spec)
    debug "Using expansion rule #3"
    var = m[1]
    from = m[2].to_i
    to = m[4].to_i
    # Then we replace all occurences of the variable
    literal = '"' + m[3].gsub(/\b#{var}\b/, '#{' + var + '}') + '"'
    debug "Ruby code used for expansion: {|#{var}| #{literal} }"
    code = eval "proc {|#{var}| #{literal} }"
    ret = []
    from.upto(to) do |i|
      ret << m.pre_match + code.call(i).to_s + m.post_match
    end
    return ret
  end
  # Fallback
  return [spec]
rescue  Exception => ex
  # In case something went wrong in the eval.
  warn "An error occured during expansion of '#{spec}': #{ex.message}"
  debug "Error backtrace: #{ex.backtrace.join "\n"}"
  warn "Ignoring, but you're nearly garanteed something will "+
    "fail later on"
  return [spec]
end

#get_cached_entry(name, exclude = [], supp_info = {}, &code) ⇒ Object

Gets a cached entry or generate it and cache it. See Cache#cache for more details. The cache’s meta_data is constructed as following:

  • the current state of the backend is taken

  • keys inside exclude are removed.

  • supp_info is added



333
334
335
336
337
338
339
340
# File 'lib/SciYAG/Backends/backend.rb', line 333

def get_cached_entry(name, exclude = [], supp_info = {}, &code)
  state = save_state
  for k in exclude
    state.delete(k)
  end
  state.merge!(supp_info)
  return @cache.get_cache(name, state, &code)
end

#has_set?(set) ⇒ Boolean Also known as: set?

Returns true if the backend can provide data for the given set.

Returns:

  • (Boolean)


186
187
188
# File 'lib/SciYAG/Backends/backend.rb', line 186

def has_set?(set)
  return false
end

#meta_data(set) ⇒ Object

This function should return a hash containing all meta-information available about the given set. The hash can contain elements such as:

:date

the date at which the set was recorded

:x_legend

legend of the X axis

:x_unit

unit of the X axis

(and the same kind for Y and Z axis)

Of course, this list is not limitative; you are encouraged at any rate to add as much metadata as possible. Later on, I’ll provide a method to register metadata types.



180
181
182
183
# File 'lib/SciYAG/Backends/backend.rb', line 180

def (set)
  warn "No metadata implementation for backend " +
    "#{description.name}"
end

#pop_xy_filterObject

Removes a filter from the top



110
111
112
# File 'lib/SciYAG/Backends/backend.rb', line 110

def pop_xy_filter
  return @xy_filters.pop
end

#push_xy_filter(f) ⇒ Object



104
105
106
107
# File 'lib/SciYAG/Backends/backend.rb', line 104

def push_xy_filter(f)
  @xy_filters << f
  @xy_filters_apply = true
end

#query_xy_data(set) ⇒ Object

This function must be redefined by children who provide 2D datasets. This function must return either:

  • a Function alone, representing the 2D data of the set

  • or an Array, whose first element is a Function and whose second element is an array containing metadata about the dataset.



203
204
205
# File 'lib/SciYAG/Backends/backend.rb', line 203

def query_xy_data(set)
  raise "query_xy_data must be redefined by children !"
end

#set_type(set) ⇒ Object

Returns :xy or :xyz depending on the kind of the given set.



193
194
195
# File 'lib/SciYAG/Backends/backend.rb', line 193

def set_type(set)
  raise "Shouldn't be called for the base class"
end

#sets_availableObject

Some backends have a pretty good idea of the sets available for use. Some really don’t. You can choose to reimplement this function if you can provide a useful list of sets for your backend. This list doesn’t need to be exhaustive (and is most unlikely to be). It can also return something that would need further expansion using expand_sets.



324
325
326
# File 'lib/SciYAG/Backends/backend.rb', line 324

def sets_available
  return []
end

#xy_data(set) ⇒ Object

Used by other classes to query for the Backends data. This may include processing with data filters. Please note that the query_xy_data functions must return a Dobjects::Function. The return value must be modifiable without consequences to the backend.



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/SciYAG/Backends/backend.rb', line 212

def xy_data(set)
  retval = query_xy_data(set)
  if retval.is_a?(Array) 
    ary,errors,meta = *retval
  else
    ary = retval
    errors = {}
    meta = {}
  end
  if (not @base_line.empty?) and @base_line_cache
    ary.y.sub!(@base_line_cache.y)
  end
  # Now, this is the fun part: we create the Dataset object
  dataset = DataSet2D.new(self, ary, errors, meta)
  # apply the filters if necessary
  if @xy_filters_apply
    for filter in @xy_filters
      filter.apply!(dataset)
    end
  end
  return dataset
end

#xyz_data(set) ⇒ Object

Returns the XYZ data for the given set



236
237
238
239
240
241
242
243
244
245
# File 'lib/SciYAG/Backends/backend.rb', line 236

def xyz_data(set)
  ary = query_xyz_data(set)
  # apply the filters if necessary
  if @xyz_filters_apply
    for filter in @xyz_filters
      ary = filter.apply(ary)
    end
  end
  return ary
end