mesa_script 0.1.4 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mesa_script.rb +441 -200
  3. metadata +6 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c5625fddbfb3e4c4f87d42ddc201c5adb6b45a75
4
- data.tar.gz: 8b5e505dd02e1d65d84bd0bea5aaeef49076105b
3
+ metadata.gz: 61f0c3a50b013b45d21a7cb35923258fa442e28e
4
+ data.tar.gz: ae3672c28b5ac2b79e20251457a841455f363951
5
5
  SHA512:
6
- metadata.gz: b6659a3b7d8b6cc11a59778c5fea9932ecead35b1a1ff2d8b14416e118414a1ebd306d36bd934fd15cd85aed75de65c6440439f65c7dd7ef393e4d10b5036d40
7
- data.tar.gz: 1d365d16c557a9f06b055d643dc56f5ffe3f29de9487a444da6691a9a041180fb7a9586241cda93c37fed8a1fa93320ee2f8aa13aed2565cd283e25fb73f2682
6
+ metadata.gz: 9fc6a8d0051a2095e5e9cbc53d255d91d48b508c26702175ad3afd3fc7341787a8503ea2c261323d5465b387e2ef025e508269391bc5f6f4d508b661b39ac5f5
7
+ data.tar.gz: 329a2b712e47b20cd553869daa01b2548ac1bb900b229d3961627828de0c420438db5f735f40f6f0b7b5ceb9a1bfae62c2e48a3be63ef9e1528955c1621f728b
data/lib/mesa_script.rb CHANGED
@@ -1,50 +1,187 @@
1
1
  InlistItem = Struct.new(:name, :type, :value, :namelist, :order, :is_arr,
2
2
  :num_indices, :flagged)
3
+ def namelist_sym(namelist)
4
+ namelist.to_s.downcase.to_sym
5
+ end
3
6
 
4
7
  class Inlist
5
-
6
8
  # Get access to current MESA version.
7
9
  def self.version
8
- v_num = IO.read(File.join(ENV['MESA_DIR'], 'data', 'version_number')).to_i
9
- return v_num
10
+ IO.read(File.join(ENV['MESA_DIR'], 'data', 'version_number')).to_i
10
11
  end
11
12
 
12
13
  # Determine proper file suffix for fortran source
13
14
  def self.f_end
14
15
  if Inlist.version >= 7380
15
- "f90"
16
+ 'f90'
16
17
  else
17
- "f"
18
+ 'f'
18
19
  end
19
20
  end
20
21
 
22
+ # these hold the names of the namelists as well as the locations of the
23
+ # fortran files that define their controls as well as the defaults files
24
+ # that define their default values and order in formatted inlists
25
+ @namelists = []
26
+ @source_files = Hash.new([])
27
+ @defaults_files = {}
21
28
 
29
+ # this holds the "guts"; control names, types, defaults, and orders
22
30
  @inlist_data = {}
23
- # Different namelists can be added or subtracted if MESA should change or
24
- # proprietary inlists are required. Later hashes should be edited in a
25
- # similar way to get the desired behavior for additional namelists.
26
31
 
27
- #################### ADD NEW NAMELISTS HERE ####################
28
- @namelists = %w{ star_job controls pgstar }
29
- ################## POINT TO .INC FILES HERE ####################
30
- @nt_files = {
31
- 'star_job' => %w{star_job_controls.inc},
32
- 'controls' => %w{star_controls.inc},
33
- 'pgstar' => %w{pgstar_controls.inc}
34
- }
35
- @nt_files['controls'] << "ctrls_io.#{f_end}"
36
- # User can specify a custom name for a namelist defaults file. The default
37
- # is simply the namelist name followed by '.defaults'
32
+ # used to turn on a namelist; need to provide namelist name as well as
33
+ # locations for source file (usually a .inc or .f90 file that defines
34
+ # allowable controls) and a defaults file (usually a .defaults file that
35
+ # lists all controls and their default values). Shorthand versions for
36
+ # the three common star namelists and the one common binary namelist are
37
+ # defined below for convenience
38
+ def self.config_namelist(namelist: nil, source_files: nil,
39
+ defaults_file: nil, verbose: true)
40
+ new_namelist = namelist_sym(namelist)
41
+ add_namelist(new_namelist)
42
+ set_source_files(new_namelist, source_files)
43
+ set_defaults_file(new_namelist, defaults_file)
44
+ return unless verbose
45
+ puts 'Added the following namelist data:'
46
+ puts " namelist: #{new_namelist}"
47
+ puts " source: #{@source_files[new_namelist].join(', ')}"
48
+ puts " defaults: #{@defaults_files[namelist_sym(namelist)]}"
49
+ puts "Did not load data yet, though.\n\n"
50
+ end
51
+
52
+ def self.add_namelist(new_namelist)
53
+ if new_namelist.nil? || new_namelist.empty?
54
+ raise(NamelistError.new, 'Must provide a namelist name.')
55
+ end
56
+ return if @namelists.include? namelist_sym(new_namelist)
57
+ @namelists << namelist_sym(new_namelist)
58
+ end
59
+
60
+ def self.set_source_files(namelist, new_sources)
61
+ # set source files. There may be more than one, so we ALWAYS make it an
62
+ # array. Flatten magic allows for users to supply an array or a scalar
63
+ # (single string)
64
+ if new_sources.nil? || new_sources.empty?
65
+ raise NamelistError.new,
66
+ "Must provide a source file for namelist #{namelist}. For " \
67
+ 'example, $MESA_DIR/star/private/star_job_controls.inc for ' \
68
+ 'star_job.'
69
+ end
70
+ source_to_add = if new_sources.respond_to?(:map)
71
+ new_sources.map(&:to_s)
72
+ else
73
+ new_sources.to_s
74
+ end
75
+ @source_files[namelist_sym(namelist)] = [source_to_add].flatten
76
+ end
77
+
78
+ def self.set_defaults_file(namelist, new_defaults_file)
79
+ # set defaults file. This is limited to being scalar string for now.
80
+ return unless new_defaults_file
81
+ @defaults_files[namelist_sym(namelist)] = new_defaults_file.to_s
82
+ end
83
+
84
+ # Delete all namelists and associated data.
85
+ def self.delete_all_namelists
86
+ namelists.each { |namelist| remove_namelist(namelist) }
87
+ @have_data = false
88
+ end
89
+
90
+ # delete namelist and associated data
91
+ def self.delete_namelist(namelist)
92
+ to_delete = namelist_sym(namelist)
93
+ found_something = namelists.delete(to_delete)
94
+ found_something = delete_files(to_delete) || found_something
95
+ # this also undefines methods
96
+ found_something = delete_data(to_delete) || found_something
97
+ unless found_something
98
+ puts "WARNING: Attempting to delete namelist #{namelist} data, but it " \
99
+ "wasn't present in existing Inlist data. Nothing happened."
100
+ end
101
+ namelist
102
+ end
103
+
104
+ # just remove associated source and defaults files; don't touch underlying
105
+ # data or methods (if any exist yet)
106
+ def self.delete_files(namelist)
107
+ found_something = false
108
+ [source_files, defaults_files].each do |files|
109
+ found_something ||= files.delete(namelist_sym(namelist))
110
+ end
111
+ found_something
112
+ end
113
+
114
+ # see if data has been loaded, and if it has, delete all the methods
115
+ # associated with it and then wipe the data, too.
116
+ def self.delete_data(namelist)
117
+ to_delete = namelist_sym(namelist)
118
+ return false unless inlist_data.include? namelist_sym(to_delete)
119
+ delete_methods(to_delete)
120
+ inlist_data.delete(to_delete)
121
+ end
122
+
123
+ # just delete the methods. This will throw errors if they don't exist
124
+ def self.delete_methods(namelist)
125
+ @inlist_data[namelist_sym(namelist)].each { |datum| delete_method(datum) }
126
+ end
38
127
 
