nera 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +37 -0
- data/README.rdoc +47 -0
- data/Rakefile +27 -0
- data/bin/nera +29 -0
- data/bin/nera_addsim +30 -0
- data/lib/nera/nera_cui.rb +417 -0
- data/lib/nera/nera_database.rb +281 -0
- data/lib/nera/nera_db_folders.rb +226 -0
- data/lib/nera/nera_dialog.rb +205 -0
- data/lib/nera/nera_job_layer_controller.rb +237 -0
- data/lib/nera/nera_job_records.rb +111 -0
- data/lib/nera/nera_job_script.rb +202 -0
- data/lib/nera/nera_parameter_layer_controller.rb +157 -0
- data/lib/nera/nera_parameter_records.rb +186 -0
- data/lib/nera/nera_run_layer_controller.rb +192 -0
- data/lib/nera/nera_run_records.rb +184 -0
- data/lib/nera/nera_simulator.rb +26 -0
- data/lib/nera/nera_simulator_layer_controller.rb +66 -0
- data/lib/nera/nera_simulator_records.rb +84 -0
- data/lib/nera.rb +25 -0
- data/lib/nera_addsim/make_simulator.rb +307 -0
- data/scripts/make_manifest.rb +21 -0
- data/test/runner.rb +3 -0
- data/test/test_helper.rb +52 -0
- data/test/test_nera_database.rb +221 -0
- data/test/test_nera_db_folders.rb +209 -0
- data/test/test_nera_dialog.rb +134 -0
- data/test/test_nera_job_layer_controller.rb +132 -0
- data/test/test_nera_job_records.rb +260 -0
- data/test/test_nera_parameter_layer_controller.rb +188 -0
- data/test/test_nera_parameter_records.rb +285 -0
- data/test/test_nera_run_layer_controller.rb +171 -0
- data/test/test_nera_run_records.rb +290 -0
- data/test/test_nera_simulator.rb +26 -0
- data/test/test_nera_simulator_layer_controller.rb +54 -0
- data/test/test_nera_simulator_records.rb +140 -0
- metadata +125 -0
@@ -0,0 +1,281 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
#require 'filelock'
|
4
|
+
require 'pstore'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
#* list of public methods
|
8
|
+
#- initialize( path_to_filename )
|
9
|
+
#
|
10
|
+
#- self.create_table( path_to_basename )
|
11
|
+
#--- method to create new table
|
12
|
+
#--- ex. NERA::Database.create_table( "./db")
|
13
|
+
#
|
14
|
+
#- add( new_rec )
|
15
|
+
#--- add a new record to the table.
|
16
|
+
#--- new_rec must be specified by a Hash.
|
17
|
+
#--- ex. db.add( { :key1 => 'test', :key2 => DateTime.now, ... } )
|
18
|
+
#--- You don't need to specify the ':id' key. If it is specified, it is omitted.
|
19
|
+
#--- During the transaction, files are locked with exclusive locks.
|
20
|
+
#--- return true if the addition succeeded.
|
21
|
+
#--- raise RuntimeError if the specified hash is not valid.
|
22
|
+
#
|
23
|
+
#- find_by_id( id )
|
24
|
+
#--- returns records in Hash.
|
25
|
+
#--- id can be Integer, Array of Integers, or Range of Integers.
|
26
|
+
#--- During reading file, the files are locked with shared locks.
|
27
|
+
#--- If there are several records, it returns Array of Hash.
|
28
|
+
#
|
29
|
+
#- find_all { |rec| (condition) }
|
30
|
+
#--- returns the array of the matched records.
|
31
|
+
#--- return nil if no record matched.
|
32
|
+
#
|
33
|
+
#- update( rec )
|
34
|
+
#--- update a record
|
35
|
+
#--- ex. db.update( {:id=>3, :key1=>'updated', :key2=>DateTime.now} )
|
36
|
+
#--- returns nil if the record of the specified 'id' does not exist.
|
37
|
+
#
|
38
|
+
#- destroy( id )
|
39
|
+
#--- destroy records
|
40
|
+
#--- id may be an Integer, Array of Integers, Range
|
41
|
+
#--- if the specified record is not found in the table, return nil.
|
42
|
+
#
|
43
|
+
#- transaction { ..(transaction).. }
|
44
|
+
#--- all the database files are exclusively locked during block is processed.
|
45
|
+
#--- database files are unlocked at the end of the block. terminates.
|
46
|
+
#--- sample -----------
|
47
|
+
#
|
48
|
+
# db.transaction do
|
49
|
+
# arr = db.find_all do |record|
|
50
|
+
# record[:id] > 5
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# arr.each do |rec| rec[:state] = finished end
|
54
|
+
# db.update( arr)
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
#--- end of sample ---
|
58
|
+
#
|
59
|
+
#
|
60
|
+
#
|
61
|
+
module NERA
|
62
|
+
class Database
|
63
|
+
# name of the file
|
64
|
+
@filename
|
65
|
+
|
66
|
+
# true if files are exclusively locked
|
67
|
+
@in_transaction
|
68
|
+
|
69
|
+
# PStore object
|
70
|
+
@pstore
|
71
|
+
|
72
|
+
# search a data file in this order
|
73
|
+
# classes of values
|
74
|
+
VALUE_CLASSES = [ String, Symbol, Integer, Float, Date, DateTime, TrueClass, FalseClass, NilClass ]
|
75
|
+
|
76
|
+
public
|
77
|
+
# ----------------------------------
|
78
|
+
def initialize( path_to_file )
|
79
|
+
@filename = path_to_file
|
80
|
+
unless File.exist?(@filename)
|
81
|
+
raise "No such database : #{@filename}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# ------------------------------------
|
86
|
+
def transaction
|
87
|
+
if @in_transaction # nothing happens when the process is already in transaction
|
88
|
+
yield
|
89
|
+
return nil
|
90
|
+
end
|
91
|
+
@pstore = PStore.new(@filename)
|
92
|
+
@pstore.transaction( false) do
|
93
|
+
begin
|
94
|
+
@in_transaction = true
|
95
|
+
yield
|
96
|
+
ensure
|
97
|
+
@in_transaction = false
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
# ------------------------------------
|
105
|
+
def readonly_transaction
|
106
|
+
if @in_transaction
|
107
|
+
yield
|
108
|
+
return nil
|
109
|
+
end
|
110
|
+
@pstore = PStore.new(@filename)
|
111
|
+
@pstore.transaction(true) do
|
112
|
+
yield
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
public
|
117
|
+
# ------------------------------------
|
118
|
+
def self.create_table( filename)
|
119
|
+
if File.exists?( filename)
|
120
|
+
$stderr.puts "Database #{filename} already exists."
|
121
|
+
return nil
|
122
|
+
end
|
123
|
+
db = PStore.new( filename)
|
124
|
+
db.transaction(false) do
|
125
|
+
db[:max_id] = 0
|
126
|
+
end
|
127
|
+
return true
|
128
|
+
end
|
129
|
+
|
130
|
+
public
|
131
|
+
# ------------------------------------
|
132
|
+
def add( new_rec )
|
133
|
+
unless valid_as_a_record?( new_rec)
|
134
|
+
raise ArgumentError, "This record is not valid."
|
135
|
+
end
|
136
|
+
new_id = nil
|
137
|
+
transaction do
|
138
|
+
new_id = @pstore[:max_id] + 1
|
139
|
+
new_rec[:id] = new_id
|
140
|
+
@pstore[:max_id] = new_id
|
141
|
+
@pstore[new_id] = new_rec
|
142
|
+
end
|
143
|
+
return new_id
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
# ------------------------------------------
|
148
|
+
def valid_as_a_record?( rec)
|
149
|
+
# check validity
|
150
|
+
raise "rec must be a Hash" unless rec.is_a?(Hash)
|
151
|
+
rec.each do |key,val|
|
152
|
+
unless VALUE_CLASSES.find do |type| val.is_a?(type) end
|
153
|
+
$stderr.puts "Types of this record is not valid : #{key}, #{val}, #{val.class.to_s}"
|
154
|
+
return false
|
155
|
+
end
|
156
|
+
end
|
157
|
+
return true
|
158
|
+
end
|
159
|
+
|
160
|
+
public
|
161
|
+
# --- find methods --------------------------
|
162
|
+
def find_by_id( ids)
|
163
|
+
if ids.is_a?(Integer)
|
164
|
+
readonly_transaction do
|
165
|
+
return @pstore[ids]
|
166
|
+
end
|
167
|
+
elsif ids.is_a?(Array)
|
168
|
+
found = []
|
169
|
+
readonly_transaction do
|
170
|
+
# find all ----
|
171
|
+
ids.each do |id|
|
172
|
+
found << @pstore[id]
|
173
|
+
end
|
174
|
+
return found
|
175
|
+
end
|
176
|
+
elsif ids.is_a?(Range)
|
177
|
+
found = []
|
178
|
+
readonly_transaction do
|
179
|
+
matched_ids = @pstore.roots.find_all do |id|
|
180
|
+
ids.include?(id)
|
181
|
+
end
|
182
|
+
return nil unless matched_ids
|
183
|
+
matched_ids.sort! do |id1,id2|
|
184
|
+
id1 <=> id2
|
185
|
+
end
|
186
|
+
matched_ids.each do |id|
|
187
|
+
found << @pstore[id]
|
188
|
+
end
|
189
|
+
found = nil if found.size == 0
|
190
|
+
return found
|
191
|
+
end
|
192
|
+
else
|
193
|
+
raise ArgumentError, "Argument of the find method must be Integer, Array or Range. : #{ids.class}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
public
|
198
|
+
# --- find all ---
|
199
|
+
# usage: find_all { ..(condition).. }
|
200
|
+
def find_all
|
201
|
+
matched = []
|
202
|
+
readonly_transaction do
|
203
|
+
all_ids = @pstore.roots
|
204
|
+
all_ids.delete( :max_id)
|
205
|
+
all_ids.each do |id|
|
206
|
+
if yield @pstore[id].dup
|
207
|
+
matched << @pstore[id]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
matched.sort! do |rec1,rec2|
|
212
|
+
rec1[:id] <=> rec2[:id]
|
213
|
+
end
|
214
|
+
matched = nil if matched.size == 0
|
215
|
+
return matched
|
216
|
+
end
|
217
|
+
|
218
|
+
public
|
219
|
+
# --- update method ---
|
220
|
+
#- update( rec )
|
221
|
+
#--- update a record
|
222
|
+
#--- ex. db.update( {:id=>3, :key1=>'updated', :key2=>DateTime.now} )
|
223
|
+
#--- returns nil if the record of the specified 'id' does not exist.
|
224
|
+
def update( rec)
|
225
|
+
unless valid_as_a_record?( rec)
|
226
|
+
$stderr.puts "This record is not valid."
|
227
|
+
return nil
|
228
|
+
end
|
229
|
+
id_to_update = rec[:id]
|
230
|
+
flag = nil
|
231
|
+
transaction do
|
232
|
+
unless @pstore.root?( id_to_update)
|
233
|
+
return nil
|
234
|
+
end
|
235
|
+
@pstore[id_to_update] = rec
|
236
|
+
flag = true
|
237
|
+
end
|
238
|
+
return flag
|
239
|
+
end
|
240
|
+
|
241
|
+
public
|
242
|
+
#- destroy( id )
|
243
|
+
#--- destroy records
|
244
|
+
#--- id may be an Integer, Array of Integers, Range
|
245
|
+
#--- if the specified record is not found in the table, return nil.
|
246
|
+
def destroy( ids)
|
247
|
+
if ids.is_a?( Array)
|
248
|
+
status = []
|
249
|
+
transaction do
|
250
|
+
ids.each do |id|
|
251
|
+
status << destroy( id)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
return status
|
255
|
+
elsif ids.is_a?( Range)
|
256
|
+
flag = nil
|
257
|
+
transaction do
|
258
|
+
id_array = @pstore.roots.find_all do |id|
|
259
|
+
ids.include?(id)
|
260
|
+
end
|
261
|
+
flag = destroy( id_array)
|
262
|
+
end
|
263
|
+
flag = flag.find do |f| f end
|
264
|
+
return flag
|
265
|
+
elsif ids.is_a?( Integer)
|
266
|
+
stat = nil
|
267
|
+
transaction do
|
268
|
+
stat = @pstore.delete(ids)
|
269
|
+
end
|
270
|
+
return stat
|
271
|
+
else
|
272
|
+
raise ArgumentError, "argument must be specified by Integer, Array of Integer, or Range. : #{ids.class}"
|
273
|
+
end
|
274
|
+
return true
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
# ------ end of Database class --------
|
279
|
+
|
280
|
+
end
|
281
|
+
# ------ end of NERA module --------
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'nera_simulator_records'
|
3
|
+
require 'nera_job_records'
|
4
|
+
|
5
|
+
|
6
|
+
module NERA
|
7
|
+
|
8
|
+
# This class contains the information of the folder structure
|
9
|
+
class DbFolders
|
10
|
+
|
11
|
+
#--------------------------------------
|
12
|
+
#-class methods -----------------------
|
13
|
+
#--------------------------------------
|
14
|
+
|
15
|
+
def initialize( db_folder)
|
16
|
+
if db_folder =~ /\/$/
|
17
|
+
@db_folder = db_folder
|
18
|
+
else
|
19
|
+
@db_folder = db_folder + '/'
|
20
|
+
end
|
21
|
+
unless valid_db?( @db_folder)
|
22
|
+
raise RuntimeError, "Database #{@db_folder} is not a valid database folder."
|
23
|
+
end
|
24
|
+
|
25
|
+
# load simulator classes
|
26
|
+
Dir.glob( "#{@db_folder}/Simulator_classes/*.rb").each do |lib|
|
27
|
+
require lib
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid_db?( fold)
|
32
|
+
return false unless FileTest.directory?( fold)
|
33
|
+
return false unless FileTest.directory?( fold + "Jobs/")
|
34
|
+
return false unless FileTest.directory?( fold + "Jobs/Include/")
|
35
|
+
return false unless FileTest.directory?( fold + "Tables/")
|
36
|
+
return false unless FileTest.directory?( fold + "Simulator_classes/")
|
37
|
+
return false unless FileTest.exist?( fold + "Tables/simulators.pstore")
|
38
|
+
return false unless FileTest.exist?( fold + "Tables/jobs.pstore")
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
private :valid_db?
|
42
|
+
|
43
|
+
def self.create_db( db_folder)
|
44
|
+
if FileTest.directory?( db_folder)
|
45
|
+
raise RuntimeError, "Folder #{db_folder} already exists."
|
46
|
+
end
|
47
|
+
|
48
|
+
if db_folder =~ /\/$/
|
49
|
+
db = db_folder
|
50
|
+
else
|
51
|
+
db = db_folder + '/'
|
52
|
+
end
|
53
|
+
FileUtils.mkdir( db, :verbose => true)
|
54
|
+
FileUtils.mkdir( db + "Jobs/", :verbose => true)
|
55
|
+
FileUtils.mkdir( db + "Jobs/Include/", :verbose => true)
|
56
|
+
FileUtils.mkdir( db + "Tables/", :verbose => true)
|
57
|
+
FileUtils.mkdir( db + "Simulator_classes/", :verbose => true)
|
58
|
+
f = NERA::SimulatorRecords.create_table( db + "Tables/simulators.pstore")
|
59
|
+
raise "must not happen" unless f
|
60
|
+
f = NERA::JobRecords.create_table( db + "Tables/jobs.pstore")
|
61
|
+
raise "must not happen" unless f
|
62
|
+
File.open( db + 'simulators.yml', 'w') do |io|
|
63
|
+
YAML.dump( [], io )
|
64
|
+
io.flush
|
65
|
+
end
|
66
|
+
File.open( db + 'Jobs/jobs.yml', 'w') do |io|
|
67
|
+
YAML.dump( [], io )
|
68
|
+
io.flush
|
69
|
+
end
|
70
|
+
|
71
|
+
return true
|
72
|
+
end
|
73
|
+
|
74
|
+
#--------------------------------------
|
75
|
+
#-public instance methods--------------
|
76
|
+
#--------------------------------------
|
77
|
+
public
|
78
|
+
def path_to_simulator_layer
|
79
|
+
return @db_folder
|
80
|
+
end
|
81
|
+
|
82
|
+
def path_to_simulators_table
|
83
|
+
return "#{@db_folder}Tables/simulators.pstore"
|
84
|
+
end
|
85
|
+
|
86
|
+
def path_to_simulators_yaml
|
87
|
+
return "#{@db_folder}simulators.yml"
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_new_simulator_folder( sim_class)
|
91
|
+
unless sim_class.superclass == NERA::Simulator
|
92
|
+
raise ArgumentError, "must be a subclass of NERA::Simulator"
|
93
|
+
end
|
94
|
+
name = sim_class.to_s
|
95
|
+
name += '/'
|
96
|
+
|
97
|
+
if FileTest.directory?( @db_folder + name) or FileTest.directory?( @db_folder + "Tables/" + name)
|
98
|
+
return nil
|
99
|
+
end
|
100
|
+
FileUtils.mkdir( @db_folder + name, :verbose => true)
|
101
|
+
FileUtils.mkdir( @db_folder + "Tables/" + name, :verbose => true)
|
102
|
+
|
103
|
+
File.open( path_to_parameters_yaml( sim_class) , 'w') do |io|
|
104
|
+
YAML.dump( [], io )
|
105
|
+
io.flush
|
106
|
+
end
|
107
|
+
|
108
|
+
return true
|
109
|
+
end
|
110
|
+
|
111
|
+
def path_to_parameter_layer( sim_class)
|
112
|
+
unless sim_class.superclass == NERA::Simulator
|
113
|
+
raise ArgumentError, "must be a subclass of NERA::Simulator"
|
114
|
+
end
|
115
|
+
name = sim_class.to_s
|
116
|
+
name += '/'
|
117
|
+
|
118
|
+
path = @db_folder + name
|
119
|
+
if FileTest.directory?(path)
|
120
|
+
return path
|
121
|
+
else
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def path_to_parameters_table( sim_class)
|
127
|
+
unless sim_class.superclass == NERA::Simulator
|
128
|
+
raise ArgumentError, "must be a subclass of NERA::Simulator"
|
129
|
+
end
|
130
|
+
name = sim_class.to_s
|
131
|
+
name += '/'
|
132
|
+
|
133
|
+
path = @db_folder + 'Tables/' + name + 'parameters.pstore'
|
134
|
+
unless FileTest.directory?( @db_folder + 'Tables/' + name)
|
135
|
+
return nil
|
136
|
+
end
|
137
|
+
return path
|
138
|
+
end
|
139
|
+
|
140
|
+
def path_to_parameters_yaml( sim_class)
|
141
|
+
return path_to_parameter_layer( sim_class) + 'parameters.yml'
|
142
|
+
end
|
143
|
+
|
144
|
+
def create_new_parameter_folder( sim_class, param_id)
|
145
|
+
p = path_to_parameter_layer( sim_class)
|
146
|
+
return nil unless p
|
147
|
+
|
148
|
+
if param_id.to_i > 99999 or param_id.to_i <= 0
|
149
|
+
raise RuntimeError, "The specified parameter id #{param_id} is out of range."
|
150
|
+
end
|
151
|
+
fold_name = sprintf("%05d/", param_id.to_i)
|
152
|
+
if FileTest.directory?( p + fold_name)
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
FileUtils.mkdir( p + fold_name)
|
156
|
+
p = path_to_parameters_table( sim_class)
|
157
|
+
p.sub!(/parameters.pstore$/, fold_name)
|
158
|
+
FileUtils.mkdir( p)
|
159
|
+
|
160
|
+
File.open( path_to_runs_yaml( sim_class, param_id) , 'w') do |io|
|
161
|
+
YAML.dump( [], io )
|
162
|
+
io.flush
|
163
|
+
end
|
164
|
+
|
165
|
+
return true
|
166
|
+
end
|
167
|
+
|
168
|
+
def path_to_run_layer( sim_class, param_id)
|
169
|
+
p = path_to_parameter_layer( sim_class)
|
170
|
+
return nil unless p
|
171
|
+
if param_id.to_i > 99999 or param_id.to_i <= 0
|
172
|
+
return nil
|
173
|
+
end
|
174
|
+
fold_name = sprintf("%05d/", param_id.to_i)
|
175
|
+
path = p + fold_name
|
176
|
+
return path
|
177
|
+
end
|
178
|
+
|
179
|
+
def path_to_runs_table( sim_class, param_id)
|
180
|
+
ppt = path_to_parameters_table( sim_class)
|
181
|
+
return nil unless ppt
|
182
|
+
if param_id.to_i > 99999 or param_id.to_i <= 0
|
183
|
+
return nil
|
184
|
+
end
|
185
|
+
fold_name = sprintf("%05d/", param_id.to_i)
|
186
|
+
path = ppt.sub(/parameters.pstore$/, fold_name)
|
187
|
+
raise "must not happen" unless path
|
188
|
+
return path + 'runs.pstore'
|
189
|
+
end
|
190
|
+
|
191
|
+
def path_to_runs_yaml( sim_class, param_id)
|
192
|
+
return path_to_run_layer( sim_class, param_id) + 'runs.yml'
|
193
|
+
end
|
194
|
+
|
195
|
+
def path_to_job_layer
|
196
|
+
return @db_folder + "Jobs/"
|
197
|
+
end
|
198
|
+
|
199
|
+
def path_to_jobs_table
|
200
|
+
return @db_folder + "Tables/jobs.pstore"
|
201
|
+
end
|
202
|
+
|
203
|
+
def path_to_jobs_yaml
|
204
|
+
return path_to_job_layer + "jobs.yml"
|
205
|
+
end
|
206
|
+
|
207
|
+
def path_to_job_script(job_id)
|
208
|
+
filename = sprintf("j%06d.sh", job_id)
|
209
|
+
return path_to_job_layer + filename
|
210
|
+
end
|
211
|
+
|
212
|
+
def path_to_include_layer
|
213
|
+
return @db_folder + "Jobs/Include/"
|
214
|
+
end
|
215
|
+
|
216
|
+
def search_include_files
|
217
|
+
return Dir.glob( path_to_include_layer + "j[0-9][0-9][0-9][0-9][0-9][0-9]*.tar.bz2")
|
218
|
+
end
|
219
|
+
|
220
|
+
def path_to_finished_jobs_file
|
221
|
+
return @db_folder + "Tables/finished_jobs.yml"
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "readline"
|
3
|
+
|
4
|
+
# module for CUI interactive interface
|
5
|
+
#
|
6
|
+
module Dialog
|
7
|
+
|
8
|
+
PROMPT = "> "
|
9
|
+
|
10
|
+
# ----------------
|
11
|
+
# Select one from the list specified by the array.
|
12
|
+
def select_one( arr, message = "Input the number", default = 0)
|
13
|
+
unless arr.is_a?(Array) and arr.size >= 1 and default.to_i >= 0 and default.to_i < arr.size
|
14
|
+
raise ArgumentError, "Arguments are not valid."
|
15
|
+
end
|
16
|
+
|
17
|
+
while true
|
18
|
+
$stdout.puts( "#{message} : [#{default.to_s}]")
|
19
|
+
arr.size.times do |i|
|
20
|
+
puts( "[#{i}]:\t#{arr[i]}")
|
21
|
+
end
|
22
|
+
buf = Readline.readline(PROMPT).chomp.strip
|
23
|
+
buf = default.to_s if buf==''
|
24
|
+
|
25
|
+
unless buf.match(/^[0-9]+$/)
|
26
|
+
$stdout.puts "The input #{buf} is not valid. Try again"
|
27
|
+
redo
|
28
|
+
end
|
29
|
+
num = Integer(buf)
|
30
|
+
if num >= 0 and num < arr.size
|
31
|
+
valid_input = true
|
32
|
+
return num
|
33
|
+
end
|
34
|
+
$stdout.puts "Your input is not valid. Try again."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# ---------------
|
39
|
+
def select_one_or_return_string( arr, message = "Input the number", default = 0)
|
40
|
+
unless arr.is_a?(Array) and arr.size >= 1
|
41
|
+
raise ArgumentError, "Arguments are not valid."
|
42
|
+
end
|
43
|
+
|
44
|
+
while true
|
45
|
+
$stdout.puts( "#{message} : [#{default.to_s}]")
|
46
|
+
arr.size.times do |i|
|
47
|
+
puts( "[#{i}]:\t#{arr[i]}")
|
48
|
+
end
|
49
|
+
buf = Readline.readline(PROMPT).chomp.strip
|
50
|
+
buf = default.to_s if buf==''
|
51
|
+
|
52
|
+
if buf.match(/^[0-9]+$/)
|
53
|
+
num = Integer(buf)
|
54
|
+
if num >= 0 and num < arr.size
|
55
|
+
valid_input = true
|
56
|
+
return num
|
57
|
+
end
|
58
|
+
else
|
59
|
+
return buf
|
60
|
+
end
|
61
|
+
$stdout.puts "Your input is not valid. Try again."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# ---------------
|
66
|
+
def select_many( arr, message = "Select multiple numbers (ex. 1,3,5-8)", default = 0)
|
67
|
+
unless arr.is_a?(Array) and arr.size >= 1
|
68
|
+
raise ArgumentError, "Arguments are not valid."
|
69
|
+
end
|
70
|
+
|
71
|
+
result = []
|
72
|
+
valid_input = nil
|
73
|
+
until valid_input
|
74
|
+
$stdout.puts( "#{message} : [#{default}]")
|
75
|
+
arr.size.times do |i|
|
76
|
+
puts( "[#{i}]:\t#{arr[i]}")
|
77
|
+
end
|
78
|
+
|
79
|
+
buf = Readline.readline(PROMPT).chomp
|
80
|
+
buf = default.to_s if buf==''
|
81
|
+
|
82
|
+
valid_input = true
|
83
|
+
buf = buf.split(',').map do |e| e.strip end
|
84
|
+
buf.each do |e|
|
85
|
+
if e.match(/^[0-9]+$/)
|
86
|
+
num = Integer(e)
|
87
|
+
if num < 0 or num >= arr.size
|
88
|
+
valid_input = nil
|
89
|
+
break
|
90
|
+
else
|
91
|
+
result << num
|
92
|
+
end
|
93
|
+
elsif e.match(/^[0-9]+-[0-9]+$/)
|
94
|
+
f = e.split('-')
|
95
|
+
from = Integer(f[0])
|
96
|
+
to = Integer(f[1])
|
97
|
+
if from >= to
|
98
|
+
valid_input = nil
|
99
|
+
break
|
100
|
+
end
|
101
|
+
for i in from..to
|
102
|
+
result << i if i > 0 and i <= arr.size
|
103
|
+
end
|
104
|
+
else
|
105
|
+
valid_input = nil
|
106
|
+
break
|
107
|
+
end
|
108
|
+
end
|
109
|
+
$stdout.puts "The input #{buf} is not valid. Try again." unless valid_input
|
110
|
+
end
|
111
|
+
return result
|
112
|
+
end
|
113
|
+
|
114
|
+
# ---------------
|
115
|
+
def message( str)
|
116
|
+
$stdout.puts str
|
117
|
+
end
|
118
|
+
|
119
|
+
# ---------------
|
120
|
+
def set_multiple_values( arr, message = "Input the values")
|
121
|
+
unless arr.is_a?(Array)
|
122
|
+
raise ArgumentError, "Arguments are not valid."
|
123
|
+
end
|
124
|
+
|
125
|
+
result = Hash.new
|
126
|
+
arr.each do |a|
|
127
|
+
unless a.is_a?(Array) and a.size == 3
|
128
|
+
raise ArgumentError, "Arguments are not valid."
|
129
|
+
end
|
130
|
+
key = a[0]
|
131
|
+
type = a[1]
|
132
|
+
val = a[2]
|
133
|
+
unless ( type == Integer or type == Float or type == String ) and val.is_a?(type)
|
134
|
+
raise ArgumentError, "Arguments are not valid."
|
135
|
+
end
|
136
|
+
result[key] = val
|
137
|
+
end
|
138
|
+
unless arr.size == result.size
|
139
|
+
raise ArgumentError, "There is a nonunique key."
|
140
|
+
end
|
141
|
+
|
142
|
+
ok = nil
|
143
|
+
until ok
|
144
|
+
# show the list
|
145
|
+
arr.each_with_index do |elem,i|
|
146
|
+
$stdout.puts "[#{i}] :\t#{elem[0]}, #{elem[1]}, #{result[elem[0]]}"
|
147
|
+
end
|
148
|
+
$stdout.puts "[#{arr.size}] : \tOK"
|
149
|
+
# input
|
150
|
+
buf = Readline.readline(PROMPT).chomp.strip
|
151
|
+
unless buf.match(/^[0-9]+$/)
|
152
|
+
redo
|
153
|
+
end
|
154
|
+
input = buf.to_i
|
155
|
+
if input >= 0 and input < arr.size
|
156
|
+
$stdout.puts "Input #{arr[input][0]} (#{arr[input][1]})"
|
157
|
+
buf = Readline.readline(PROMPT).chomp.strip
|
158
|
+
if arr[input][1] == Integer
|
159
|
+
begin
|
160
|
+
result[ arr[input][0] ] = Integer(buf)
|
161
|
+
rescue
|
162
|
+
$stdout.puts "The specified value is not convertible to Integer"
|
163
|
+
end
|
164
|
+
elsif arr[input][1] == Float
|
165
|
+
begin
|
166
|
+
result[ arr[input][0] ] = Float(buf)
|
167
|
+
rescue
|
168
|
+
$stdout.puts "The specified value is not convertible to Float"
|
169
|
+
end
|
170
|
+
elsif arr[input][1] == String
|
171
|
+
result[ arr[input][0] ] = buf.to_s
|
172
|
+
end
|
173
|
+
elsif input == arr.size
|
174
|
+
ok = true
|
175
|
+
end
|
176
|
+
end
|
177
|
+
return result
|
178
|
+
end
|
179
|
+
|
180
|
+
# ---------------
|
181
|
+
def get_integer( message = "Input a integer", default = 0)
|
182
|
+
unless default.is_a?(Integer)
|
183
|
+
raise ArgumentError, "Arguments are not valid."
|
184
|
+
end
|
185
|
+
|
186
|
+
while true
|
187
|
+
$stdout.puts( "#{message} : [#{default}]")
|
188
|
+
|
189
|
+
buf = Readline.readline(PROMPT).chomp.strip
|
190
|
+
buf = default.to_s if buf==''
|
191
|
+
unless buf.match(/^[0-9]+$/)
|
192
|
+
$stdout.puts "The input #{buf} is not valid. Try again"
|
193
|
+
redo
|
194
|
+
end
|
195
|
+
num = Integer(buf)
|
196
|
+
if yield num
|
197
|
+
return num
|
198
|
+
else
|
199
|
+
$stdout.puts "The input #{buf} does not match the condition. Try again"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
module_function :select_one, :select_one_or_return_string, :select_many, :message, :set_multiple_values, :get_integer
|
205
|
+
end
|