coderunner 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1281 @@
1
+ class CodeRunner
2
+
3
+ class Run
4
+
5
+ # This is a class which is has several methods to facilitate the generation
6
+ # of input files for simulation codes which use a Fortran namelist style
7
+ # input file.
8
+ #
9
+ # Those developing a code module to deal with such a simulation code can
10
+ # make their custom run class a subclass of this class, and take advantage
11
+ # of all its functionality.
12
+ #
13
+ # There is a convention introduced by this class:
14
+ # * a "code_variable" is a variable name as it appears in the simulation code
15
+ # * a "variable" is a variable name as it appears in and is referred to by CodeRunner
16
+ # Why is this necessary? Every variable in CodeRunner has to have a
17
+ # <em>unique,lowercase</em> name. In contrast, in the simulation code, variables
18
+ # in different namelists can have the same name, and this name may contain uppercase
19
+ # letters. To get around this problem, when it occurs, a new CodeRunner name is
20
+ # defined and the name as it appears in the simulation code (which is referred to
21
+ # as the code_variable), is stored in the database as <tt>:code_name</tt>.
22
+
23
+ class FortranNamelist < Run
24
+
25
+ # Read the database of namelists and generate the four run class properties
26
+ # * variables_with_help
27
+ # * variables_with_autoscanned_defaults
28
+ # * variables_with_hashes
29
+ # * variables
30
+ #
31
+ # The full namelist database itself is assigned to the run class property
32
+ # * namelists
33
+ #
34
+ # (Reminder: run class properties are accessed with the <tt>rcp</tt> call)
35
+
36
+ def self.setup_namelists(folder)
37
+ # folder = File.dirname(__FILE__)
38
+
39
+
40
+ @namelists = eval(File.read(folder + '/namelists.rb'), binding, folder + '/namelists.rb')
41
+
42
+ @variables_with_help = (@namelists.inject({}) do |hash, (namelist, namelist_hash)|
43
+ namelist_hash[:variables].each{|var, var_hash| hash[var] = var_hash[:help] if var_hash[:help]}
44
+ hash
45
+ end)
46
+
47
+ @variables_with_autoscanned_defaults = (@namelists.inject({}) do |hash, (namelist, namelist_hash)|
48
+ namelist_hash[:variables].each{|var, var_hash| hash[var] = var_hash[:autoscanned_defaults] if var_hash[:autoscanned_defaults]}
49
+ hash
50
+ end)
51
+
52
+ @variables_with_hashes = @namelists.inject({}) do |hash, (namelist, namelist_hash)|
53
+ namelist_hash[:variables].each{|var, var_hash| hash[var] = var_hash unless hash[var] and hash[var][:help]} # If there are duplicates, take the one with help
54
+ hash
55
+ end
56
+
57
+ @variables = @namelists.inject([]) do |arr, (namelist, namelist_hash)|
58
+ if en = namelist_hash[:enumerator]
59
+ en[:estimated_value].times do |i|
60
+ namelist_hash[:variables].each{|var, var_hash| arr.push var + "_#{i+1}".to_sym}
61
+ end
62
+ else
63
+ namelist_hash[:variables].each{|var, var_hash| arr.push var}
64
+ end
65
+ arr
66
+ end
67
+
68
+
69
+ # VARIABLES = VARIABLES_WITH_HELP.keys
70
+ @variables.each{|var| attr_accessor var}
71
+
72
+ # Needed for backwards compatibility with old simulation data - variables that
73
+ # are no longer input parameters for the current version of the
74
+ # simulation code.
75
+ #
76
+
77
+
78
+ begin
79
+ @deleted_variables = eval(File.read(folder + '/deleted_variables.rb'), binding, folder + '/deleted_variables.rb')
80
+ rescue Errno::ENOENT
81
+ @deleted_variables = {}
82
+ save_deleted_variables
83
+ end
84
+
85
+ @deleted_variables.keys.each{|var| attr_accessor var}
86
+
87
+
88
+ end
89
+
90
+
91
+ # Does the variable var exist? If it does exist, returns a list of the namelists
92
+ # in which it is found. Otherwise returns false
93
+
94
+ def self.variable_exists?(namelist=nil, var)
95
+ exists = false
96
+ #exists = rcp.namelists.find_all{|namelist, hash| hash[:variables].keys.map{|v| v.to_s.downcase.to_sym}.include? var.to_s.downcase.to_sym}
97
+ exists = rcp.namelists.find_all{|namelist, hash| hash[:variables].keys.include? var}
98
+ # end
99
+ return exists.size > 0 ? exists.map{|(namelist, hash)| namelist} : false
100
+ end
101
+
102
+ # Returns true if the code variable (which may correspond to the code name) is present
103
+ # in namelist
104
+
105
+ def self.known_code_variable?(namelist, var)
106
+ return true if rcp.namelists[namelist] and rcp.namelists[namelist][:variables].map{|(v,h)| (h[:code_name] or v).to_s.downcase.to_sym}.include? var.to_s.downcase.to_sym
107
+ # end
108
+ return false
109
+ end
110
+
111
+ # Deletes the given variable from the namelists and saves the namelists
112
+
113
+ def self.delete_variable(namelist, var)
114
+ #variables_hash = rcp.namelists[namelist][:variables]
115
+ #var_name = (variables_hash.find do |var_n, var_hash|
116
+ #var_hash[:code_name] == var or var_n == var
117
+ #end)[0]
118
+ rcp.deleted_variables ||= {}
119
+ rcp.deleted_variables[var] = rcp.namelists[namelist][:variables][var]
120
+ rcp.namelists[namelist][:variables].delete(var)
121
+
122
+ save_deleted_variables
123
+ save_namelists
124
+ end
125
+
126
+ # This reads the mediawiki documentation of the input variables (as generated
127
+ # by write_mediawiki_documentation), copied from a wiki where it has been posted
128
+ # and placed in <tt>file</tt>, to see if anyone has updated the variable help
129
+ # on the wiki.
130
+
131
+ def self.read_mediawiki_documentation(file = ARGV[2])
132
+ documentation = File.read(file)
133
+ #documentation.scan(/^(?<markup>=+)(?<namelist>\w+)\k<markup>(?<vars>.+?)\s+(?=^\k<markup>|\s*\Z)/m) do
134
+ documentation.sub!(/\A.*=Namelists=/m, '')
135
+ documentation.sub!(/\<\/textarea.*\Z/m, '')
136
+ #documentation.scan(/(?<markup>=+)(?<namelist>\w+)\k<markup>(?<vars>.+?)\s+(?=\k<markup>|\|\})/m) do
137
+ documentation.scan(/(?<markup>=+)(?<namelist>\w+)\k<markup>(?<vars>.+?)\s+(?=\|\})/m) do
138
+ p 'nmlist', namelist = $~[:namelist].downcase.to_sym
139
+ vars = $~[:vars]
140
+ p vars
141
+ #vars.scan(/^\*\s*(?:\[\[)?(?<var>\w+)(?:\]\])?\s*:\s+(?<help>.+?)(?=\n\*[^*]|\s*\Z)/m) do
142
+ vars.scan(/\|\-\s+\|'''\[\[\w+\]\]'''\s+\|\|.*?\|\|.*?\|\|\s*?
143
+ (?<var>\w+)
144
+ \s*\|\s*
145
+ \<\!\-\-\s*begin\s+help\s*\-\-\>
146
+ (?<help>.+?)
147
+ \<\!\-\-\s*end\s+help\s*\-\-\>
148
+ /mx) do
149
+ var = $~[:var].downcase.to_sym
150
+ help = $~[:help].sub(/\A\s*\*\s*/, '')
151
+ #p var, help
152
+ sync_variable_help(namelist, var, help) if help.length > 0
153
+ end
154
+ end
155
+ end
156
+
157
+ # For backward compatibility: ensures that all variables (NB variables,
158
+ # not code_variables) are lower case in the namelist database
159
+
160
+ def self.correct_namelist_cases
161
+ rcp.namelists.each do |namelist, namelist_hash|
162
+ namelist_hash[:variables].each do |var, varhash|
163
+ #p var
164
+ if var.to_s =~ /[A-Z]/ or var.kind_of? String
165
+ p var
166
+ namelist_hash[:variables].delete(var)
167
+ namelist_hash[:variables][var.to_s.downcase.to_sym] = varhash
168
+ end
169
+ end
170
+ end
171
+ save_namelists
172
+ end
173
+
174
+ # Edit the help for the namelist. Requires the environment variable EDITOR to be
175
+ # set.
176
+
177
+ def self.edit_namelist_help(namelist, message = "")
178
+ raise ArgumentError.new("Unknown namelist #{namelist}") unless namelist_hash = rcp.namelists[namelist]
179
+ raise "Please set the environment variable EDITOR" unless ENV['EDITOR']
180
+ File.open('/tmp/.tmp_namelist_help.txt', 'w') do |file|
181
+ file.puts <<EOF
182
+
183
+ ------------------------------------------------------------------
184
+ Editing help and description for namelist #{namelist}:
185
+ -----------------------------------------------------------------
186
+
187
+ Edit the help and description, then save and quit the editor. Help can be long and detailed, and can include MediaWiki markup. Description should be short and in plain text.
188
+
189
+ #{message}
190
+
191
+ -------------------------------------begin help text
192
+
193
+
194
+ #{namelist_hash[:help]}
195
+
196
+
197
+ ------------------------------------begin description
198
+
199
+ #{namelist_hash[:description]}
200
+ EOF
201
+ end
202
+ system "#{ENV['EDITOR']} /tmp/.tmp_namelist_help.txt"
203
+ namelist_hash[:help], namelist_hash[:description] = File.read('/tmp/.tmp_namelist_help.txt').split(/^\-+begin help text/)[1].split(/^\-+begin description/, -1).map{|s| s.sub(/\A\s+/, '').sub(/\s+\Z/, '')}
204
+ save_namelists
205
+ end
206
+
207
+ # Edit the help for the variable in var in the given namelist.
208
+ # Requires the environment variable EDITOR to be set.
209
+
210
+ def self.edit_variable_help(namelist, var, message = "")
211
+ raise ArgumentError.new("Unknown namelist,variable #{namelist},#{var}") unless namelist_hash = rcp.namelists[namelist] and var_hash = namelist_hash[:variables][var]
212
+ raise "Please set the environment variable EDITOR" unless ENV['EDITOR']
213
+ File.open('/tmp/.tmp_variable_help.txt', 'w') do |file|
214
+ file.puts <<EOF
215
+
216
+ ------------------------------------------------------------------
217
+ Editing help and description for #{var} in namelist #{namelist}:
218
+ -----------------------------------------------------------------
219
+
220
+ Edit the help and description, then save and quit the editor. Help can be long and detailed, and can include MediaWiki markup. Description should be short and in plain text.
221
+
222
+ #{message}
223
+
224
+ -------------------------------------begin help text
225
+
226
+
227
+ #{var_hash[:help]}
228
+
229
+
230
+ ------------------------------------begin description
231
+
232
+ #{var_hash[:description]}
233
+ EOF
234
+ end
235
+ system "#{ENV['EDITOR']} /tmp/.tmp_variable_help.txt"
236
+ var_hash[:help], var_hash[:description] = File.read('/tmp/.tmp_variable_help.txt').split(/^\-+begin help text/)[1].split(/^\-+begin description/, -1).map{|s| s.sub(/\A\s+/, '').sub(/\s+\Z/, '')}
237
+ save_namelists
238
+ end
239
+
240
+ # If variable var in the given namelist has no help, add <tt>help</tt> (a string) to it.
241
+ # If it already has help which is different from <tt>help</tt>, open an editor to allow
242
+ # the user to resolve the conflict.
243
+ # Requires the environment variable EDITOR to be set.
244
+
245
+ def self.sync_variable_help(namelist, var, help)
246
+ raise ArgumentError.new("Unknown namelist,variable #{namelist.inspect},#{var.inspect}") unless namelist_hash = rcp.namelists[namelist] and var_hash = namelist_hash[:variables][var]
247
+ if not var_hash[:help] or var_hash[:help] == ""
248
+ var_hash[:help] = help
249
+ save_namelists
250
+ return
251
+ elsif var_hash[:help].sub(/\A\s+/, '').sub(/\s+\Z/, '') == help.sub(/\A\s+/, '').sub(/\s+\Z/, '')
252
+ return
253
+ else
254
+ var_hash[:help] = <<EOF
255
+
256
+ .<<<<<<<<<<<<current
257
+
258
+ #{var_hash[:help]}
259
+
260
+ .>>>>>>>>>>>>new
261
+
262
+ #{help}
263
+ EOF
264
+ edit_variable_help(namelist, var, "Note: There has been a conflict.")
265
+ end
266
+ end
267
+
268
+ # Add help to the variable in the given namelist
269
+
270
+ def self.add_help_to_variable(namelist=ARGV[-3], var=ARGV[-2], help=ARGV[-1])
271
+ # p rcp.namelists[namelist.to_sym]
272
+ rcp.namelists[namelist.to_sym][:variables][var.to_sym][:help] = help
273
+ rcp.namelists[namelist.to_sym][:variables][var.to_sym][:description] ||= help
274
+ save_namelists
275
+ end
276
+
277
+ # Add variable <tt>var</tt>, with a sample value, to <tt>namelist</tt>
278
+ # in the database of namelists.
279
+ # The parameter <tt> var</tt> should be the name of the variable as it appears
280
+ # in the simulation code.
281
+
282
+ def self.add_code_variable_to_namelist(namelist, var, value)
283
+ code_name = var
284
+ var = var.to_s.downcase.to_sym
285
+ namelist = namelist.to_s.sub(/_(?<num>\d+)$/, '').to_sym
286
+ enum = $~ ? $~[:num] : nil
287
+ return if rcp.namelists[namelist] and rcp.namelists[namelist][:variables].map{|v, h| (h[:code_name] or v).to_s.downcase.to_sym}.include? var
288
+ namelists = rcp.namelists
289
+ namelist_file = 'namelists.rb'
290
+ # end
291
+ raise "This namelist: #{namelist} should have an enumerator and does not have one" if enum and not @namelists[namelist][:enumerator]
292
+ return unless Feedback.get_boolean("An unknown variable has been found in this input file: \n\n\t Namelist: #{namelist}, Name: #{code_name}, Sample Value: #{value.inspect}.\n\nDo you wish to add it to the CodeRunner module? (Recommended: answer yes as long as the variable is not a typo)")
293
+
294
+ while nms = variable_exists?(namelist, var)
295
+ puts "This variable: #{var} already exists in these namelists: #{nms}. Please give an alternative name for CodeRunner (this will not affect the name that appears in the input file). If you know that the variable has the same meaning in these other namelists, or if you know that none of these namelists will appear at the same time, enter '0' to leave it unchanged."
296
+ ans = STDIN.gets.chomp
297
+ break if ans == "0"
298
+ var = ans.to_sym
299
+ end
300
+
301
+ namelists[namelist] ||= {}
302
+ namelists[namelist][:description] ||= ""
303
+ namelists[namelist][:should_include] ||= "true"
304
+ namelists[namelist][:variables] ||= {}
305
+ raise "Shouldn't have got here" if namelists[namelist][:variables][var]
306
+ tst = nil
307
+
308
+ case value
309
+ when Float
310
+ #tst = "Tst::FLOAT"
311
+ newtst = "kind_of? Numeric"
312
+ explanation = "This variable must be a floating point number (an integer is also acceptable: it will be converted into a floating point number)."
313
+ type = :Float
314
+ when Integer
315
+ #tst = "Tst::INT"
316
+ newtst = "kind_of? Integer"
317
+ explanation = "This variable must be an integer."
318
+ type = :Integer
319
+ when *String::FORTRAN_BOOLS
320
+ #tst = "Tst::FORTRAN_BOOL"
321
+ newtst = "kind_of? String and FORTRAN_BOOLS.include? self"
322
+ explanation = "This variable must be a fortran boolean. (In Ruby this is represented as a string: e.g. '.true.')"
323
+ type = :Fortran_Bool
324
+ when String
325
+ #tst = "Tst::STRING"
326
+ newtst = "kind_of? String"
327
+ explanation = "This variable must be a string."
328
+ type = :String
329
+ when Complex
330
+ #tst = "true"
331
+ newtst = "kind_of? Complex"
332
+ explanation = "This variable must be a complex number."
333
+ type = :Complex
334
+ end
335
+ namelists[namelist][:variables][var] = {
336
+ should_include: "true",
337
+ description: nil,
338
+ help: nil,
339
+ code_name: code_name,
340
+ must_pass: [{
341
+ test: newtst,
342
+ explanation: explanation
343
+ }],
344
+ type: type
345
+ }
346
+ if enum
347
+ attr_accessor (var + "_#{enum}").to_sym
348
+ else
349
+ attr_accessor var
350
+ end
351
+ save_namelists
352
+ edit_variable_help(namelist, var)
353
+ # folder = File.dirname(__FILE__)
354
+ # File.open(folder + '/' + namelist_file, 'w'){|f| f.puts namelists.pretty_inspect}
355
+ end
356
+
357
+ # A one off function designed to correct an old error in the namelists
358
+
359
+ def self.correct_type_location
360
+ rcp.namelists.values.each do |namelist_hash|
361
+ namelist_hash[:variables].each do |var, var_hash|
362
+ if var_hash[:must_pass][0] and var_hash[:must_pass][0][:type]
363
+ var_hash[:type] = var_hash[:must_pass][0][:type]
364
+ var_hash[:must_pass][0].delete(:type)
365
+ #pp var_hash
366
+ end
367
+ end
368
+ end
369
+ save_namelists
370
+ end
371
+
372
+
373
+ # Write the namelist database to the file namlists.rb.
374
+
375
+ def self.save_namelists
376
+ File.open(rcp.code_module_folder + '/namelists.rb', 'w'){|f| f.puts rcp.namelists.pretty_inspect}
377
+ end
378
+
379
+ # Write the list of old variables to the file deleted_variables.rb
380
+ def self.save_deleted_variables
381
+ File.open(rcp.code_module_folder + '/deleted_variables.rb', 'w'){|f| f.puts rcp.deleted_variables.pretty_inspect}
382
+ end
383
+
384
+ # Sets the allowed values for the variable <tt>var</tt> in <tt>namelist</tt>.
385
+
386
+ def self.set_allowed_values(namelist=nil, var, values)
387
+ unless namelist
388
+ namelist = rcp.namelists.find{|n, nh| nh[:variables].keys.include? var}[0]
389
+ eputs "Editing namelist #{namelist}"; STDIN.gets
390
+ end
391
+ rcp.namelists[namelist][:variables][var][:allowed_values] = values
392
+ save_namelists
393
+ end
394
+
395
+ # Add a test which the variable must pass before being included in the
396
+ # input file. The parameter <tt>tst</tt> must be of the form:
397
+ # {
398
+ # test: test_string
399
+ # explanation: explanation_string
400
+ # }
401
+ # where test_string is a string such that
402
+ # variable_value.instance_eval(test_string)
403
+ # should be <tt>true</tt> when the variable_value passes the test. The
404
+ # explanation should be a string which explains what the test does.
405
+ #
406
+ # If variable_value fails the test, an error is raised.
407
+
408
+ def self.add_variable_must_pass(namelist=nil, var, tst)
409
+ unless namelist
410
+ namelist = rcp.namelists.find{|n, nh| nh[:variables].keys.include? var}[0]
411
+ eputs "Editing namelist #{namelist}"; STDIN.gets
412
+ end
413
+ rcp.namelists[namelist][:variables][var][:must_pass] ||= []
414
+ rcp.namelists[namelist][:variables][var][:must_pass].push tst
415
+ rcp.namelists[namelist][:variables][var][:must_pass].uniq!
416
+ save_namelists
417
+ end
418
+
419
+ # Add a test which the namelist must pass before being included in the
420
+ # input file. The parameter <tt>tst</tt> must be of the form:
421
+ # {
422
+ # test: test_string
423
+ # explanation: explanation_string
424
+ # }
425
+ # where test_string is a string such that
426
+ # run.instance_eval(test_string)
427
+ # should be <tt>true</tt> when the run to be submitted passes the test. The
428
+ # explanation should be a string which explains what the test does.
429
+ #
430
+ # If the run to be submitted fails the test, an error is raised.
431
+
432
+ def self.add_namelist_must_pass(namelist, tst)
433
+ rcp.namelists[namelist][:must_pass] ||= []
434
+ rcp.namelists[namelist][:must_pass].push tst
435
+ rcp.namelists[namelist][:must_pass].uniq!
436
+ save_namelists
437
+ end
438
+
439
+ # Add a test which the variable should pass before being included in the
440
+ # input file. The parameter <tt>tst</tt> must be of the form:
441
+ # {
442
+ # test: test_string
443
+ # explanation: explanation_string
444
+ # }
445
+ # where test_string is a string such that
446
+ # variable_value.instance_eval(test_string)
447
+ # should be <tt>true</tt> when the variable_value passes the test. The
448
+ # explanation should be a string which explains what the test does.
449
+ #
450
+ # If variable_value fails the test, a warning is given.
451
+ #
452
+ def self.add_variable_should_pass(namelist=nil, var, tst)
453
+ unless namelist
454
+ namelist = rcp.namelists.find{|n, nh| nh[:variables].keys.include? var}[0]
455
+ eputs "Editing namelist #{namelist}"; STDIN.gets
456
+ end
457
+ rcp.namelists[namelist][:variables][var][:should_pass] ||= []
458
+ rcp.namelists[namelist][:variables][var][:should_pass].push tst
459
+ rcp.namelists[namelist][:variables][var][:should_pass].uniq!
460
+ save_namelists
461
+ end
462
+
463
+ # Add a test which the namelist should pass before being included in the
464
+ # input file. The parameter <tt>tst</tt> must be of the form:
465
+ # {
466
+ # test: test_string
467
+ # explanation: explanation_string
468
+ # }
469
+ # where test_string is a string such that
470
+ # run.instance_eval(test_string)
471
+ # should be <tt>true</tt> when the run to be submitted passes the test. The
472
+ # explanation should be a string which explains what the test does.
473
+ #
474
+ # If the run to be submitted fails the test, a warning is given.
475
+
476
+ def self.add_namelist_should_pass(namelist, tst)
477
+ rcp.namelists[namelist][:should_pass] ||= []
478
+ rcp.namelists[namelist][:should_pass].push tst
479
+ rcp.namelists[namelist][:should_pass].uniq!
480
+ save_namelists
481
+ end
482
+
483
+
484
+ # This regex is used to parse a Fortran namelist style input file.
485
+
486
+ @matching_regex = Regexp.new('(^|\A)(?<everything>[^!
487
+ ]*?\b #a word boundary
488
+
489
+ (?<name>[A-Za-z_]\w*) # the name, which must be a single word (not beginning
490
+ # with a digit) followed by
491
+
492
+ \s*=\s* # an equals sign (possibly with whitespace either side), then
493
+
494
+ (?<default>(?> # the default answer, which can be either:
495
+
496
+ (?<string>' + Regexp.quoted_string.to_s + ') # a quoted string
497
+
498
+ | # or
499
+
500
+
501
+ (?<float>\-?(?:(?>\d+\.\d*)|(?>\d*\.\d+))(?:[eEdD][+-]?\d+)?) # a floating point number
502
+
503
+ | #or
504
+
505
+ (?<int>\-?\d++) # an integer
506
+
507
+ | #or
508
+
509
+ (?<complex>\((?:\-?(?:(?>\d+\.\d*)|(?>\d*\.\d+))(?:[eEdD][+-]?\d+)?),\s*(?:\-?(?:(?>\d+\.\d*)|(?>\d*\.\d+))(?:[eEdD][+-]?\d+)?)\)) #a complex number
510
+
511
+ | #or
512
+
513
+
514
+ (?:(?<word>\S+)(?=\s|\)|\]|[\n\r]+|\Z)) # a single word containing no spaces
515
+ # which must be followed by a space or ) or ] or \n or \Z
516
+
517
+ )))', Regexp::EXTENDED)
518
+
519
+ class_accessor :matching_regex
520
+
521
+
522
+ # Parses a Fortran namelist input file into a hash of
523
+ # {
524
+ # :namelist => {
525
+ # :variable => value
526
+ # }
527
+ # }
528
+
529
+ def self.parse_input_file(input_file, strict=true)
530
+ if FileTest.file? input_file
531
+ text = File.read(input_file)
532
+ else
533
+ text = input_file
534
+ end
535
+ namelist_hash = {}
536
+ regex = Regexp.new("#{rcp.matching_regex.to_s}\\s*(?:\\!(?<comment>.*))?\\n")
537
+ text.scan(/(?:^\s*\!\s*(?<namelist_comment>[^\n]+))?\n^\&(?<namelist>\S+).*?^\//m) do
538
+ namelist = $~[:namelist].to_sym
539
+ hash = namelist_hash[namelist] = {}
540
+ scan_text_for_variables($~.to_s).each do |var, val|
541
+ add_code_variable_to_namelist(namelist, var, val) if @strict
542
+ hash[var] = val
543
+ end
544
+ end
545
+ # pp namelist_hash
546
+ namelist_hash
547
+ end
548
+
549
+ # Scan the text of a namelist from an input file and return an array variables with their default values
550
+
551
+ def self.scan_text_for_variables(text)
552
+ regex = Regexp.new("#{rcp.matching_regex.to_s}\\s*(?:\\!(?<comment>.*))?[\\n|;]")
553
+ arr = []
554
+ text.scan(regex) do
555
+ match = $~
556
+ var = match[:name].to_sym
557
+ default = match[:default.to_sym]
558
+ default = (match[:float] or match[:complex]) ? match[:default].gsub(/(\.)(\D|$)/, '\10\2').gsub(/[dD]/, 'e').gsub(/(\D)(\.)/, '\10\2') : match[:default]
559
+ # ep default
560
+ default = eval(default) unless match[:word] or match[:complex]
561
+ default= Complex(*default.scan(LongRegexen::FLOAT).map{|f| f[0].to_f}) if match[:complex]
562
+ arr.push [var, default]
563
+ end
564
+ arr
565
+ end
566
+
567
+ class << self
568
+ alias :generate_simple_namelist_hash :parse_input_file
569
+ end
570
+
571
+ # Generate input file text using the namelists, the values of the run instance variables and the customised method <tt>input_file_header</tt>.
572
+
573
+ def input_file_text
574
+ text = input_file_header
575
+ rcp.namelists.each do |namelist, hash|
576
+ next if hash[:should_include].kind_of? String and not eval(hash[:should_include])
577
+ if en = hash[:enumerator] # Single = is deliberate!
578
+ next unless send(en[:name])
579
+ send(en[:name]).times do |i|
580
+ next unless hash[:variables].keys.inject(false){|b, v| b or !send(v+"_#{i+1}".to_sym).nil?} # i.e. at least one variable must be non-nil
581
+ text << namelist_text(namelist, i+1)
582
+ end
583
+ else
584
+ next unless hash[:variables].keys.inject(false){|b, v| b or !send(v).nil?} # i.e. at least one variable must be non-nil
585
+ text << namelist_text(namelist)
586
+ end
587
+
588
+
589
+ end
590
+ text
591
+ end
592
+
593
+ # Generate the input file text for the given namelist. Called by input_file_text.
594
+
595
+ def namelist_text(namelist, enum = nil)
596
+ hash = rcp.namelists[namelist]
597
+ text = ""
598
+ ext = enum ? "_#{enum}" : ""
599
+ text << "!#{'='*30}\n!#{hash[:description]} #{enum} \n!#{'='*30}\n" if hash[:description]
600
+ text << "&#{namelist}#{ext}\n"
601
+ hash[:variables].each do |var, var_hash|
602
+ code_var = (var_hash[:code_name] or var)
603
+ cr_var = var+ext.to_sym
604
+ if send(cr_var) and (not var_hash[:should_include] or eval(var_hash[:should_include]))
605
+ if String::FORTRAN_BOOLS.include? send(cr_var) # var is a Fortran Bool, not really a string
606
+ output = send(cr_var).to_s
607
+ elsif (v = send(cr_var)).kind_of? Complex
608
+ output = "(#{v.real}, #{v.imag})"
609
+ else
610
+ #p cr_var, cr_var.class
611
+ output = send(cr_var).inspect
612
+ end
613
+ text << " #{code_var} = #{output} #{var_hash[:description] ? "! #{var_hash[:description]}": ""}\n"
614
+ end
615
+ end
616
+ text << "/\n\n"
617
+ text
618
+ end
619
+
620
+ # Return the hash of variable properties if it exists. Else return nil
621
+
622
+ def self.find_variable_hash(var)
623
+ varhash = nil
624
+ rcp.namelists.each do |namelist, hash|
625
+ varhash = hash[:variables][var]
626
+ return varhash if varhash
627
+ end
628
+ return nil
629
+ end
630
+
631
+ # Print help for the given variable to STDOUT.
632
+
633
+ def self.help_input(var=ARGV[2].to_sym)
634
+ eputs "\n------------ Help for '#{var}' -----------------"
635
+ eputs "\n#{(rcp.variables_with_help[var] or "No help currently available")}\n"
636
+ varhash = find_variable_hash(var)
637
+ namelists = rcp.namelists.find_all{|namelist, hash| hash[:variables].keys.include? var.to_sym}.map{|n,h| n}
638
+ eputs "\nFound in namelists: #{namelists.inspect}"
639
+ return unless varhash
640
+ eputs "This variable must take one of the following values: \n\t#{(varhash[:allowed_values] or varhash[:text_options])}" if (varhash[:allowed_values] or varhash[:text_options])
641
+ eputs "\n-------- Autoscanned Defaults for '#{var}' --------------\n\nIf this variable is not specified it may be given one of these default values:\n\t#{rcp.variables_with_autoscanned_defaults[var].inspect.sub(/^\[/, '').sub(/\]$/, '')}\n in the code. These values have been automatically scanned from the source code and do not constitute a recommendation; they may raise an error."
642
+ eputs "\n-------- Must Pass Tests for '#{var}' --------------\n\nThe variable must pass the following tests:\n\n"
643
+ varhash[:must_pass].each do |hash|
644
+ eputs "\tTest: #{hash[:test]}"
645
+ eputs "\tExplanation: #{hash[:explanation]}"
646
+ end
647
+ eputs
648
+ end
649
+
650
+
651
+ # Print out a list of every variable with help attached.
652
+
653
+ def self.help_variables
654
+ max_length = rcp.variables.map{|var| var.to_s.length}.inject{|old, new| [old,new].max}
655
+ # + "-" * ([max_length - var.length - 8, 0].max)
656
+ eputs rcp.variables_with_help.map{|var, comment| "#{var.to_s.rjust(max_length)}---> #{comment}"}.find_all{|string| not string =~ /^\s*\w+_[ie]/}
657
+ end
658
+
659
+ # This method compares two different input files and prints out a hash summarising the differences between them.
660
+
661
+ def self.diff_input_files(file1 = ARGV[2], file2 = ARGV[3])
662
+
663
+ rcp.runner.update if [file1, file1].find{|file| file =~ /^\d+$/}
664
+ file1, file2 = [file1, file2].map{|file| (run = rcp.runner.run_list[file.to_i]; file = "#{run.directory}/#{run.run_name}.in") if file.to_s =~ /^\d+$/; file}
665
+ hash1 = generate_simple_namelist_hash(file1)
666
+ hash2 = generate_simple_namelist_hash(file2)
667
+ new_hash = {}
668
+ (hash1.keys + hash2.keys).uniq.each do |key|
669
+ unless hash1[key] and hash2[key]
670
+ new_hash[key] = hash1[key] ? "Missing in #{File.basename(file2)} (Right)" : "Missing in #{File.basename(file1)} (Left)"
671
+ else
672
+ new_hash[key] = {}
673
+ (hash1[key].keys + hash2[key].keys).uniq.each do |vkey|
674
+ # p vkey
675
+ unless hash1[key][vkey].hash == hash2[key][vkey].hash
676
+ new_hash[key][vkey] = [hash1[key][vkey], hash2[key][vkey]]
677
+ end
678
+ end
679
+ end
680
+ end
681
+ eputs new_hash.pretty_inspect
682
+ end
683
+
684
+ # This method doesn't quite work. It is supposed to edit a local copy of a defaults file and update it to reflect any changes made to the central defaults file, without overriding any changes made to the local defaults file.
685
+
686
+ def self.update_folder_defaults #updates the defaults file in the current folder to add any new defaults settings from the given modlet. Does NOT change exist settings
687
+ current = File.read((defaults_file = rcp.modlet + '_defaults.rb'))
688
+ updated = File.read("#{rcp.modlet_location}/#{defaults_file}")
689
+ hash = current.scan(/(^\s*\@(\w+).*)/).inject({}) do |hash, (all, name)|
690
+ hash[name] = all unless name.to_s =~ /iphi00/
691
+ hash
692
+ end
693
+ # new_hash = {}
694
+ updated.scan(/(^\s*\@(\w+).*)/).each do |all, name|
695
+ next if name.to_s =~ /iphi00/
696
+ unless SPECIES_DEPENDENT_VARIABLES.include? name.sub(/_\d+$/, '').to_sym
697
+ hash[name] = all unless hash[name]
698
+ else
699
+ # puts name
700
+ name.sub(/_1$/, '')
701
+ if hash[name.sub(/_1$/, '_i').sub(/_2$/, '_e')]
702
+ hash[name] = hash[name.sub(/_1$/, '_i').sub(/_2$/, '_e')].sub(/_i\b/, '_1').sub(/_e\b/, '_2')
703
+ hash.delete(name.sub(/_1$/, '_i').sub(/_2$/, '_e'))
704
+ elsif hash[name.sub(/_1$/, '')]
705
+ ep name
706
+ hash[name] = hash[name.sub(/_1$/, '')].sub(/(^\@\w+)/, '\1_1')
707
+ hash.delete(name.sub(/_1$/, ''))
708
+ else
709
+ hash[name] = all
710
+ end
711
+ end
712
+ end
713
+ # eputs hash.pretty_inspect
714
+ # puts hash.values
715
+ hash['adiabatic_option'] ||= %[@adiabatic_option = "iphi00=2"]
716
+ puts hash.values
717
+
718
+ # File.open(defaults_file, 'w'){|file| file.puts hash.values}
719
+
720
+ end
721
+
722
+ # The name is self-explanatory: this method takes an input file and generates a CodeRunner defaults file. The first argument is the name of the new defaults file.
723
+
724
+ def self.make_new_defaults_file(name=ARGV[-2], input_file=ARGV[-1])
725
+ string = defaults_file_text_from_input_file(input_file)
726
+ defaults_filename = "#{name}_defaults.rb"
727
+ raise "This defaults name already exists" if FileTest.exist? defaults_filename
728
+ File.open(defaults_filename, 'w'){|file| file.puts(string)}
729
+ end
730
+
731
+ # Called by defaults_file_text_from_input_file.
732
+
733
+ def self.namelist_defaults_text(hash, namelist, namelist_hash, enum = nil)
734
+ ext = enum ? "_#{enum}" : ""
735
+ namelist = namelist + ext.to_sym
736
+ return "" unless hash[namelist]
737
+ text = "\n\n######################################\n# Defaults for namelist #{namelist}\n#######################################\n\n"
738
+ # pp hash[namelist]
739
+ namelist_hash[:variables].each do |var, varhash|
740
+ code_var = (varhash[:code_name] or var)
741
+ cr_var = var + ext.to_sym
742
+ text << "@#{cr_var} = #{hash[namelist][code_var].inspect} # #{varhash[:description]}\n" if hash[namelist][code_var]
743
+ end
744
+ return text
745
+ end
746
+
747
+
748
+ # The name is self-explanatory: this method takes an input file and generates the text of a CodeRunner defaults file.
749
+
750
+
751
+ def self.defaults_file_text_from_input_file(input_file)
752
+ string = defaults_file_header
753
+
754
+ hash = parse_input_file(input_file)
755
+ #pp hash; exit
756
+ rcp.namelists.each do |namelist, namelist_hash|
757
+ #ep namelist
758
+ if namelist_hash[:enumerator] # ie. This is an indexed namelist
759
+ #p namelist_hash[:enumerator]
760
+ enumerator = namelist_hash[:enumerator][:name]
761
+ enum_hash = hash.find{|nml, nmlh| nmlh[enumerator]}
762
+ #pp enum_hash
763
+ enum = enum_hash[1][enumerator]
764
+ enum.times{|i| string << namelist_defaults_text(hash, namelist, namelist_hash, i+1)}
765
+ else
766
+ string << namelist_defaults_text(hash, namelist, namelist_hash)
767
+ end
768
+ end
769
+ string
770
+ end
771
+
772
+
773
+
774
+ # This method takes an input file and generates a CodeRunner info file. It should not be called in a folder where a CodeRunner info file already exists, as it will overwrite that info file with a less complete one.
775
+
776
+ def make_info_file(file=ARGV[-1], strict=true)
777
+ hash = self.class.parse_input_file(file, strict)
778
+ # species_dependent_namelists = SPECIES_DEPENDENT_NAMELISTS.keys
779
+ filename = File.dirname(file) + '/code_runner_info.rb'
780
+ # (puts "Warning: An info file exists: if you continue it will be overwritten, and the original may contain more information. Press enter to continue, Crtl + C to cancel"; gets) if FileTest.exist? filename
781
+
782
+ # pp hash, species_dependent_namelists
783
+ # run= new(@@runner)
784
+ hash.each do |namelist, vars|
785
+ num = nil
786
+ # ep namelist
787
+ namelist = namelist.to_s.sub(/\_(?<num>\d+)$/, '').to_sym
788
+ if $~ # I.e if there was a number at the end of the namelist
789
+ # ep namelist
790
+ "This namelist: #{namelist} should have an enumerator and does not have one" if not rcp.namelists[namelist][:enumerator]
791
+ num = $~[:num]
792
+ end
793
+ vars.each do |var, value|
794
+ var = (var.to_s + "_#{num}").to_sym if num
795
+ # p var, value
796
+ set(var, value)
797
+ end
798
+ set(:run_name, file.sub(/\.in/, ''))
799
+ # p 'hello'
800
+ File.open(filename, 'w'){|file| file.puts info_file}
801
+ File.open(".code_runner_version.txt", 'w'){|file| file.puts CODE_RUNNER_VERSION}
802
+ end
803
+ # end
804
+ # ep @@variables
805
+
806
+ end
807
+
808
+ def self.parse_old_website_docs(file = ARGV[-1])
809
+ text = File.read(file)
810
+ text.scan(/Namelist:\s+<i>\s*(?<namelist>\w+).*?<\s*table(?<var_text>(?:<table.*?<\/table>|.)*?)(?=<\/table>)/m) do
811
+ namelist = $~[:namelist].to_sym
812
+ ep 'namelist', namelist
813
+ var_text = $~[:var_text]
814
+ vars = var_text.split('<tr><th>')
815
+ 2.times{vars.shift}
816
+ vars.each do |var_text|
817
+ ep var_text
818
+ name, type, default, help = var_text.split('<td>')
819
+ name, type, default = [name, type, default].map do |str|
820
+ str.sub(/^\s+/, '').sub(/\s+\Z/, '').gsub(/<[^>]+>/, '')
821
+ end
822
+ name = name.sub(/^\s+/, '').sub(/\s+\Z/, '').gsub(/<[^>]+>/, '').to_sym
823
+ ep 'name', name
824
+ #begin
825
+ #p name, type, help, default, rcp.namelists[namelist][:variables][name][:autoscanned_defaults]
826
+ #rescue => err
827
+ #p err
828
+ #p namelist, name
829
+ #end
830
+ if rcp.namelists[namelist][:variables][name]
831
+ names = [name]
832
+ else
833
+ names = name.to_s.split(/\s*,\s*/).map{|n| n.to_sym}
834
+ end
835
+ ep 'names', names
836
+ help = help.gsub(/<\/?[bi]>/, '').gsub(/<br>/, '**')
837
+ names.each do |name|
838
+ unless rcp.namelists[namelist][:variables][name]
839
+ var = rcp.namelists[namelist][:variables].keys.find{|var| rcp.namelists[namelist][:variables][var][:code_name] == name}
840
+ raise "Can't find #{name.inspect} in #{namelist}" unless var
841
+ name = var
842
+ end
843
+
844
+ begin
845
+ sync_variable_help(namelist, name, help)
846
+ if rcp.namelists[namelist][:variables][name][:help] =~ /<[^>]+>/
847
+ edit_variable_help(namelist, name)
848
+ end
849
+ rescue => err
850
+ p namelist, name
851
+ raise err
852
+ end
853
+
854
+ end
855
+
856
+
857
+
858
+ end
859
+
860
+
861
+ end
862
+
863
+ end
864
+
865
+ # This method takes the help written into this module for the various input parameters and writes it in a format suitable for the mediawiki page on input parameters.
866
+
867
+ def self.write_mediawiki_documentation
868
+ puts <<EOF
869
+ =Introduction=
870
+
871
+ This page lists every GS2 input parameter currently known about, along with any help available. It is intended as a reference, not an introduction. Note: some parameters are highly specialised and not intended for general use.
872
+
873
+ A full introduction to writing input files is to be written, but until then, this is an old example input file. Be aware that not every section should be included.
874
+
875
+ [[GS2 Reference Input File]]
876
+
877
+ ==Format==
878
+
879
+ The parameters are divided into namelists. Each parameter has type information and written help where available. The format is:
880
+
881
+ {| border="2" cellpadding="5"
882
+ ! Name !! Type !! Def !! CR Name !! Description
883
+ |-
884
+ |-
885
+ |Name as it appears in GS2 || Fortran Data Type || Autoscanned Default(s): guesses at what the default value of this parameter will be if you do not specify it in the input file. They are usually correct.
886
+ || CodeRunner Name: is the variable name used by [http://coderunner.sourceforge.net CodeRunner] to refer to the quantity (only given if it is different to the GS2 name).
887
+ |
888
+ Long and detailed help for the variable
889
+ |}
890
+
891
+
892
+ ==Updating this Page==
893
+
894
+ This page is automatically generated by [http://coderunner.sourceforge.net CodeRunner], but any '''changes you make will be kept''', so please feel free to contribute. Please keep to the same format as this allows easy automatic syncing of your changes with the CodeRunner database. '''Please only edit in between the <nowiki><!-- begin help --> <!-- end help --> </nowiki> or the <nowiki><!-- begin namelist help --> <!-- end namelist help --> </nowiki> tags'''. Don't edit type/default information (or this introduction) as it will not be kept.
895
+
896
+ =Namelists=
897
+
898
+ Each GS2 module is controlled by its own namelist. For typical applications, not all 32+ namelists should appear in a single file. For a run called runname, this file should be called <tt>runname.in</tt>. In most cases, defaults are loaded for each namelist element, so that if a namelist or element does not appear, the run will not automatically stop. (It may still be forced to stop if the defaults are incompatible with your other choices.) The namelists and defaults appear below.
899
+ EOF
900
+
901
+ rcp.namelists.each do |namelist, hash|
902
+ puts "==#{namelist}=="
903
+ puts "<!--begin namelist help-->#{hash[:help]}<!--end namelist help-->"
904
+ puts "\n{| border=\"2\" cellpadding=\"5\"\n! Name !! Type !! Def !! CR Name !! Description \n|-"
905
+ hash[:variables].keys.sort.each do |var|
906
+ var_hash = hash[:variables][var]
907
+ #puts "==='''[[#{(var_hash[:code_name] or var)}]]'''==="
908
+ puts "|-\n|'''[[#{(var_hash[:code_name] or var)}]]''' || #{var_hash[:type]} || #{(var_hash[:autoscanned_defaults]||[]).map{|v| v.to_s}.join(",")} || #{var} \n|\n<!--begin help-->* #{hash[:variables][var][:help]} <!-- end help -->"
909
+ #puts "''Type'': #{var_hash[:type]} "
910
+ #puts "''Autoscanned Defaults'': #{var_hash[:autoscanned_defaults]} "
911
+ #puts "''CodeRunner name'': #{var} "
912
+ #puts "\n", "#{hash[:variables][var][:help]}".sub(/\A\s+/, '') if hash[:variables][var][:help]
913
+ #puts " #{(var_hash[:code_name] or var)} Properties:"
914
+ end
915
+ puts "|}"
916
+ end
917
+ end
918
+
919
+ # This method takes the help written into this module for the various input parameters and writes it in a format suitable for the mediawiki page on input parameters.
920
+
921
+ #def self.write_mediawiki_documentation
922
+ #rcp.namelists.each do |namelist, hash|
923
+ #puts "==#{namelist}=="
924
+ #puts hash[:help]
925
+ #hash[:variables].keys.sort.each do |var|
926
+ #var_hash = hash[:variables][var]
927
+ ##puts "==='''[[#{(var_hash[:code_name] or var)}]]'''==="
928
+ #puts "* '''[[#{(var_hash[:code_name] or var)}]]''':<!-- begin help --> #{hash[:variables][var][:help]}<!-- end help -->"
929
+ #puts "** Properties: \n*** ''Type'': #{var_hash[:type]} \n*** ''Autoscanned Defaults'': #{var_hash[:autoscanned_defaults]} \n*** ''CodeRunner Name'': #{var}"
930
+ ##puts "''Type'': #{var_hash[:type]} "
931
+ ##puts "''Autoscanned Defaults'': #{var_hash[:autoscanned_defaults]} "
932
+ ##puts "''CodeRunner name'': #{var} "
933
+ ##puts "\n", "#{hash[:variables][var][:help]}".sub(/\A\s+/, '') if hash[:variables][var][:help]
934
+ ##puts " #{(var_hash[:code_name] or var)} Properties:"
935
+ #end
936
+ #end
937
+ #end
938
+
939
+
940
+ #def self.write_wiki_documentation
941
+ #rcp.namelists.each do |namelist, hash|
942
+ #puts "==#{namelist}=="
943
+ #puts hash[:help]
944
+ #hash[:variables].keys.sort.each do |var|
945
+ #var_hash = hash[:variables][var]
946
+ ##puts "==='''[[#{(var_hash[:code_name] or var)}]]'''==="
947
+ #puts "'''[[#{(var_hash[:code_name] or var)}]]'''"
948
+ #puts "\n<font color=gray>''Type'': #{var_hash[:type]} ''Autoscanned Defaults'': #{var_hash[:autoscanned_defaults]} ''CodeRunner name'': #{var}</font>"
949
+ #puts "<!-- begin help --> #{"#{hash[:variables][var][:help]}".sub(/\*{1}/, "\n").sub(/\*\*/, "*")}<!-- end help -->"
950
+ ##puts "''Type'': #{var_hash[:type]} "
951
+ ##puts "''Autoscanned Defaults'': #{var_hash[:autoscanned_defaults]} "
952
+ ##puts "''CodeRunner name'': #{var} "
953
+ ##puts "\n", "#{hash[:variables][var][:help]}".sub(/\A\s+/, '') if hash[:variables][var][:help]
954
+ ##puts " #{(var_hash[:code_name] or var)} Properties:"
955
+ #end
956
+ #end
957
+ #end
958
+
959
+ # This method scans the source code in the given folder and tries to find what value each parameter will be given if the value is not specified in the input file. It is about as subtle as a sledgehammer and doesn't always find the right answer, but in general is pretty good. The values it finds are stored in the name list hashes as :autoscanned_defaults
960
+
961
+ def self.update_defaults_from_source_code(source_code_folder = ARGV[-1])
962
+ eputs "Scanning - this takes a while..."
963
+
964
+ # [[, File.dirname(__FILE__) + '/namelists.rb']].each do |namelists, file|
965
+
966
+ namelists = rcp.namelists
967
+ # file = File.dirname(__FILE__) + '/namelists.rb'
968
+ string = ""
969
+ Dir.chdir(source_code_folder) do
970
+ namelists.each do |namelist, hash|
971
+ hash[:variables].each do |var, varhash|
972
+ string += `grep -h -E '^[ \t]*#{(varhash[:code_name] or var)}[ \t]*=' *`
973
+ string += `grep -h -E '^[ \t]*#{(varhash[:code_name] or var)}[ \t]*=' */*`
974
+ end
975
+ end
976
+ end
977
+
978
+ # string.gsub!(/^.+?:/, '') # Get rid of file names from grep
979
+ File.open('found1','w'){|f| f.puts string}
980
+ # exit
981
+ defs = scan_text_for_variables(string)
982
+ File.open('found2','w'){|f| f.puts defs.pretty_inspect}
983
+ # exit
984
+ namelists.each do |namelist, hash|
985
+ hash[:variables].each do |var, varhash|
986
+ p var if var == :nwrite
987
+ values = defs.find_all{|(v, df)| v == (varhash[:code_name] or var)}.map{|(v,df)| df}
988
+ values.uniq!
989
+ p values if var == :nwrite
990
+ values.delete_if{|val| val.kind_of? String} if values.find{|val| val.kind_of? Numeric}
991
+ p values if var == :nwrite
992
+ values.delete_if{|val| val.kind_of? String and not String::FORTRAN_BOOLS.include? val} if values.find{|val| val.kind_of? String and String::FORTRAN_BOOLS.include? val}
993
+ p values if var == :nwrite
994
+ values.sort!
995
+ hash[:variables][var][:autoscanned_defaults] = values
996
+ # ep var, values
997
+ end
998
+ end
999
+ save_namelists
1000
+ # File.open(file, 'w'){|f| f.puts namelists.pretty_inspect}
1001
+ # end
1002
+ end
1003
+
1004
+ # Given the folder where the source code resides, return a single string containing all the code
1005
+
1006
+ def self.get_aggregated_source_code_text(source_code_folder)
1007
+ #p 'source_code_folder', source_code_folder
1008
+ string = ""
1009
+ (rcp.source_code_subfolders.map{|f| '/' + f} + [""]).map{|f| source_code_folder + f}.each do |folder|
1010
+ Dir.chdir(folder) do
1011
+ Dir.entries.each do |file|
1012
+ next unless file =~ /((\.f9[05])|(\.fpp))$/
1013
+ next if file =~ /ingen/
1014
+ ep file
1015
+ text = File.read(file) + "\n"
1016
+ text =~ /a/
1017
+ string += text
1018
+ end
1019
+ end
1020
+ end
1021
+ string
1022
+ end
1023
+
1024
+ # Find unknown input variables in the source code and add them to the database of namelists
1025
+ # Delete input variables which are no longer present in the source code
1026
+
1027
+ def self.synchronise_variables(source_code_folder = ARGV[2])
1028
+ source = get_aggregated_source_code_text(source_code_folder)
1029
+ # ep source.size
1030
+ nms = {}
1031
+ all_variables_in_source = {}
1032
+ namelist_declarations = {}
1033
+ #source.scan(/^\s*namelist\s*\/(?<namelist>\w+)\/(?<variables>(?:(?:&\s*[\n\r]+)|[^!\n\r])*)/) do
1034
+ source.scan(Regexp.new("#{/^\s*namelist\s*\/(?<namelist>\w+)\//}(?<variables>#{FORTRAN_SINGLE_LINE})")) do
1035
+ namelist = $~[:namelist].to_s.downcase.to_sym
1036
+ variables = $~[:variables].gsub(/!.*/, '')
1037
+ eputs namelist, variables
1038
+ namelist_declarations[namelist] = variables
1039
+ #gets # if namelist == :collisions_knobs
1040
+
1041
+ next if [:stuff, :ingen_knobs].include? namelist
1042
+ nms[namelist] = []
1043
+ all_variables_in_source[namelist] = []
1044
+ # puts variables
1045
+ variables.scan(/\w+/) do
1046
+ var = $~.to_s.to_sym
1047
+ # (p variables, namelist; exit) if var == :following or var == :sou
1048
+ all_variables_in_source[namelist].push var
1049
+ next if known_code_variable?(namelist, var)
1050
+ nms[namelist].push var
1051
+ end
1052
+ nms[namelist].uniq!
1053
+ all_variables_in_source[namelist].uniq!
1054
+ end
1055
+ variables_to_delete = {}
1056
+ #pp 'namelists', rcp.namelists
1057
+ rcp.namelists.each do |namelist, namelist_hash|
1058
+ namelist_hash[:variables].each do |variable, var_hash|
1059
+ code_variable = var_hash[:code_name] || variable
1060
+ unless all_variables_in_source[namelist] and all_variables_in_source[namelist].map{|var| var.to_s.downcase.to_sym}.include? code_variable.to_s.downcase.to_sym
1061
+ variables_to_delete[namelist] ||= []
1062
+ variables_to_delete[namelist].push variable
1063
+ end
1064
+ end
1065
+ end
1066
+ variables_to_delete.each do |namelist, var_array|
1067
+ #eputs namelist_declarations[namelist]
1068
+ var_array.each do |var|
1069
+ p "Namelist: #{namelist} Variable: #{var}"
1070
+ end
1071
+ end
1072
+ if variables_to_delete.find{|namelist, var_array| var_array.size > 0}
1073
+ delete_old = Feedback.get_boolean("These variables are no longer present in the #{rcp.code_long} source folder. Do you wish to delete them?")
1074
+ if delete_old
1075
+ variables_to_delete.each do |namelist, var_array|
1076
+ var_array.each do |var|
1077
+ delete_variable(namelist, var)
1078
+ end
1079
+ end
1080
+ end
1081
+ end
1082
+
1083
+ eputs nms.keys.zip(nms.values.map{|vs| vs.size})
1084
+ eputs "Namelists to be added to. (Press Enter)"; STDIN.gets
1085
+ n = 0
1086
+ # ep nms.values.sum
1087
+ nms.values.sum.each do |var|
1088
+ eputs var if variable_exists? var
1089
+ end
1090
+ eputs "Conflicting Variables. (Press Enter)";; STDIN.gets
1091
+ nms.each do |namelist, vars|
1092
+ ep namelist
1093
+ ep vars
1094
+ vars.each do |var|
1095
+ # next unless var == :w_antenna
1096
+ ep var
1097
+ values_text = source.scan(Regexp.new("\\W#{var}\\s*=\\s*.+")).join("\n")
1098
+ ep values_text
1099
+ values = scan_text_for_variables(values_text).map{|(v,val)| val}
1100
+ values.uniq!
1101
+ # ep values if var == :nbeta
1102
+ values.delete_if{|val| val.kind_of? String} if values.find{|val| val.kind_of? Numeric}
1103
+ values.delete_if{|val| val.kind_of? String and not String::FORTRAN_BOOLS.include? val} if values.find{|val| val.kind_of? String and String::FORTRAN_BOOLS.include? val}
1104
+ # values.sort!
1105
+ # ep var
1106
+ # ep values
1107
+ sample_val = values[0]
1108
+ if not values[0] or ( values[0].kind_of? String and not String::FORTRAN_BOOLS.include? values[0])
1109
+ p source.scan(Regexp.new("^\s*(?<type>integer|float|character|logical|real|double|complex)(?:&[\\n\\r]|.)*\\W#{var}\\W", Regexp::IGNORECASE)).uniq
1110
+ p var unless $~
1111
+
1112
+ case $~[:type]
1113
+ when /logical/
1114
+ sample_val = '.false.'
1115
+ when /int/
1116
+ sample_val = 0
1117
+ when 'real', 'float', 'double'
1118
+ sample_val = 0.0
1119
+ when /character/
1120
+ sample_val = ""
1121
+ when /complex/
1122
+ sample_val = Complex(0.0, 0.0)
1123
+ end
1124
+ # type = Feedback.get_choice("Found the following possible values for '#{var}' in namelist '#{namelist}': #{values.inspect} but cannot determine its type. Please choose its type", ['Float', 'Integer', 'String', 'Unknown' ])
1125
+ # ep type
1126
+ n +=1
1127
+
1128
+ end
1129
+ p namelist, var, sample_val
1130
+ add_code_variable_to_namelist(namelist, var, sample_val)
1131
+ end
1132
+ end
1133
+ ep n
1134
+ end
1135
+
1136
+
1137
+
1138
+ #FORTRAN_SINGLE_LINE = /(?:(?:&\s*[\n\r]+)|(?:[ \t]*!.*[\n\r]+)|[^!\n\r])*/
1139
+ #FORTRAN_SINGLE_LINE = /(?:(?:&\s*[\n\r]+)|(?:&[ \t]*!.*[\n\r]+)|[^!\n\r])*/
1140
+ FORTRAN_SINGLE_LINE = /(?:
1141
+ (?:&[ \t]*\r?\n? #continuing line with
1142
+ (?:[ \t]*!.*\r?\n?)+) # multiple comments
1143
+ |
1144
+ (?:&\s*[\n\r]+) # continuing line
1145
+ |
1146
+ [^!\n\r])* # non continuing line
1147
+ /x
1148
+
1149
+ def self.update_text_options(source_code_folder = ARGV[-1])
1150
+ source = get_aggregated_source_code_text(source_code_folder)
1151
+ options = {}
1152
+ source.scan(/^\s*type\s*\(text_option\)\s*.*?\:\:\s*(?<var>\w+)\s+(?<options>(?:(?:&\s*[\n\r]+)|(?:\s*!.*[\n\r])|[^!\n\r])*)/) do
1153
+
1154
+ name = $~[:var]
1155
+ # eputs $~ if name == "adiabaticopts"
1156
+ opts = $~[:options].scan(/text_option\('([^']+)/).flatten
1157
+ name = "collision_model_opts" if opts.include? "krook"
1158
+ options[name] = opts
1159
+ end
1160
+ mapping = {}
1161
+ # get_option_value &
1162
+ # (ginit_option, ginitopts, ginitopt_switch, &
1163
+ source.scan(/^\s*call\s*get_option_value[\s|&]*\((?<var>\w+),[\s|&]*(?<options>\w+)/) do
1164
+ # p $~
1165
+ var = $~[:var].to_sym
1166
+ op = $~[:options]
1167
+ op = "collision_model_opts" if var == :collision_model
1168
+ mapping[var] = op
1169
+ end
1170
+ # pp mapping
1171
+ # pp options
1172
+ # string_vars = []
1173
+ rcp.namelists.each do |namelist, nhash|
1174
+ nhash[:variables].each do |var, varhash|
1175
+ if varhash[:type] == :String
1176
+ if mapping[(varhash[:code_name] or var)]
1177
+ varhash[:text_options] = options[mapping[var]].uniq
1178
+ # pp var, varhash
1179
+ end
1180
+
1181
+ end
1182
+ end
1183
+ end
1184
+ save_namelists
1185
+ # ep options, string_vars, mapping
1186
+ # File.open(File.dirname(__FILE__) + '/namelists.rb', 'w'){|f| f.puts NAMELISTS.pretty_inspect}
1187
+ end
1188
+
1189
+ def self.print_doxygen_variable_documentation(variable=ARGV[2])
1190
+ #rcp.variables_with_help.each do |var, help|
1191
+ #next if var
1192
+ #puts var, "\n", help.gsub(/*/, '-'), "\n"
1193
+ #end
1194
+ #["! <CRDOC #{variable}: CodeRunner generated doc for #{variable}: edit on the wiki!>\n", " !>" + (rcp.variables_with_help[variable.to_sym]||"").gsub(
1195
+ [ " !>" + (rcp.variables_with_help[variable.to_sym]||"").gsub(
1196
+ /\<math\>/, "\\f$").gsub(
1197
+ /\<\/math\>/, "\\f$").gsub(
1198
+ /^\s*\*\*/, " - ").gsub(
1199
+ /^\s*\#\#/, " #- ").gsub(
1200
+ /^\s*\#/, '#- ').gsub(
1201
+ /^\s*\*/, '- ').gsub(
1202
+ /[^\A]^/, "\n !!")].join(" ")
1203
+ #/[^\A]^/, "\n !!"), "\n ! </CRDOC #{variable}>"].join(" ")
1204
+ #/[^\A]^/, "\n !!"), "\n !!- NB, this is automatically generated documentation for an input parameter... see also the wiki page!", "\n ! </CRDOC #{variable}>"].join(" ")
1205
+
1206
+ end
1207
+ class << self
1208
+ alias :pdvd :print_doxygen_variable_documentation
1209
+
1210
+ # Work out which module a variable is found in
1211
+ def get_variable_modules(folder=ARGV[2])
1212
+ text = get_aggregated_source_code_text(folder)
1213
+ #puts text
1214
+ modules = {}
1215
+ regex = /^\s*module\s+(\w+)((?:.|\n)*?)^\s*end\s+module\s+\g<1>/i
1216
+ #regex = /^\s*module\s+(\w+\b)/i
1217
+ p regex
1218
+ text.scan(regex) do
1219
+ p $~[1]
1220
+ modules[$~[1].to_sym] = $~[2]
1221
+ end
1222
+ #pp modules
1223
+
1224
+
1225
+ rcp.namelists.each do |nmlist, hash|
1226
+ hash[:variables].each do |var, varhash|
1227
+ #regex = Regexp.new("module\\s+(\\w+)(?:.|\\n)*?public.*?(#{var})(?:.|\\n)*?namelist(#{FORTRAN_SINGLE_LINE})(?:.|\\n)*?end\\s+module\\s+\\1")
1228
+ #regex = Regexp.new("module\\s+(\\w+)(?:.|\\n)*?public.*?(#{var})(?:.|\\n)*?namelist(#{FORTRAN_SINGLE_LINE})")
1229
+ #regex = Regexp.new("public.*?(#{var})(?:.|\\n)*?namelist#{FORTRAN_SINGLE_LINE}#{var}")
1230
+ regex = Regexp.new("namelist#{FORTRAN_SINGLE_LINE}#{var}")
1231
+ #p regex
1232
+ modules.each do |m, mod|
1233
+ mod.scan(regex) do
1234
+ varhash[:module] = m.to_sym
1235
+ end
1236
+ end
1237
+ end
1238
+ end
1239
+ save_namelists
1240
+ end
1241
+ alias :gvm :get_variable_modules
1242
+
1243
+ #Make a file with doxygen style comments to document
1244
+ #the input parameters
1245
+
1246
+ def print_doxygen_documentation
1247
+ puts "! This file is not part of GS2, but exists to document those variables which are also input parameters. It is generated automatically by CodeRunner from its input parameter database. To update this database, DO NOT edit this file, your changes will be lost: go to the wiki page for the input parameters (http://sourceforge.net/apps/mediawiki/gyrokinetics/index.php?title=Gs2_Input_Parameters) and follow the instructions there. \n\n"
1248
+
1249
+ rcp.namelists.each do |nmlist, hash|
1250
+ hash[:variables].each do |var, varhash|
1251
+ next unless varhash[:module] and varhash[:help]
1252
+ puts "module #{varhash[:module]}\n\n\n"
1253
+ puts " #{print_doxygen_variable_documentation(var)}"
1254
+ #puts " public :: #{var}"
1255
+ puts " #{fortran_type(varhash)} :: #{var}"
1256
+ puts "end module #{varhash[:module]}"
1257
+
1258
+ end
1259
+ end
1260
+ end
1261
+ alias :pdd :print_doxygen_documentation
1262
+ def fortran_type(varhash)
1263
+ case varhash[:type]
1264
+ when :Float
1265
+ 'real'
1266
+ when :Fortran_Bool
1267
+ 'logical'
1268
+ when :String
1269
+ 'character'
1270
+ when :Integer
1271
+ 'integer'
1272
+ else
1273
+ raise 'unknown type'
1274
+ end
1275
+ end
1276
+ end
1277
+
1278
+ end # class FortranNamelist
1279
+
1280
+ end # class Run
1281
+ end # class CodeRunner