nera 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|