39
- ################ POINT TO .DEFAULTS FILES HERE #################
40
- @d_files = {}
128
+ # short hand for adding star_job namelist using sensible defaults as of 10108
129
+ def self.add_star_job_defaults(verbose: false)
130
+ config_namelist(
131
+ namelist: :star_job,
132
+ source_files: File.join(ENV['MESA_DIR'], 'star', 'private',
133
+ 'star_job_controls.inc'),
134
+ defaults_file: File.join(ENV['MESA_DIR'], 'star', 'defaults',
135
+ 'star_job.defaults'),
136
+ verbose: verbose
137
+ )
138
+ end
41
139
 
42
- # User can add new paths to namelist default files through this hash
140
+ # short hand for adding controls namelist using sensible defaults as of 10108
141
+ def self.add_controls_defaults(verbose: false)
142
+ config_namelist(
143
+ namelist: :controls,
144
+ source_files: [File.join(ENV['MESA_DIR'], 'star', 'private',
145
+ 'star_controls.inc'),
146
+ File.join(ENV['MESA_DIR'], 'star', 'private',
147
+ "ctrls_io.#{f_end}")],
148
+ defaults_file: File.join(ENV['MESA_DIR'], 'star', 'defaults',
149
+ 'controls.defaults'),
150
+ verbose: verbose
151
+ )
152
+ end
43
153
 
44
- ############ GIVE PATHS TO .INC AND .DEF FILES HERE ###########
45
- @nt_paths = Hash.new(ENV['MESA_DIR'] + '/star/private/')
46
- @d_paths = Hash.new(ENV['MESA_DIR'] + '/star/defaults/')
154
+ # short hand for adding pgstar namelist using sensible defaults as of 10108
155
+ def self.add_pgstar_defaults(verbose: false)
156
+ config_namelist(
157
+ namelist: :pgstar,
158
+ source_files: File.join(ENV['MESA_DIR'], 'star', 'private',
159
+ 'pgstar_controls.inc'),
160
+ defaults_file: File.join(ENV['MESA_DIR'], 'star', 'defaults',
161
+ 'pgstar.defaults'),
162
+ verbose: verbose
163
+ )
164
+ end
47
165
 
166
+ # short hand for adding binary_defaults namelist using sensible defaults as
167
+ # of 10108
168
+ def self.add_binary_defaults
169
+ config_namelist(
170
+ namelists: :binary_controls,
171
+ source_files: File.join(ENV['MESA_DIR'], 'binary', 'public',
172
+ 'binary_controls.inc'),
173
+ defaults_file: File.join(ENV['MESA_DIR'], 'binary', 'defaults',
174
+ 'binary_controls.defaults')
175
+ )
176
+ end
177
+
178
+ # quickly add all three major namelists for star module (star_job, controls,
179
+ # and pgstar)
180
+ def self.add_star_defaults
181
+ add_star_job_defaults
182
+ add_controls_defaults
183
+ add_pgstar_defaults
184
+ end
48
185
 
49
186
  ############### NO MORE [SIMPLE] USER-CUSTOMIZABLE FEATURES BELOW ##############
50
187
 
@@ -57,34 +194,44 @@ class Inlist
57
194
  # Establish class instance variables
58
195
  class << self
59
196
  attr_accessor :have_data
60
- attr_accessor :namelists, :nt_paths, :d_paths, :inlist_data, :d_files,
61
- :nt_files
197
+ attr_accessor :namelists, :source_files, :defaults_files, :inlist_data
62
198
  end
63
199
 
64
200
  # Generate methods for the Inlist class that set various namelist parameters.
65
- def self.get_data
201
+ def self.get_data(use_star_as_fallback: true)
202
+ # might need to add star data; preserves expected behavior (minus binary)
203
+ Inlist.add_star_defaults if use_star_as_fallback && Inlist.namelists.empty?
66
204
  Inlist.namelists.each do |namelist|
67
- @inlist_data[namelist] = Inlist.get_namelist_data(namelist,
68
- Inlist.nt_files[namelist], Inlist.d_files[namelist])
205
+ @inlist_data[namelist] = Inlist.get_namelist_data(namelist)
69
206
  end
