ctioga 1.11.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +340 -0
- data/ctioga/bin/ctable +28 -0
- data/ctioga/bin/ctioga +37 -0
- data/ctioga/doc/ctable.1 +156 -0
- data/ctioga/doc/ctioga.1 +2363 -0
- data/ctioga/examples/README +46 -0
- data/ctioga/examples/ctioga.gnuplot +4 -0
- data/ctioga/examples/ctioga_within_tioga.rb +53 -0
- data/ctioga/examples/ctiogarc.rb +24 -0
- data/ctioga/examples/include_1.rb +15 -0
- data/ctioga/examples/noise.dat +100 -0
- data/ctioga/examples/noise.rb +13 -0
- data/ctioga/examples/trig.csv +100 -0
- data/ctioga/examples/trig.dat +100 -0
- data/ctioga/examples/trig.rb +14 -0
- data/ctioga/examples/trigh.dat +100 -0
- data/ctioga/examples/trigh.rb +10 -0
- data/ctioga/examples/tutorial +763 -0
- data/ctioga/examples/tutorial.sh +269 -0
- data/ctioga/tests/README +14 -0
- data/ctioga/tests/axes.sh +40 -0
- data/ctioga/tests/basic.sh +11 -0
- data/ctioga/tests/draw.sh +24 -0
- data/ctioga/tests/histograms.sh +14 -0
- data/ctioga/tests/insets.sh +41 -0
- data/ctioga/tests/layouts.sh +29 -0
- data/ctioga/tests/legends.sh +113 -0
- data/ctioga/tests/styles.sh +43 -0
- data/ctioga/tests/test_style.sh +8 -0
- data/ctioga/tests/tests.sh +24 -0
- data/ctioga/tests/text_backend.sh +83 -0
- data/ctioga/tests/tioga_defaults.rb +18 -0
- data/lib/CTioga/axes.rb +904 -0
- data/lib/CTioga/backends.rb +88 -0
- data/lib/CTioga/boundaries.rb +224 -0
- data/lib/CTioga/ctable.rb +134 -0
- data/lib/CTioga/curve_style.rb +246 -0
- data/lib/CTioga/debug.rb +199 -0
- data/lib/CTioga/dimension.rb +133 -0
- data/lib/CTioga/elements.rb +17 -0
- data/lib/CTioga/elements/base.rb +84 -0
- data/lib/CTioga/elements/containers.rb +578 -0
- data/lib/CTioga/elements/curves.rb +368 -0
- data/lib/CTioga/elements/tioga_primitives.rb +440 -0
- data/lib/CTioga/layout.rb +595 -0
- data/lib/CTioga/legends.rb +29 -0
- data/lib/CTioga/legends/cmdline.rb +187 -0
- data/lib/CTioga/legends/item.rb +164 -0
- data/lib/CTioga/legends/style.rb +257 -0
- data/lib/CTioga/log.rb +73 -0
- data/lib/CTioga/movingarrays.rb +131 -0
- data/lib/CTioga/partition.rb +271 -0
- data/lib/CTioga/plot_style.rb +230 -0
- data/lib/CTioga/plotmaker.rb +1677 -0
- data/lib/CTioga/shortcuts.rb +69 -0
- data/lib/CTioga/structures.rb +82 -0
- data/lib/CTioga/styles.rb +140 -0
- data/lib/CTioga/themes.rb +581 -0
- data/lib/CTioga/themes/classical.rb +82 -0
- data/lib/CTioga/themes/demo.rb +63 -0
- data/lib/CTioga/themes/fits.rb +91 -0
- data/lib/CTioga/themes/mono.rb +33 -0
- data/lib/CTioga/tioga.rb +32 -0
- data/lib/CTioga/utils.rb +173 -0
- data/lib/MetaBuilder/Parameters/dates.rb +38 -0
- data/lib/MetaBuilder/Parameters/lists.rb +132 -0
- data/lib/MetaBuilder/Parameters/numbers.rb +69 -0
- data/lib/MetaBuilder/Parameters/strings.rb +86 -0
- data/lib/MetaBuilder/Parameters/styles.rb +75 -0
- data/lib/MetaBuilder/Qt4/Parameters/dates.rb +51 -0
- data/lib/MetaBuilder/Qt4/Parameters/numbers.rb +65 -0
- data/lib/MetaBuilder/Qt4/Parameters/strings.rb +106 -0
- data/lib/MetaBuilder/Qt4/parameter.rb +172 -0
- data/lib/MetaBuilder/Qt4/parameters.rb +9 -0
- data/lib/MetaBuilder/descriptions.rb +603 -0
- data/lib/MetaBuilder/factory.rb +101 -0
- data/lib/MetaBuilder/group.rb +57 -0
- data/lib/MetaBuilder/metabuilder.rb +10 -0
- data/lib/MetaBuilder/parameter.rb +374 -0
- data/lib/MetaBuilder/parameters.rb +11 -0
- data/lib/MetaBuilder/qt4.rb +8 -0
- data/lib/SciYAG/Backends/backend.rb +379 -0
- data/lib/SciYAG/Backends/binner.rb +168 -0
- data/lib/SciYAG/Backends/cache.rb +102 -0
- data/lib/SciYAG/Backends/dataset.rb +158 -0
- data/lib/SciYAG/Backends/descriptions.rb +469 -0
- data/lib/SciYAG/Backends/filters.rb +25 -0
- data/lib/SciYAG/Backends/filters/average.rb +134 -0
- data/lib/SciYAG/Backends/filters/cumulate.rb +37 -0
- data/lib/SciYAG/Backends/filters/filter.rb +70 -0
- data/lib/SciYAG/Backends/filters/norm.rb +39 -0
- data/lib/SciYAG/Backends/filters/smooth.rb +63 -0
- data/lib/SciYAG/Backends/filters/sort.rb +43 -0
- data/lib/SciYAG/Backends/filters/strip.rb +34 -0
- data/lib/SciYAG/Backends/filters/trim.rb +64 -0
- data/lib/SciYAG/Backends/gnuplot.rb +131 -0
- data/lib/SciYAG/Backends/math.rb +108 -0
- data/lib/SciYAG/Backends/mdb.rb +462 -0
- data/lib/SciYAG/Backends/multitext.rb +96 -0
- data/lib/SciYAG/Backends/source.rb +64 -0
- data/lib/SciYAG/Backends/text.rb +339 -0
- data/lib/SciYAG/backends.rb +16 -0
- metadata +191 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
# cache.rb : A cache for various objects
|
2
|
+
# Copyright (C) 2007 Vincent Fourmond
|
3
|
+
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
17
|
+
|
18
|
+
module SciYAG
|
19
|
+
|
20
|
+
module Backends
|
21
|
+
|
22
|
+
# The Cache class aims at providing a small and easy-to-use cache
|
23
|
+
# with validation checking, usage statistics and flushing (automatically
|
24
|
+
# removes some entries when it grows too big).
|
25
|
+
class Cache
|
26
|
+
|
27
|
+
# An element of the cache
|
28
|
+
class CacheEntry
|
29
|
+
# The name under which it is cached
|
30
|
+
attr_accessor :name
|
31
|
+
|
32
|
+
# The data cached
|
33
|
+
attr_accessor :data
|
34
|
+
|
35
|
+
# The meta-data that should be matched against when checking
|
36
|
+
# the cache's relevance
|
37
|
+
attr_accessor :meta_data
|
38
|
+
|
39
|
+
def initialize(name, data, meta_data)
|
40
|
+
@name = name
|
41
|
+
@data = data
|
42
|
+
# Maybe we should even .dup the contents ?
|
43
|
+
@meta_data = meta_data.dup # (just to make sure)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Checks that the meta_data corresponds to this cache entry
|
47
|
+
def relevant?(meta_data)
|
48
|
+
return @meta_data == meta_data
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Records all accessed items and the number of cache misses/succes
|
53
|
+
# and so on.
|
54
|
+
attr_accessor :statistics
|
55
|
+
|
56
|
+
# Creates a Cache
|
57
|
+
def initialize
|
58
|
+
# The cache itself
|
59
|
+
@cache = {}
|
60
|
+
|
61
|
+
@statistics = {}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Look inside the cache for a cached element. If it is found and
|
65
|
+
# up-to-date, it is returned. If not, #get_cache runs _code_,
|
66
|
+
# stores its return value as a new cache for (_name_, _meta_data_)
|
67
|
+
# and returns it.
|
68
|
+
def get_cache(name, meta_data, &code)
|
69
|
+
if (@cache.key?(name)) and
|
70
|
+
((cached = @cache[name]).relevant?(meta_data))
|
71
|
+
stats(name)[:accesses] += 1
|
72
|
+
return cached.data
|
73
|
+
elsif @cache.key?(name)
|
74
|
+
stats(name)[:updates] += 1
|
75
|
+
end
|
76
|
+
stats(name)[:misses] += 1
|
77
|
+
|
78
|
+
# Now, we run the code to update the cache entry:
|
79
|
+
data = code.call
|
80
|
+
@cache[name] = CacheEntry.new(name, data, meta_data)
|
81
|
+
return data
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the statistics for the given item, or create them
|
85
|
+
# on the fly if necessary:
|
86
|
+
def stats(name)
|
87
|
+
if @statistics[name]
|
88
|
+
return @statistics[name]
|
89
|
+
else
|
90
|
+
@statistics[name] = {
|
91
|
+
:accesses => 0,
|
92
|
+
:misses => 0,
|
93
|
+
:updates => 0
|
94
|
+
}
|
95
|
+
return @statistics[name]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# dataset.rb : The implementation of data sets
|
2
|
+
# Copyright (C) 2006 Vincent Fourmond
|
3
|
+
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
17
|
+
|
18
|
+
require 'MetaBuilder/metabuilder'
|
19
|
+
|
20
|
+
require 'forwardable'
|
21
|
+
|
22
|
+
module SciYAG
|
23
|
+
|
24
|
+
module Backends
|
25
|
+
|
26
|
+
|
27
|
+
# An abstract class representing a DataSet. You should consider using
|
28
|
+
# a subclass, DataSet2D or DataSet3D. A DataSet must be either 2D or 3D,
|
29
|
+
# and redefine #is_2D? and #is_3D? accordingly.
|
30
|
+
#
|
31
|
+
# As DataSets grow more complex, it is likely that it will be less and
|
32
|
+
# less easy to tweak directly the data inside. So, please, do use the
|
33
|
+
# accessors and tweakers provided in the interface, else you'll expose
|
34
|
+
# yourself to some intensive breakages when I get more clever.
|
35
|
+
# Meanwhile, the apply(meth,where,*rest) method should be used to keep
|
36
|
+
# everything in sync when you apply a method to a Dvector.
|
37
|
+
class DataSet
|
38
|
+
|
39
|
+
extend Forwardable
|
40
|
+
|
41
|
+
# The #creation_context attribute is a hash containing a :backend key
|
42
|
+
# indicating the name of the Backend created and filled with the
|
43
|
+
# contents of the backend's DescriptionInclude#save_state function.
|
44
|
+
attr_accessor :creation_context
|
45
|
+
|
46
|
+
# The data of the set.
|
47
|
+
attr_accessor :data
|
48
|
+
|
49
|
+
# Errors on data, if applicable.
|
50
|
+
# For 2D data, this will just be a hash with the following Dvectors:
|
51
|
+
# :xmin, :xmax for errors on the x values + :x, to make sure
|
52
|
+
# we keep it up-to-date.
|
53
|
+
# :ymin, :ymax for errors on y + :y.
|
54
|
+
#
|
55
|
+
# Both ?min and ?max have to be specified if any output should be
|
56
|
+
# created.
|
57
|
+
attr_accessor :errors
|
58
|
+
|
59
|
+
# The metadata given when the set was created.
|
60
|
+
attr_accessor :meta_data
|
61
|
+
|
62
|
+
def is_2D?
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
def is_3D?
|
67
|
+
return false
|
68
|
+
end
|
69
|
+
|
70
|
+
# A few forwarded stuff to make the DataSet behave exactly like
|
71
|
+
# Function and it's future 3D counterpart
|
72
|
+
|
73
|
+
def_delegators :@data, :x, :y, :z
|
74
|
+
|
75
|
+
def initialize(context, data, errors = {}, meta_data = {})
|
76
|
+
if context.respond_to? :save_state
|
77
|
+
@creation_context = context.save_state
|
78
|
+
@creation_context[:backend] = context.description.name
|
79
|
+
else
|
80
|
+
@creation_context = context.dup
|
81
|
+
end
|
82
|
+
@data = data
|
83
|
+
@errors = errors
|
84
|
+
@meta_data = meta_data
|
85
|
+
end
|
86
|
+
|
87
|
+
# Apply a Dvector operation to the given dimension, including
|
88
|
+
# everything that could be left somewhere in the error bars.
|
89
|
+
# It should be very powerful in the end.
|
90
|
+
#
|
91
|
+
# Beautiful, isn't it ??
|
92
|
+
def apply(what, where, *rest)
|
93
|
+
self.send(where).send(what, *rest)
|
94
|
+
for key,values in @errors
|
95
|
+
if key.to_s =~ /^#{where}/ # Very much overkill, but, well...
|
96
|
+
values.send(what,*rest)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Does a redirection to the underlying @data if that makes sense.
|
102
|
+
def method_missing(sym, *args, &b)
|
103
|
+
if @data.respond_to?(sym)
|
104
|
+
@data.send(sym, *args, &b)
|
105
|
+
elsif x.respond_to?(sym) # We are trying to apply something
|
106
|
+
# like data.mul!(:x, factor)
|
107
|
+
apply(sym,args.shift,*args,&b)
|
108
|
+
else
|
109
|
+
super
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns all the Dvectors held by this dataset. Can be used
|
114
|
+
# to Dvector#replace them
|
115
|
+
def all_vectors
|
116
|
+
list = [@data.x, @data.y, *@errors.values]
|
117
|
+
list << @data.z if @data.respond_to?(:z)
|
118
|
+
return list
|
119
|
+
end
|
120
|
+
|
121
|
+
# Sorts the data according to the X value. This also includes
|
122
|
+
# the error bars.
|
123
|
+
def sort!
|
124
|
+
new_error = {}
|
125
|
+
idx_vector = Dobjects::Dvector.new(@data.x.size) do |i|
|
126
|
+
i
|
127
|
+
end
|
128
|
+
f = Dobjects::Function.new(@data.x.dup, idx_vector)
|
129
|
+
f.sort
|
130
|
+
for vector in all_vectors
|
131
|
+
new_vector = Dobjects::Dvector.new(vector.size) do |i|
|
132
|
+
vector[f.y[i]]
|
133
|
+
end
|
134
|
+
vector.replace(new_vector)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
# A 2-dimensionnal DataSet
|
141
|
+
class DataSet2D < DataSet
|
142
|
+
|
143
|
+
def is_2D?
|
144
|
+
return true
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
# A 3-dimensionnal DataSet
|
150
|
+
class DataSet3D < DataSet
|
151
|
+
def is_3D?
|
152
|
+
return true
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,469 @@
|
|
1
|
+
# description.rb : How do classes describe themselves
|
2
|
+
# Copyright (C) 2006 Vincent Fourmond
|
3
|
+
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
17
|
+
|
18
|
+
module SciYAG
|
19
|
+
|
20
|
+
module Backends
|
21
|
+
|
22
|
+
|
23
|
+
module Descriptions
|
24
|
+
|
25
|
+
# A module to convert text input into an object of the given type.
|
26
|
+
module Conversion
|
27
|
+
|
28
|
+
# The regular expression that matches 'true' for boolean values:
|
29
|
+
TRUE_RE = /^\s*(true|yes)\s*$/i
|
30
|
+
|
31
|
+
# The regular expression that matches 'false'
|
32
|
+
FALSE_RE = /^\s*(false|no(ne)?)\s*$/i
|
33
|
+
|
34
|
+
# Converts +text+ to the given type. It support different schemes:
|
35
|
+
# * if +type+ is String, Float, Array of Integer, the values are
|
36
|
+
# converted using the appropriate functions;
|
37
|
+
# * if +type+ is a string, the type is considered to be String;
|
38
|
+
# this leaves a possibility to implement a more precise
|
39
|
+
# mechanism for choosing; see Descriptions::Parameter#type
|
40
|
+
# * if +type+ is :bool, the text is interpreted as a true/false
|
41
|
+
# expression.
|
42
|
+
# * if +type+ is an array (of symbols/strings), then a valid input
|
43
|
+
# is one of the elements of the arrays (it is converted to symbols
|
44
|
+
# if necessary)
|
45
|
+
# * if +type+ is a hash, it behaves as if the keys had been
|
46
|
+
# specified as an array. The values can be used to provide
|
47
|
+
# a proper name;
|
48
|
+
# * for any other input, +type+ is assumed to be a class, and its
|
49
|
+
# constructor should support build from 1 argument, a String.
|
50
|
+
|
51
|
+
def text_to_type(text, type)
|
52
|
+
# warning: case cannot be used, as it is the comparison
|
53
|
+
# === which is used, that is is_a?.
|
54
|
+
if type == String
|
55
|
+
value = String(text)
|
56
|
+
elsif type.is_a?(String) # some special specification;
|
57
|
+
# always a string
|
58
|
+
value = String(text)
|
59
|
+
elsif type == Float
|
60
|
+
value = Float(text)
|
61
|
+
elsif type == Array
|
62
|
+
value = Array(text)
|
63
|
+
elsif type == Integer
|
64
|
+
value = Integer(text)
|
65
|
+
elsif type.is_a?(Array)
|
66
|
+
h = {}
|
67
|
+
type.each do |a|
|
68
|
+
h[a.to_s] = a
|
69
|
+
end
|
70
|
+
return h[text] # No checking done...
|
71
|
+
elsif type.is_a?(Hash)
|
72
|
+
return text_to_type(text, type.keys)
|
73
|
+
elsif type == :bool
|
74
|
+
if text =~ TRUE_RE
|
75
|
+
return true
|
76
|
+
else
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
else
|
80
|
+
value = type.new(text)
|
81
|
+
end
|
82
|
+
return value
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# A class that describes one parameter that will be fixed at
|
89
|
+
# run-time by the user.
|
90
|
+
class Parameter
|
91
|
+
include Conversion
|
92
|
+
# The short name of the parameter
|
93
|
+
attr_accessor :name
|
94
|
+
|
95
|
+
# The long name of the parameter, to be translated
|
96
|
+
attr_accessor :long_name
|
97
|
+
|
98
|
+
# The function names that should be used to set the symbol and
|
99
|
+
# retrieve it's current value. The corresponding functions should
|
100
|
+
# read or return a string, and writer(reader) should be a noop.
|
101
|
+
attr_accessor :reader_symbol, :writer_symbol
|
102
|
+
|
103
|
+
# The description
|
104
|
+
attr_accessor :description
|
105
|
+
|
106
|
+
# The type of the parameter. It can take several values;
|
107
|
+
# see Descriptions::Conversion.text_to_type. Moreover, several values
|
108
|
+
# for a string parameter can be interpreted by systems in charge
|
109
|
+
# of querying the parameter:
|
110
|
+
#
|
111
|
+
# * <tt>"File: ..."</tt> represents a file, and the rest is the
|
112
|
+
# filter, Qt style;
|
113
|
+
# * <tt>"Set"</tt> represents a data set, which can queried for by
|
114
|
+
# the sets_available function.
|
115
|
+
attr_accessor :type
|
116
|
+
|
117
|
+
def initialize(name, writer_symbol,
|
118
|
+
reader_symbol,
|
119
|
+
long_name,
|
120
|
+
type,
|
121
|
+
description)
|
122
|
+
@name = name
|
123
|
+
@writer_symbol = writer_symbol
|
124
|
+
@reader_symbol = reader_symbol
|
125
|
+
@description = description
|
126
|
+
@long_name = long_name
|
127
|
+
@type = type
|
128
|
+
end
|
129
|
+
|
130
|
+
# Sets the value of the given parameter in the _target_. It tries
|
131
|
+
# to be clever somehow, using @type to know what should be
|
132
|
+
# expected. See the text_to_type function.
|
133
|
+
|
134
|
+
def set(target, value)
|
135
|
+
value = text_to_type(value, @type)
|
136
|
+
target.send(@writer_symbol, value)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Aquires the value from the backend, and returns it
|
140
|
+
def get(target)
|
141
|
+
target.send(@reader_symbol).to_s
|
142
|
+
end
|
143
|
+
|
144
|
+
def value(v)
|
145
|
+
return text_to_type(v, @type)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# The base class for all descriptions. A description describes
|
150
|
+
# a "plugin" class. It has the following attributes:
|
151
|
+
#
|
152
|
+
# * the basic name, code-like, which is used mainly for internal
|
153
|
+
# purposes;
|
154
|
+
# * the long name, which has to be translated
|
155
|
+
# * the description itself, some small text describing the nature
|
156
|
+
# of the plugin
|
157
|
+
# * a list of parameters the plugin can take, along with their
|
158
|
+
# description. These are Parameter .
|
159
|
+
#
|
160
|
+
class Description
|
161
|
+
# the class to instantiate.
|
162
|
+
attr_accessor :class
|
163
|
+
|
164
|
+
# The name of the backend (short, code-like)
|
165
|
+
attr_accessor :name
|
166
|
+
|
167
|
+
# (text) description !
|
168
|
+
attr_accessor :description
|
169
|
+
|
170
|
+
# Long name, the one for public display
|
171
|
+
attr_accessor :long_name
|
172
|
+
|
173
|
+
# The hash holding parameters. Useful mainly for subclasses
|
174
|
+
attr_reader :param_hash
|
175
|
+
|
176
|
+
# The parameter list
|
177
|
+
attr_reader :param_list
|
178
|
+
|
179
|
+
# The list of parameters that have to be fed into the initialize
|
180
|
+
# function.
|
181
|
+
attr_accessor :init_param_list
|
182
|
+
|
183
|
+
|
184
|
+
# Initializes a Description
|
185
|
+
def initialize(cls, name, long_name, description = "")
|
186
|
+
@class = cls
|
187
|
+
@name = name
|
188
|
+
@long_name = long_name
|
189
|
+
@description = description
|
190
|
+
@param_list = []
|
191
|
+
@param_hash = {}
|
192
|
+
@init_param_list = []
|
193
|
+
end
|
194
|
+
|
195
|
+
# Creates an instance of the class, forwards parameters to the
|
196
|
+
# initialize method.
|
197
|
+
def instantiate(*args)
|
198
|
+
return @class.new(*args)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Prepares the argument list for instantiate based on
|
202
|
+
# init_param_list and the text arguments given:
|
203
|
+
def prepare_instantiate_arglist(*args)
|
204
|
+
target = []
|
205
|
+
for pars in @init_param_list
|
206
|
+
target << pars.value(args.shift)
|
207
|
+
end
|
208
|
+
return target
|
209
|
+
end
|
210
|
+
|
211
|
+
def add_param(param)
|
212
|
+
@param_list << param
|
213
|
+
|
214
|
+
# Three different cross-linkings.
|
215
|
+
@param_hash[param.reader_symbol] = param
|
216
|
+
@param_hash[param.writer_symbol] = param
|
217
|
+
@param_hash[param.name] = param
|
218
|
+
end
|
219
|
+
|
220
|
+
def param_set(i, p)
|
221
|
+
param = p
|
222
|
+
instance = i
|
223
|
+
return proc { |x| param.set(instance,x) }
|
224
|
+
end
|
225
|
+
|
226
|
+
# Pushes the names of the params onto @init_list_param_list.
|
227
|
+
# Arguments have to be strings.
|
228
|
+
def init_params(*args)
|
229
|
+
@init_param_list += args
|
230
|
+
end
|
231
|
+
|
232
|
+
# Fills an OptionParser with all the parameters the
|
233
|
+
# Backend should be able to take.
|
234
|
+
|
235
|
+
def fill_parser(instance, parser, uniquify = true)
|
236
|
+
parser_banner(instance, parser)
|
237
|
+
parser_options(instance, parser, uniquify)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Fills a parser with options (and only that)
|
241
|
+
def parser_options(instance, parser, uniquify = true)
|
242
|
+
raise "The instance is not of the right class" unless
|
243
|
+
instance.is_a? @class
|
244
|
+
for param in @param_list
|
245
|
+
if uniquify
|
246
|
+
param_name = "--#{@name}-#{param.name} #{param.name.upcase}"
|
247
|
+
else
|
248
|
+
param_name = "--#{param.name} #{param.name.upcase}"
|
249
|
+
end
|
250
|
+
parser.on(param_name, param.description,
|
251
|
+
param_set(instance, param))
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# The parsers's banner.
|
256
|
+
def parser_banner(instance, parser)
|
257
|
+
# nothing by default
|
258
|
+
end
|
259
|
+
|
260
|
+
# Creates a parser entry in +parser+ that creates a new instance of the
|
261
|
+
# description's class and feed it to the +result+ method of
|
262
|
+
# the +receiver+. You can specify an optionnal +prefix+ for the
|
263
|
+
# option's name.
|
264
|
+
def parser_instantiate_option(parser, receiver, result, prefix = "")
|
265
|
+
op_name = "--#{prefix}#{@name}"
|
266
|
+
for arg in @init_param_list
|
267
|
+
op_name += " " + arg.name.upcase
|
268
|
+
end
|
269
|
+
# cool !
|
270
|
+
parser.on(op_name, @description) do |*a|
|
271
|
+
b = prepare_instantiate_arglist(*a)
|
272
|
+
instance = instantiate(*b)
|
273
|
+
receiver.send(result, instance)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
# This module should be used with +include+ to provide the class with
|
280
|
+
# descriptions functionnalities. You also need to +extend+
|
281
|
+
# DescriptionExtend
|
282
|
+
module DescriptionInclude
|
283
|
+
# Returns the description associated with the backend object. Actually
|
284
|
+
# returns the one associated with the class.
|
285
|
+
def description
|
286
|
+
return self.class.description
|
287
|
+
end
|
288
|
+
|
289
|
+
# Fills an OptionParser with their parameters. Most probably, the
|
290
|
+
# default implementation should do for most cases. _uniquify_ asks
|
291
|
+
# if we should try to make the command-line options as unique as
|
292
|
+
# reasonable to do ?
|
293
|
+
def fill_parser(parser, uniquify = true)
|
294
|
+
description.fill_parser(self, parser, uniquify)
|
295
|
+
end
|
296
|
+
|
297
|
+
def parser_banner(parser)
|
298
|
+
description.parser_banner(self,parser)
|
299
|
+
end
|
300
|
+
|
301
|
+
def parser_options(parser, uniquify = true)
|
302
|
+
description.parser_options(self,parser, uniquify)
|
303
|
+
end
|
304
|
+
|
305
|
+
# Returns the long name of the backend:
|
306
|
+
def long_name
|
307
|
+
return description.long_name
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# This module should be used with +extend+ to provide the class with
|
312
|
+
# descriptions functionnalities. You also need to +include+
|
313
|
+
# DescriptionInclude. Please not that all the *instance* methods
|
314
|
+
# defined here will become *class* methods there.
|
315
|
+
module DescriptionExtend
|
316
|
+
|
317
|
+
# Returns the description of the class.
|
318
|
+
def description
|
319
|
+
return @description
|
320
|
+
end
|
321
|
+
|
322
|
+
# Sets the description of the class. Classes should provide
|
323
|
+
# an easier interface for that.
|
324
|
+
def set_description(desc)
|
325
|
+
@description = desc
|
326
|
+
end
|
327
|
+
|
328
|
+
# Like param, but doesn't define an accessor.
|
329
|
+
def param_noaccess(writer, reader, name, long_name, type = String,
|
330
|
+
desc = "")
|
331
|
+
raise "Use describe first" if description.nil?
|
332
|
+
param = Descriptions::Parameter.new(name, writer, reader,
|
333
|
+
long_name,
|
334
|
+
type, desc)
|
335
|
+
description.add_param(param)
|
336
|
+
return param
|
337
|
+
end
|
338
|
+
|
339
|
+
# Tells if the current object is a base ancestor ?
|
340
|
+
def base_ancestor?
|
341
|
+
if superclass.respond_to?(:lookup_description_extend_ancestor)
|
342
|
+
return false
|
343
|
+
else
|
344
|
+
return true
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Retrieves the most ancient ancestor that has included
|
349
|
+
# DescriptionExtend.
|
350
|
+
def lookup_description_extend_ancestor
|
351
|
+
t = self
|
352
|
+
while not t.base_ancestor?
|
353
|
+
t = t.superclass
|
354
|
+
end
|
355
|
+
return t
|
356
|
+
end
|
357
|
+
|
358
|
+
# Valid only in the base class
|
359
|
+
def description_list_base
|
360
|
+
if base_ancestor?
|
361
|
+
return @description_list
|
362
|
+
else
|
363
|
+
raise "This function should only be called from the " +
|
364
|
+
"base ancestor"
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# Valid only in the base class
|
369
|
+
def set_description_list_base()
|
370
|
+
@description_list = []
|
371
|
+
end
|
372
|
+
|
373
|
+
|
374
|
+
# Returns the list of descriptions associated with the base
|
375
|
+
# class
|
376
|
+
def description_list
|
377
|
+
return lookup_description_extend_ancestor.description_list_base
|
378
|
+
end
|
379
|
+
|
380
|
+
# Valid only in the base class
|
381
|
+
def description_hash_base
|
382
|
+
if base_ancestor?
|
383
|
+
return @description_hash
|
384
|
+
else
|
385
|
+
raise "This function should only be called from the " +
|
386
|
+
"base ancestor"
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# Valid only in the base class
|
391
|
+
def set_description_hash_base()
|
392
|
+
@description_hash = {}
|
393
|
+
end
|
394
|
+
|
395
|
+
# Returns the hash of descriptions associated with the base
|
396
|
+
# class
|
397
|
+
def description_hash
|
398
|
+
return lookup_description_extend_ancestor.description_hash_base
|
399
|
+
end
|
400
|
+
|
401
|
+
|
402
|
+
# Registers a description in the base ancestor.
|
403
|
+
def register_description(desc)
|
404
|
+
description_list << desc
|
405
|
+
description_hash[desc.name] = desc
|
406
|
+
end
|
407
|
+
|
408
|
+
# Redefined so that we can add the description lists only in the
|
409
|
+
# base ancestor.
|
410
|
+
def DescriptionExtend.extend_object(t)
|
411
|
+
# First call super, to make sure the methods are here !
|
412
|
+
super
|
413
|
+
if t.base_ancestor?
|
414
|
+
t.set_description_list_base
|
415
|
+
t.set_description_hash_base
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
|
420
|
+
# This shortcut creates an accessor for the given symbol and registers
|
421
|
+
# it at the same time. Use if *after* describe. Something like
|
422
|
+
#
|
423
|
+
# param :size, "size", "Size", Integer , "The size of the backend"
|
424
|
+
#
|
425
|
+
# should be perfectly fine.
|
426
|
+
def param(symbol, name, long_name, type = String,
|
427
|
+
desc = "")
|
428
|
+
# We use the accessor = symbol.
|
429
|
+
param_noaccess((symbol.to_s + "=").to_sym,
|
430
|
+
symbol, name, long_name,
|
431
|
+
type, desc)
|
432
|
+
# Creates an accessor
|
433
|
+
attr_accessor symbol
|
434
|
+
end
|
435
|
+
|
436
|
+
# The parameters the class should inherit from its direct parents
|
437
|
+
# (which could have in turn inherited them...).
|
438
|
+
def inherit(*names)
|
439
|
+
if self.superclass.respond_to?(:description)
|
440
|
+
parents_params = self.superclass.description.param_hash
|
441
|
+
for n in names
|
442
|
+
if parents_params.key?(n)
|
443
|
+
description.add_param(parents_params[n])
|
444
|
+
else
|
445
|
+
warn "Param #{n} not found"
|
446
|
+
end
|
447
|
+
end
|
448
|
+
else
|
449
|
+
warn "The parent class has no description"
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# Adds the params to the list of init params. Better use init_param
|
454
|
+
def init_params(*params)
|
455
|
+
description.init_params(*params)
|
456
|
+
end
|
457
|
+
|
458
|
+
# Creates a parameter which has to be used for instancitation
|
459
|
+
def init_param(name, long_name, type, desc)
|
460
|
+
p = param_noaccess(nil, nil, name, long_name, type, desc)
|
461
|
+
init_params(p)
|
462
|
+
end
|
463
|
+
|
464
|
+
end
|
465
|
+
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
end
|