mesa_script 0.1.4 → 0.1.6

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