70
207
  # create methods (interface) for each data category
71
208
  @inlist_data.each_value do |namelist_data|
72
- namelist_data.each do |datum|
73
- if datum.is_arr
74
- Inlist.make_parentheses_method(datum)
75
- else
76
- Inlist.make_regular_method(datum)
77
- end
78
- end
209
+ namelist_data.each { |datum| Inlist.make_method(datum) }
79
210
  end
80
211
  # don't do this nonsense again unles specifically told to do so
81
212
  Inlist.have_data = true
82
213
  end
83
214
 
215
+ def self.make_method(datum)
216
+ if datum.is_arr
217
+ Inlist.make_parentheses_method(datum)
218
+ else
219
+ Inlist.make_regular_method(datum)
220
+ end
221
+ end
222
+
223
+ def self.delete_method(datum)
224
+ if datum.is_arr
225
+ Inlist.delete_parentheses_method(datum)
226
+ else
227
+ Inlist.delete_regular_method(datum)
228
+ end
229
+ end
230
+
84
231
  # Three ways to access array categories. All methods will cause the
85
232
  # data category to be staged into your inlist, even if you do not change it
86
233
  # Basically, if it appears in your mesascript, it will definitely appear
87
- # in your inlist. A command can be unflagged by calling
234
+ # in your inlist. A command can be unflagged by calling
88
235
  # `unflag_command('COMMAND_NAME')` where COMMAND_NAME is the case-sensitive
89
236
  # name of the command to be unflagged.
90
237
  #
@@ -104,19 +251,19 @@ class Inlist
104
251
  # xa_lower_limit_species(1, 'h1') # flags and sets value 1
105
252
  # xa_lower_limit_species 1, 'h1' # same
106
253
  #
107
- # For multi-dimensional arrays, things are even more vaired. You can treat
254
+ # For multi-dimensional arrays, things are even more vaired. You can treat
108
255
  # them like 1-dimensional arrays with the "index" just being an array of
109
256
  # indices, for instance:
110
- #
257
+ #
111
258
  # text_summary1_name[[1,2]] = 'star_mass' # flags ALL values and sets
112
259
  # text_summary1_name([1,2], 'star_mass') # text_summary1_name(1,2)
113
260
  # text_summary1_name [1,2], 'star_mass # to 'star_mass'
114
261
  #
115
- # text_summary1_name [1,2] # flags ALL values and
116
- # text_summary1_name([1,2]) # returns
262
+ # text_summary1_name [1,2] # flags ALL values and
263
+ # text_summary1_name([1,2]) # returns
117
264
  # # text_sumarry_name(1,2)
118
- #
119
- # text_summary_name() # flags ALL values and
265
+ #
266
+ # text_summary_name() # flags ALL values and
120
267
  # text_summary_name # returns entire hash for
121
268
  # # text_summary_name
122
269
  #
@@ -126,64 +273,127 @@ class Inlist
126
273
  #
127
274
  # text_summary1_name(1, 2, 'star_mass')
128
275
  # text_summary1_name 1, 2, 'star_mass' # same as above (first 3)
129
- #
276
+ #
130
277
  # text_summary1_name
131
-
278
+
132
279
  def self.make_parentheses_method(datum)
133
- name = datum.name
280
+ method_name = datum.name
134
281
  num_indices = datum.num_indices
135
- define_method(name + '[]=') do|arg1, arg2|
282
+
283
+ # assignment array form
284
+ define_method(method_name + '[]=') do |arg1, arg2|
136
285
  if num_indices > 1
137
- raise "First argument of #{name}[]= (part in brackets) must be an array with #{num_indices} indices since #{name} is a multi-dimensional array." unless (arg1.is_a?(Array) and arg1.length == num_indices)
286
+ unless arg1.is_a?(Array) && arg1.length == num_indices
287
+ raise "First argument of #{method_name}[]= (part in brackets) must "\
288
+ "be an array with #{num_indices} indices since #{method_name}"\
289
+ ' is a multi-dimensional array.'
290
+ end
138
291
  end
139
- self.flag_command(name)
140
- self.data_hash[name].value[arg1] = arg2
292
+ flag_command(method_name)
293
+ data_hash[method_name].value[arg1] = arg2
141
294
  end
142
- define_method(name + '[]') do |arg|
295
+
296
+ # de-referencing array form
297
+ define_method(method_name + '[]') do |arg|
143
298
  if num_indices > 1
144
- raise "Argument of #{name}[] (part in brackets) must be an array with #{num_indices} indices since #{name} is a multi-dimensional array." unless (arg.is_a?(Array) and arg.length == num_indices)
299
+ unless arg.is_a?(Array) && arg.length == num_indices
300
+ raise "Argument of #{method_name}[] (part in brackets) must be an " \
301
+ "array with #{num_indices} indices since #{method_name} is a "\
302
+ 'multi-dimensional array.'
303
+ end
145
304
  end
146
- self.flag_command(name)
147
- self.data_hash[name].value[arg]
305
+ flag_command(method_name)
306
+ data_hash[method_name].value[arg]
148
307
  end
149
- define_method(name) do |*args|
150
- self.flag_command(name)
308
+
309
+ # imperative multi-purpose form
310
+ define_method(method_name) do |*args|
311
+ flag_command(method_name)
151
312
  case args.length
152
- when 0 then self.data_hash[name].value
313
+ # just retrieve whole value (de-reference)
314
+ when 0 then data_hash[method_name].value
315
+ # just retrieve part of value (de-reference)
153
316
  when 1
154
317
  if num_indices > 1
155
- raise "First argument of #{name} must be an array with #{num_indices} indices since #{name} is a multi-dimensional array OR must provide all indices as separate arguments." unless (args[0].is_a?(Array) and args[0].length == num_indices)
318
+ unless args[0].is_a?(Array) && args[0].length == num_indices
319
+ raise "First argument of #{method_name} must be an array with " \
320
+ "#{num_indices} indices since #{method_name} is a " \
321
+ 'multi-dimensional array OR must provide all indices as ' \
322
+ 'separate arguments.'
323
+ end
156
324
  end
157
- self.data_hash[name].value[args[0]]
325
+ data_hash[method_name].value[args[0]]
326
+ # might be trying to access or a multi-d array OR assign to an array.
158
327
  when 2
159
- if num_indices == 1 and (not args[0].is_a?(Array))
160
- self.data_hash[name].value[args[0]] = args[1]
161
- elsif num_indices == 2 and (not args[0].is_a?(Array)) and args[1].is_a?(Fixnum)
162
- self.data_hash[name].value[args]
328
+ # 1-D array with scalar value; simple assignement
329
+ if num_indices == 1 && !args[0].is_a?(Array)
330
+ data_hash[method_name].value[args[0]] = args[1]
331
+ # 2-D array, de-reference single value (NOT AN ASSIGNMENT!)
332
+ elsif num_indices == 2 && !args[0].is_a?(Array) &&
333
+ args[1].is_a?(Integer)
334
+ data_hash[method_name].value[args]
335
+ # Multi-d array with first argument being an array, second a value to
336
+ # assign; simple assignment
163
337
  elsif num_indices > 1
164
- raise "First argument of #{name} must be an array with #{num_indices} indices since #{name} is a multi-dimensional array OR must provide all indices as separate arguments." unless (args[0].is_a?(Array) and args[0].length == num_indices)
165
- self.data_hash[name].value[args[0]] = args[1]
338
+ unless args[0].is_a?(Array) && args[0].length == num_indices
339
+ raise "First argument of #{method_name} must be an array with " \
340
+ "#{num_indices} indices since #{method_name} is a " \
341
+ 'multi-dimensional array OR must provide all indices as ' \
342
+ 'separate arguments.'
343
+ end
344
+ data_hash[method_name].value[args[0]] = args[1]
345
+ # Can't parse... throw hands up.
166
346
  else
167
- raise "First argument of #{name} must be an array with #{num_indices} indices since #{name} is a multi-dimensional array OR must provide all indices as separate arguments. The optional final argument is what the #{name} would be set to. Omission of this argument will simply flag #{name} to appear in the inlist."
168
- end
169
- when num_indices
170
- self.data_hash[name].value[args]
347
+ raise "First argument of #{method_name} must be an array with "\
348
+ "#{num_indices} indices since #{method_name} is a "\
349
+ 'multi-dimensional array OR must provide all indices as '\
350
+ 'separate arguments. The optional final argument is what the '\
351
+ "#{method_name} would be set to. Omission of this argument "\
352
+ "will simply flag #{method_name} to appear in the inlist."
353
+ end
354
+ # one more argument than number of indices; first n are location to be
355
+ # assigned, last one is value to be assigned
171
356
  when num_indices + 1
172
- raise "Bad arguments for #{name}. Either provide an array of #{num_indices} indices for the first argument or provide each index in succession, optionally specifying the desired value for the last argument." if args[0].is_a?(Array)
173
- self.data_hash[name].value[args[0..-2]] = args[-1]
357
+ if args[0].is_a?(Array)
358
+ raise "Bad arguments for #{method_name}. Either provide an array " \
359
+ "of #{num_indices} indices for the first argument or provide "\
360
+ 'each index in succession, optionally specifying the desired '\
361
+ 'value for the last argument.'
362
+ end
363
+ data_hash[method_name].value[args[0..-2]] = args[-1]
364
+ # same number of arguments as indices; assume we are de-referencing a
365
+ # value
366
+ when num_indices then data_hash[method_name].value[args]
367
+ # give up... who knows what the user is doing?!
174
368
  else
175
- raise "Wrong number of arguments for #{name}. Can provide zero arguments (just flag command), one argument (array of indices for multi-d array or one index for 1-d array), two arguments (array of indices/single index for multi-/1-d array and a new value for the value), #{num_indices} arguments where the elements themselves are the right indices (returns the specified element of the array), or #{num_indices + 1} arguments to set the specific value and return it."
176
- end
369
+ raise "Wrong number of arguments for #{method_name}. Can provide " \
370
+ 'zero arguments (just flag command), one argument (array of ' \
371
+ 'indices for multi-d array or one index for 1-d array), two ' \
372
+ 'arguments (array of indices/single index for multi-/1-d array '\
373
+ 'and a new value for the value), #{num_indices} arguments ' \
374
+ 'where the elements themselves are the right indices (returns ' \
375
+ "the specified element of the array), or #{num_indices + 1} " \
376
+ 'arguments to set the specific value and return it.'
377
+ end
177
378
  end
178
- alias_method name.downcase.to_sym, name.to_sym
179
- alias_method (name.downcase + '[]').to_sym, (name + '[]').to_sym
180
- alias_method (name.downcase + '[]=').to_sym, (name + '[]=').to_sym
379
+ alias_method method_name.downcase.to_sym, method_name.to_sym
380
+ alias_method((method_name.downcase + '[]').to_sym,
381
+ (method_name + '[]').to_sym)
382
+ alias_method((method_name.downcase + '[]=').to_sym,
383
+ (method_name + '[]=').to_sym)
384
+ end
385
+
386
+ def self.delete_parentheses_method(datum)
387
+ base_name = datum.name
388
+ method_names = [base_name, base_name + '[]', base_name + '[]=']
389
+ alias_names = method_names.map(&:downcase)
390
+ [method_names, alias_names].flatten.uniq.each { |meth| remove_method(meth) }
181
391
  end
182
392
 
183
393
  # Two ways to access/change scalars. All methods will cause the data category
184
394
  # to be staged into your inlist, even if you do not change the value.
185
395
  # Basically, if it appears in your mesascript, it will definitely appear in
186
- # your inlist.
396
+ # your inlist.
187
397
  #
188
398
  # 1. Change value, like
189
399
  # initial_mass(1.0)
@@ -200,23 +410,30 @@ class Inlist
200
410
  # traditional for ruby (why do you want empty parentheses anyway?). Returns
201
411
  # current value.
202
412
  #
203
- # A command can be unflagged by calling `unflag_command('COMMAND_NAME')`
413
+ # A command can be unflagged by calling `unflag_command('COMMAND_NAME')`
204
414
  # where COMMAND_NAME is the case-sensitive name of the command to be
205
415
  # unflagged.
206
416
 
207
417
  def self.make_regular_method(datum)
208
- name = datum.name
209
- define_method(name) do |*args|
210
- self.flag_command(name)
211
- return self.data_hash[name].value if args.empty?
212
- self.data_hash[name].value = args[0]
418
+ method_name = datum.name
419
+ define_method(method_name) do |*args|
420
+ self.flag_command(method_name)
421
+ return self.data_hash[method_name].value if args.empty?
422
+ self.data_hash[method_name].value = args[0]
213
423
  end
214
- aliases = [(name + '=').to_sym,
215
- (name.downcase + '=').to_sym,
216
- name.downcase.to_sym]
217
- aliases.each { |ali| alias_method ali, name.to_sym }
424
+ aliases = [(method_name + '=').to_sym,
425
+ (method_name.downcase + '=').to_sym,
426
+ method_name.downcase.to_sym]
427
+ aliases.each { |ali| alias_method ali, method_name.to_sym }
218
428
  end
219
429
 
430
+ def self.delete_regular_method(datum)
431
+ method_name = datum.name
432
+ aliases = [method_name + '=',
433
+ method_name.downcase + '=',
434
+ method_name.downcase]
435
+ [method_name, aliases].flatten.uniq.each { |meth| remove_method meth }
436
+ end
220
437
 
221
438
  # Ensure provided value's data type matches expected data type. Then convert
222
439
  # to string for printing to an inlist. If value is a string, change nothing
@@ -225,39 +442,49 @@ class Inlist
225
442
  def self.parse_input(name, value, type)
226
443
  if value.class == String
227
444
  if type == :string
228
- value = "'#{value}'" unless value[0] == "'" and value[-1] == "'"
445
+ value = "'#{value}'" unless value[0] == "'" && value[-1] == "'"
229
446
  end
230
- return value
447
+ value
231
448
  elsif type == :bool
232
449
  unless [TrueClass, FalseClass].include?(value.class)
233
- raise "Invalid value for namelist item #{name}: #{value}. Use " +
234
- "'.true.', '.false.', or a Ruby boolean (true/false)."
450
+ raise "Invalid value for namelist item #{name}: #{value}. Use " \
451
+ "'.true.', '.false.', or a Ruby boolean (true/false)."
235
452
  end
236
453
  if value == true
237
- return '.true.'
454
+ '.true.'
238
455
  elsif value == false
239
- return '.false.'
456
+ '.false.'
240
457
  else
241
458
  raise "Error converting value #{value} of #{name} to a boolean."
242
459
  end
243
460
  elsif type == :int
244
- raise "Invalid value for namelist item #{name}: #{value}. Must provide"+
245
- " an int or float." unless value.is_a?(Integer) or value.is_a?(Float)
461
+ unless value.is_a?(Integer) || value.is_a?(Float)
462
+ raise "Invalid value for namelist item #{name}: #{value}. Must " \
463
+ 'provide an int or float.'
464
+ end
246
465
  if value.is_a?(Float)
247
- puts "WARNING: Expected integer for #{name} but got #{value}. Value" +
248
- " will be converted to an integer."
466
+ puts "WARNING: Expected integer for #{name} but got #{value}. Value" \
467
+ ' will be converted to an integer.'
249
468
  end
250
- return value.to_i.to_s
469
+ value.to_i.to_s
251
470
  elsif type == :float
252
- raise "Invalid value for namelist item #{name}: #{value}. Must provide " +
253
- "an int or float." unless value.is_a?(Integer) or value.is_a?(Float)
254
- return sprintf("%g", value).sub('e', 'd')
471
+ unless value.is_a?(Integer) || value.is_a?(Float)
472
+ raise "Invalid value for namelist item #{name}: #{value}. Must "\
473
+ 'provide an int or float.'
474
+ end
475
+ res = format('%g', value).sub('e', 'd')
476
+ res += 'd0' unless res.include?('d')
477
+ res
255
478
  elsif type == :type
256
- puts "WARNING: 'type' values are currently unsupported (regarding #{name}) because your humble author has no idea what they look like in an inlist. You should tell him what to do at wmwolf@physics.ucsb.edu. Your input, #{value}, has been passed through to your inlist verbatim."
257
- return value.to_s
479
+ puts "WARNING: 'type' values are currently unsupported " \
480
+ "(regarding #{name}) because your humble author has no idea what " \
481
+ 'they look like in an inlist. You should tell him what to do at ' \
482
+ "wmwolf@asu.edu. Your input, #{value}, has been passed through to "\
483
+ 'your inlist verbatim.'
484
+ value.to_s
258
485
  else
259
- raise "Error parsing value for namelist item #{name}: #{value}. Expected "
260
- "type was #{type}."
486
+ raise "Error parsing value for namelist item #{name}: #{value}. " \
487
+ "Expected type was #{type}."
261
488
  end
262
489
  end
263
490
 
@@ -268,7 +495,7 @@ class Inlist
268
495
  # clean up and re-order your inlist, but all comments will be lost. All other
269
496
  # information SHOULD remain intact.
270
497
  def self.inlist_to_mesascript(inlist_file, script_file, dbg = false)
271
- Inlist.get_data unless Inlist.have_data # ensure we have inlist data
498
+ Inlist.get_data unless Inlist.have_data # ensure we have inlist data
272
499
  inlist_contents = File.readlines(inlist_file)
273
500
 
274
501
  # make namelist separators comments
@@ -301,7 +528,7 @@ class Inlist
301
528
  command =~ /(\s*$)/ # save buffer space
302
529
  buffer_space = Regexp.last_match(1)
303
530
  command.strip! # remove white space
304
- name, value = command.split('=').map { |datum| datum.strip }
531
+ name, value = command.split('=').map(&:strip)
305
532
  if dbg
306
533
  puts "name: #{name}"
307
534
  puts "value: #{value}"
@@ -309,28 +536,28 @@ class Inlist
309
536
  if name =~ /\((\d+)\)/ # fix 1D array assignments
310
537
  name.sub!('(', '[')
311
538
  name.sub!(')', ']')
312
- name = name + ' ='
539
+ name += ' ='
313
540
  elsif name =~ /\((\s*\d+\s*,\s*)+\d\s*\)/ # fix multi-D arrays
314
541
  # arrays become hashes in MesaScript, so rather than having multiple
315
542
  # indices, the key becomes the array of indices themselves, hence
316
543
  # the double braces replacing single parentheses
317
- name.sub!('(', '[[')
544
+ name.sub!('(', '[[')
318
545
  name.sub!(')', ']]')
319
- name = name + ' ='
546
+ name += ' ='
320
547
  end
321
548
  name.downcase!
322
- if value =~ /'.*'/ or value =~ /".*"/
323
- result = name + ' ' + value # leave strings alone
324
- elsif %w[.true. .false.].include?(value.downcase)
325
- result = name + ' ' + value.downcase.gsub('.', '') # fix booleans
326
- elsif value =~ /\d+\.?\d*([eEdD]\d+)?/
327
- result = name + ' ' + value.downcase.sub('d', 'e') # fix floats
328
- else
329
- result = name + ' ' + value # leave everything else alone
330
- end
549
+ results = if value =~ /'.*'/ || value =~ /".*"/
550
+ name + ' ' + value # leave strings alone
551
+ elsif %w[.true. .false.].include?(value.downcase)
552
+ name + ' ' + value.downcase.delete('.') # fix booleans
553
+ elsif value =~ /\d+\.?\d*([eEdD]\d+)?/
554
+ name + ' ' + value.downcase.sub('d', 'e') # fix floats
555
+ else
556
+ name + ' ' + value # leave everything else alone
557
+ end
331
558
  result = leading_space + result + buffer_space + comment
332
559
  if dbg
333
- puts "parsed to:"
560
+ puts 'parsed to:'
334
561
  puts result
335
562
  puts ''
336
563
  end
@@ -342,11 +569,10 @@ class Inlist
342
569
  f.puts ''
343
570
  f.puts "Inlist.make_inlist('#{File.basename(inlist_file)}') do"
344
571
  new_contents.each { |line| f.puts ' ' + line }
345
- f.puts "end"
572
+ f.puts 'end'
346
573
  end
347
574
  end
348
575
 
349
-
350
576
  # Create an Inlist object, execute block of commands that presumably populate
351
577
  # the inlist, then write the inlist to a file with the given name. This is
352
578
  # the money routine with user-supplied commands in the instance_eval block.
@@ -370,29 +596,21 @@ class Inlist
370
596
  # belongs to, and its relative ordering in that namelist. Bogus defaults are
371
597
  # assigned according to the object's type, and the ordering is unknown.
372
598
 
373
- def self.get_namelist_data(namelist, nt_filename = nil, d_filename = nil)
374
- temp_data = Inlist.get_names_and_types(namelist, nt_filename)
375
- Inlist.get_defaults(temp_data, namelist, d_filename)
599
+ def self.get_namelist_data(namelist)
600
+ temp_data = Inlist.get_names_and_types(namelist)
601
+ Inlist.get_defaults(temp_data, namelist)
376
602
  end
377
603
 
378
- def self.get_names_and_types(namelist, nt_filenames = nil)
379
- nt_filenames ||= Inlist.nt_files[namelist]
380
- unless nt_filenames.respond_to?(:each)
381
- nt_filenames = [nt_filenames]
382
- end
383
- nt_full_paths = nt_filenames.map { |file| Inlist.nt_paths[namelist] + file }
384
-
604
+ def self.get_names_and_types(namelist)
385
605
  namelist_data = []
386
606
 
387
- nt_full_paths.each do |nt_full_path|
388
- unless File.exists?(nt_full_path)
389
- raise "Couldn't find file #{nt_full_path}"
390
- end
391
- contents = File.readlines(nt_full_path)
607
+ source_files[namelist].each do |source_file|
608
+ raise "Couldn't find file #{source_file}" unless File.exist?(source_file)
609
+ contents = File.readlines(source_file)
392
610
 
393
611
  # Throw out comments and blank lines, ensure remaining lines are a proper
394
612
  # Fortran assignment, then remove leading and trailing white space
395
- contents.reject! { |line| is_comment?(line) or is_blank?(line) }
613
+ contents.reject! { |line| is_comment?(line) || is_blank?(line) }
396
614
  contents.map! do |line|
397
615
  my_line = line.dup
398
616
  my_line = my_line[0...my_line.index('!')] if has_comment?(my_line)
@@ -405,24 +623,24 @@ class Inlist
405
623
  full_lines << Inlist.full_line(contents, i)
406
624
  end
407
625
  pairs = full_lines.map do |line|
408
- line.split('::').map { |datum| datum.strip}
626
+ line.split('::').map(&:strip)
409
627
  end
410
628
  pairs.each do |pair|
411
629
  type = case pair[0]
412
- when /logical/ then :bool
413
- when /character/ then :string
414
- when /real/ then :float
415
- when /integer/ then :int
416
- when /type/ then :type
417
- else
418
- raise "Couldn't determine type of entry #{pair[0]} in " +
419
- "#{nt_full_path}."
420
- end
630
+ when /logical/ then :bool
631
+ when /character/ then :string
632
+ when /real/ then :float
633
+ when /integer/ then :int
634
+ when /type/ then :type
635
+ else
636
+ raise "Couldn't determine type of entry #{pair[0]} in " \
637
+ "#{source_file}."
638
+ end
421
639
  name_chars = pair[1].split('')
422
640
  names = []
423
641
  paren_level = 0
424
642
  name_chars.each do |char|
425
- if paren_level > 0 and char == ','
643
+ if paren_level > 0 && char == ','
426
644
  names << '!'
427
645
  next
428
646
  elsif char == '('
@@ -432,7 +650,7 @@ class Inlist
432
650
  end
433
651
  names << char
434
652
  end
435
- names = names.join.split(',').map { |name| name.strip }
653
+ names = names.join.split(',').map(&:strip)
436
654
  names.each do |name|
437
655
  is_arr = false
438
656
  num_indices = 0
@@ -442,10 +660,9 @@ class Inlist
442
660
  name.sub!(/\(.*\)/, '')
443
661
  elsif pair[0] =~ /dimension\((.*)\)/i
444
662
  is_arr = true
445
- num_indices = $1.count(',') + 1
663
+ num_indices = Regexp.last_match[1].count(',') + 1
446
664
  end
447
- type_default = {:bool => false, :string => '', :float => 0.0,
448
- :int => 0}
665
+ type_default = { bool: false, string: '', float: 0.0, int: 0 }
449
666
  dft = is_arr ? Hash.new(type_default[type]) : type_default[type]
450
667
  namelist_data << InlistItem.new(name, type, dft, namelist, -1, is_arr,
451
668
  num_indices)
@@ -459,64 +676,85 @@ class Inlist
459
676
  # Inlist.get_names_and_types and assigns defaults and orders to each item.
460
677
  # Looks for this information in the specified defaults filename.
461
678
 
462
- def self.get_defaults(temp_data, namelist, d_filename = nil, whine = false)
463
- d_filename ||= namelist + '.defaults'
464
- d_full_path = Inlist.d_paths[namelist] + d_filename
465
- raise "Couldn't find file #{d_filename}" unless File.exists?(d_full_path)
466
- contents = File.readlines(d_full_path)
467
- contents.reject! { |line| is_comment?(line) or is_blank?(line) }
679
+ def self.get_defaults(temp_data, namelist, whine = false)
680
+ defaults_file = defaults_files[namelist]
681
+ unless File.exist?(defaults_file)
682
+ raise "Couldn't find file #{defaults_file}"
683
+ end
684
+ contents = File.readlines(defaults_file)
685
+ # throw out comments and blank lines
686
+ contents.reject! { |line| is_comment?(line) || is_blank?(line) }
687
+ # remaining lines should only be assignments. Only use the part of the line
688
+ # up to the comment character, then strip all whitespace
468
689
  contents.map! do |line|
469
690
  my_line = line.dup
470
- if has_comment?(line)
471
- my_line = my_line[0...my_line.index('!')]
691
+ my_line = my_line[0...my_line.index('!')] if has_comment?(line)
692
+ unless my_line =~ /=/
693
+ raise "Equal sign missing in line:\n\t #{my_line}\n in file " \
694
+ "#{full_path}."
472
695
  end
473
- raise "Equal sign missing in line:\n\t #{my_line}\n in file " +
474
- "#{full_path}." unless my_line =~ /=/
475
696
  my_line.strip!
476
697
  end
477
- pairs = contents.map {|line| line.split('=').map {|datum| datum.strip}}
478
- n_d_hash = {}
479
- n_o_hash = {}
698
+ # divide lines into two element arrays: name and value
699
+ pairs = contents.map { |line| line.split('=').map(&:strip) }
700
+ n_d_hash = {} # maps names to default values
701
+ n_o_hash = {} # maps names to default order in inlist
480
702
  pairs.each_with_index do |pair, i|
481
703
  name = pair[0]
482
704
  default = pair[1]
705
+ # look for parentheses in name, indicating an array
483
706
  if name =~ /\(.*\)/
707
+ # make selector be the stuff in the parentheses
484
708
  selector = name[/\(.*\)/][1..-2]
709
+ # make name just be the part without parentheses
485
710
  name.sub!(/\(.*\)/, '')
711
+ # colon indicates mass assignment
486
712
  if selector.include?(':')
487
713
  default = Hash.new(default)
488
- elsif selector.count(',') == 0
489
- default = {selector.to_i => default}
714
+ # lack of a comma indicates dimension = 1
715
+ elsif selector.count(',').zero?
716
+ default = { selector.to_i => default }
717
+ # at least one comma, so dimension > 1
490
718
  else
719
+ # reformat the selector (now a key in the default hash) to an
720
+ # array of integers
491
721
  selector = selector.split(',').map { |index| index.strip.to_i }
492
- default = default = {selector => default}
722
+ default = { selector => default }
493
723
  end
494
724
  end
725
+ # if the default value is a hash, we probably don't have every possible
726
+ # value, so just merge scraped values with the automatically chosen
727
+ # defaults
495
728
  if n_d_hash[name].is_a?(Hash)
496
729
  n_d_hash[name].merge!(default)
730
+ # scalar values get a simple assignment
497
731
  else
498
732
  n_d_hash[name] = default
499
733
  end
734
+ # order is just the same as the order it appeared in its defaults file
500
735
  n_o_hash[name] ||= i
501
736
  end
502
737
  temp_data.each do |datum|
503
- unless n_d_hash.keys.include?(datum.name)
504
- puts "WARNING: no default found for control #{datum.name}. Using standard defaults." if whine
738
+ unless n_d_hash.key?(datum.name)
739
+ if whine
740
+ puts "WARNING: no default found for control #{datum.name}. Using " \
741
+ 'standard defaults.'
742
+ end
505
743
  end
506
744
  default = n_d_hash[datum.name]
507
- if default.is_a?(Hash) and datum.value.is_a?(Hash)
508
- datum.value = datum.value.merge(default)
509
- else
510
- datum.value = default || datum.value
511
- end
745
+ datum.value = if default.is_a?(Hash) && datum.value.is_a?(Hash)
746
+ datum.value.merge(default)
747
+ else
748
+ default || datum.value
749
+ end
512
750
  datum.order = n_o_hash[datum.name] || datum.order
513
751
  end
514
752
  temp_data
515
753
  end
516
754
 
517
- def self.full_line(lines, i)
518
- return lines[i] unless lines[i][-1] == '&'
519
- [lines[i].sub('&', ''), full_line(lines, i+1)].join(' ')
755
+ def self.full_line(lines, indx)
756
+ return lines[indx] unless lines[indx][-1] == '&'
757
+ [lines[indx].sub('&', ''), full_line(lines, indx + 1)].join(' ')
520
758
  end
521
759
 
522
760
  def self.is_comment?(line)
@@ -538,14 +776,15 @@ class Inlist
538
776
 
539
777
  attr_accessor :data_hash
540
778
  attr_reader :names
541
- def initialize
542
- Inlist.get_data unless Inlist.have_data?
779
+ def initialize(use_star_as_fallback: true)
780
+ unless Inlist.have_data?
781
+ Inlist.get_data(use_star_as_fallback: use_star_as_fallback)
782
+ end
543
783
  @data = Inlist.inlist_data
544
784
  @data_hash = {}
545
785
  @data.each_value do |namelist_data|
546
786
  namelist_data.each do |datum|
547
787
  @data_hash[datum.name] = datum.dup
548
-
549
788
  end
550
789
  end
551
790
  @names = @data_hash.keys
@@ -570,7 +809,7 @@ class Inlist
570
809
  def flag_command(name)
571
810
  @data_hash[name].flagged = true
572
811
  end
573
-
812
+
574
813
  def unflag_command(name)
575
814
  @data_hash[name].flagged = false
576
815
  end
@@ -580,21 +819,22 @@ class Inlist
580
819
  if datum.is_arr
581
820
  lines = @data_hash[name].value.keys.map do |key|
582
821
  prefix = " #{datum.name}("
583
- suffix = ") = " +
584
- Inlist.parse_input(datum.name, datum.value[key], datum.type) + "\n"
585
- if key.respond_to?(:inject)
586
- indices = key[1..-1].inject(key[0].to_s) do |res, elt|
587
- "#{res}, #{elt}"
588
- end
589
- else
590
- indices = key.to_s
591
- end
822
+ suffix = ') = ' +
823
+ Inlist.parse_input(datum.name, datum.value[key], datum.type) +
824
+ "\n"
825
+ indices = if key.respond_to?(:inject)
826
+ key[1..-1].inject(key[0].to_s) do |res, elt|
827
+ "#{res}, #{elt}"
828
+ end
829
+ else
830
+ key.to_s
831
+ end
592
832
  prefix + indices + suffix
593
833
  end
594
834
  lines = lines.join
595
835
  @to_write[datum.namelist][datum.order] = lines
596
836
  else
597
- @to_write[datum.namelist][datum.order] = " " + datum.name + ' = ' +
837
+ @to_write[datum.namelist][datum.order] = ' ' + datum.name + ' = ' +
598
838
  Inlist.parse_input(datum.name, datum.value, datum.type) + "\n"
599
839
  end
600
840
  end
@@ -615,13 +855,13 @@ class Inlist
615
855
  # blank lines between disparate data
616
856
  namelists.each do |namelist|
617
857
  @to_write[namelist].each_index do |i|
618
- next if (i == 0 or i == @to_write[namelist].size - 1)
858
+ next if [0, @to_write[namelist].size - 1].include? i
619
859
  this_line = @to_write[namelist][i]
620
- prev_line = @to_write[namelist][i-1]
621
-
860
+ prev_line = @to_write[namelist][i - 1]
861
+
622
862
  this_line = '' if this_line.nil?
623
863
  prev_line = '' if prev_line.nil?
624
- if this_line.empty? and not(prev_line.empty? or prev_line == "\n")
864
+ if this_line.empty? && !(prev_line.empty? || prev_line == "\n")
625
865
  @to_write[namelist][i] = "\n"
626
866
  end
627
867
  end
@@ -634,10 +874,11 @@ class Inlist
634
874
  result = ''
635
875
  namelists.each do |namelist|
636
876
  result += "\n&#{namelist}\n"
637
- result += @to_write[namelist].join("")
877
+ result += @to_write[namelist].join('')
638
878
  result += "\n/ ! end of #{namelist} namelist\n"
639
879
  end
640
880
  result.sub("\n\n\n", "\n\n")
641
881
  end
642
-
643
882
  end
883
+
884
+ class NamelistError < Exception; end
metadata CHANGED
@@ -1,18 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mesa_script
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Wolf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-23 00:00:00.000000000 Z
11
+ date: 2018-05-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: MesaScript - a DSL for making dynamic inlists for the MESA stellar evolution
14
14
  code.
15
- email: wmwolf@physics.ucsb.edu
15
+ email: wmwolf@asu.edu
16
16
  executables:
17
17
  - inlist2mesascript
18
18
  extensions: []
@@ -25,13 +25,7 @@ homepage: https://wmwolf.github.io
25
25
  licenses:
26
26
  - MIT
27
27
  metadata: {}
28
- post_install_message: |2
29
- Thanks for installing MesaScript!
30
-
31
- To learn more about how to use MesaScript and to keep up to date with its
32
- features, check out the README at the project homepage:
33
-
34
- https://github.com/wmwolf/MesaScript
28
+ post_install_message:
35
29
  rdoc_options: []
36
30
  require_paths:
37
31
  - lib
@@ -47,12 +41,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
47
41
  version: '0'
48
42
  requirements: []
49
43
  rubyforge_project:
50
- rubygems_version: 2.4.3
44
+ rubygems_version: 2.6.14
51
45
  signing_key:
52
46
  specification_version: 4
53
47
  summary: MesaScript is a domain specific language (DSL) that allows the user to write
54
48
  inlists for MESA that include variables, loops, conditionals, etc. For more detailed
55
- instructions, see the readme on the github page at https://github.com/wmwolf/MesaScript This
49
+ instructions, see the readme on the github page at https://github.com/wmwolf/MesaScript This
56
50
  software requires a relatively modern installation of MESA (version > 5596). It
57
51
  has been tested on Ruby versions > 1.9, but there is no guarantee it will work on
58
52
  older (or newer!) versions. Any bugs or requests should be sent to the author, Bill