bcl 0.7.1 → 0.9.0

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.
@@ -1,36 +1,6 @@
1
1
  # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) 2008-2021, Alliance for Sustainable Energy, LLC.
3
- # All rights reserved.
4
- # Redistribution and use in source and binary forms, with or without
5
- # modification, are permitted provided that the following conditions are met:
6
- #
7
- # (1) Redistributions of source code must retain the above copyright notice,
8
- # this list of conditions and the following disclaimer.
9
- #
10
- # (2) Redistributions in binary form must reproduce the above copyright notice,
11
- # this list of conditions and the following disclaimer in the documentation
12
- # and/or other materials provided with the distribution.
13
- #
14
- # (3) Neither the name of the copyright holder nor the names of any contributors
15
- # may be used to endorse or promote products derived from this software without
16
- # specific prior written permission from the respective party.
17
- #
18
- # (4) Other than as required in clauses (1) and (2), distributions in any form
19
- # of modifications or other derivative works may not use the "OpenStudio"
20
- # trademark, "OS", "os", or any other confusingly similar designation without
21
- # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
- #
23
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
- # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
- # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
- # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
- # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
- # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
- # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
- # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2
+ # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
3
+ # See also https://openstudio.net/license
34
4
  # *******************************************************************************
35
5
 
36
6
  module BCL
@@ -44,121 +14,32 @@ module BCL
44
14
  def initialize
45
15
  @parsed_measures_path = './measures/parsed'
46
16
  @config = nil
47
- @session = nil
48
- @access_token = nil
49
17
  @http = nil
50
- @api_version = 2.0
51
- @group_id = nil
52
- @logged_in = false
18
+ @api_version = nil
53
19
 
20
+ # load configs from file or default
54
21
  load_config
55
- end
56
22
 
57
- def login(username = nil, password = nil, url = nil, group_id = nil)
58
- # figure out what url to use
59
- if url.nil?
60
- url = @config[:server][:url]
61
- end
23
+ # configure connection
24
+ url = @config[:server][:url]
62
25
  # look for http vs. https
63
26
  if url.include? 'https'
64
27
  port = 443
65
28
  else
66
29
  port = 80
67
30
  end
31
+
68
32
  # strip out http(s)
69
33
  url = url.gsub('http://', '')
70
34
  url = url.gsub('https://', '')
71
35
 
72
- if username.nil? || password.nil?
73
- # log in via cached credentials
74
- username = @config[:server][:user][:username]
75
- password = @config[:server][:user][:password]
76
- @group_id = group_id || @config[:server][:user][:group]
77
- puts "logging in using credentials in .bcl/config.yml: Connecting to #{url} on port #{port} as #{username} with group #{@group_id}"
78
- else
79
- @group_id = group_id || @config[:server][:user][:group]
80
- puts "logging in using credentials in function arguments: Connecting to #{url} on port #{port} as #{username} with group #{@group_id}"
81
- end
82
-
83
- if @group_id.nil?
84
- puts '[WARNING] You did not set a group ID in your config.yml file or pass in a group ID. You can retrieve your group ID from the node number of your group page (e.g., https://bcl.nrel.gov/node/32). Will continue, but you will not be able to upload content.'
85
- end
86
-
87
36
  @http = Net::HTTP.new(url, port)
88
37
  @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
89
38
  if port == 443
90
39
  @http.use_ssl = true
91
40
  end
92
41
 
93
- data = %({"username":"#{username}","password":"#{password}"})
94
-
95
- login_path = '/api/user/login.json'
96
- headers = { 'Content-Type' => 'application/json' }
97
-
98
- res = @http.post(login_path, data, headers)
99
-
100
- # for debugging:
101
- # res.each do |key, value|
102
- # puts "#{key}: #{value}"
103
- # end
104
-
105
- if res.code == '200'
106
- puts 'Login Successful'
107
-
108
- bnes = ''
109
- bni = ''
110
- junkout = res['set-cookie'].split(';')
111
- junkout.each do |line|
112
- if line.match?(/BNES_SESS/)
113
- bnes = line.match(/(BNES_SESS.*)/)[0]
114
- end
115
- end
116
-
117
- junkout.each do |line|
118
- if line.match?(/BNI/)
119
- bni = line.match(/(BNI.*)/)[0]
120
- end
121
- end
122
-
123
- # puts "DATA: #{data}"
124
- session_name = ''
125
- sessid = ''
126
- json = JSON.parse(res.body)
127
- json.each do |key, val|
128
- if key == 'session_name'
129
- session_name = val
130
- elsif key == 'sessid'
131
- sessid = val
132
- end
133
- end
134
-
135
- @session = session_name + '=' + sessid + ';' + bni + ';' + bnes
136
-
137
- # get access token
138
- token_path = '/services/session/token'
139
- token_headers = { 'Content-Type' => 'application/json', 'Cookie' => @session }
140
- # puts "token_headers = #{token_headers.inspect}"
141
- access_token = @http.post(token_path, '', token_headers)
142
- if access_token.code == '200'
143
- @access_token = access_token.body
144
- else
145
- puts 'Unable to get access token; uploads will not work'
146
- puts "error code: #{access_token.code}"
147
- puts "error info: #{access_token.body}"
148
- end
149
-
150
- # puts "access_token = *#{@access_token}*"
151
- # puts "cookie = #{@session}"
152
-
153
- res
154
- else
155
-
156
- puts "error code: #{res.code}"
157
- puts "error info: #{res.body}"
158
- puts 'continuing as unauthenticated sessions (you can still search and download)'
159
-
160
- res
161
- end
42
+ puts "Connecting to BCL at URL: #{@config[:server][:url]}"
162
43
  end
163
44
 
164
45
  # retrieve measures for parsing metadata.
@@ -169,10 +50,18 @@ module BCL
169
50
  # raise "Please login before performing this action" if @session.nil?
170
51
 
171
52
  # make sure filter_term includes bundle
172
- if filter_term.nil?
173
- filter_term = 'fq[]=bundle%3Anrel_measure'
174
- elsif !filter_term.include? 'bundle'
175
- filter_term += '&fq[]=bundle%3Anrel_measure'
53
+ if @api_version == 2.0
54
+ if filter_term.nil?
55
+ filter_term = 'fq[]=bundle%3Anrel_measure'
56
+ elsif !filter_term.include? 'bundle'
57
+ filter_term += '&fq[]=bundle%3Anrel_measure'
58
+ end
59
+ else
60
+ if filter_term.nil?
61
+ filter_term = 'fq=bundle%3Ameasure'
62
+ elsif !filter_term.include? 'bundle'
63
+ filter_term += '&fq=bundle%3Ameasure'
64
+ end
176
65
  end
177
66
 
178
67
  # use provided search term or nil.
@@ -186,183 +75,6 @@ module BCL
186
75
  end
187
76
  end
188
77
 
189
- # evaluate the response from the API in a consistent manner
190
- def evaluate_api_response(api_response)
191
- valid = false
192
- result = { error: 'could not get json from http post response' }
193
- case api_response.code
194
- when '200'
195
- puts " Response Code: #{api_response.code}"
196
- if api_response.body.empty?
197
- puts ' 200 BUT ERROR: Returned body was empty. Possible causes:'
198
- puts ' - BSD tar on Mac OSX vs gnutar'
199
- result = { error: 'returned 200, but returned body was empty' }
200
- valid = false
201
- else
202
- puts ' 200 - Successful Upload'
203
- result = JSON.parse api_response.body
204
- valid = true
205
- end
206
- when '404'
207
- puts " Error Code: #{api_response.code} - #{api_response.body}"
208
- puts ' - check these common causes first:'
209
- puts " - you are trying to update content that doesn't exist"
210
- puts " - you are not an 'administrator member' of the group you're trying to upload to"
211
- result = JSON.parse api_response.body
212
- valid = false
213
- when '406'
214
- puts " Error Code: #{api_response.code}"
215
- # try to parse the response a bit
216
- error = JSON.parse api_response.body
217
- puts "temp error: #{error}"
218
- if error.key?('form_errors')
219
- if error['form_errors'].key?('field_tar_file')
220
- result = { error: error['form_errors']['field_tar_file'] }
221
- elsif error['form_errors'].key?('og_group_ref][und][0][default')
222
- result = { error: error['form_errors']['og_group_ref][und][0][default'] }
223
- end
224
- else
225
- result = error
226
- end
227
- valid = false
228
- when '500'
229
- puts " Error Code: #{api_response.code}"
230
- result = { error: api_response.message }
231
- # fail 'server exception'
232
- valid = false
233
- else
234
- puts " Response: #{api_response.code} - #{api_response.body}"
235
- valid = false
236
- end
237
-
238
- [valid, result]
239
- end
240
-
241
- # Construct the post parameter for the API content.json end point.
242
- # param(@update) is a boolean that triggers whether to use content_type or uuid
243
- def construct_post_data(filepath, update, content_type_or_uuid)
244
- # TODO: remove special characters in the filename; they create firewall errors
245
- # filename = filename.gsub(/\W/,'_').gsub(/___/,'_').gsub(/__/,'_').chomp('_').strip
246
-
247
- file_b64 = Base64.encode64(File.binread(filepath))
248
-
249
- data = {}
250
- data['file'] = {
251
- 'file' => file_b64,
252
- 'filesize' => File.size(filepath).to_s,
253
- 'filename' => File.basename(filepath)
254
- }
255
-
256
- data['node'] = {}
257
-
258
- # Only include the content type if this is an update
259
- if update
260
- data['node']['uuid'] = content_type_or_uuid
261
- else
262
- data['node']['type'] = content_type_or_uuid
263
- end
264
-
265
- # TODO: remove this field_component_tags once BCL is fixed
266
- data['node']['field_component_tags'] = { 'und' => '1289' }
267
- data['node']['og_group_ref'] = { 'und' => ['target_id' => @group_id] }
268
-
269
- # NOTE THIS ONLY WORKS IF YOU ARE A BCL SITE ADMIN
270
- data['node']['publish'] = '1'
271
-
272
- data
273
- end
274
-
275
- # pushes component to the bcl and publishes them (if logged-in as BCL Website Admin user).
276
- # username, password, and group_id are set in the ~/.bcl/config.yml file
277
- def push_content(filename_and_path, write_receipt_file, content_type)
278
- raise 'Please login before pushing components' if @session.nil?
279
- raise 'Do not have a valid access token; try again' if @access_token.nil?
280
-
281
- data = construct_post_data(filename_and_path, false, content_type)
282
-
283
- path = '/api/content.json'
284
- headers = { 'Content-Type' => 'application/json', 'X-CSRF-Token' => @access_token, 'Cookie' => @session }
285
-
286
- res = @http.post(path, JSON.dump(data), headers)
287
-
288
- valid, json = evaluate_api_response(res)
289
-
290
- if valid
291
- # write out a receipt file into the same directory of the component with the same file name as
292
- # the component
293
- if write_receipt_file
294
- File.open("#{File.dirname(filename_and_path)}/#{File.basename(filename_and_path, '.tar.gz')}.receipt", 'w') do |file|
295
- file << Time.now.to_s
296
- end
297
- end
298
- end
299
-
300
- [valid, json]
301
- end
302
-
303
- # pushes updated content to the bcl and publishes it (if logged-in as BCL Website Admin user).
304
- # username and password set in ~/.bcl/config.yml file
305
- def update_content(filename_and_path, write_receipt_file, uuid = nil)
306
- raise 'Please login before pushing components' unless @session
307
-
308
- # get the UUID if zip or xml file
309
- version_id = nil
310
- if uuid.nil?
311
- puts File.extname(filename_and_path).downcase
312
- if filename_and_path.match?(/^.*.tar.gz$/i)
313
- uuid, version_id = uuid_vid_from_tarball(filename_and_path)
314
- puts "Parsed uuid out of tar.gz file with value #{uuid}"
315
- end
316
- else
317
- # verify the uuid via regex
318
- unless uuid.match?(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/)
319
- raise "uuid of #{uuid} is invalid"
320
- end
321
- end
322
- raise 'Please pass in a tar.gz file or pass in the uuid' unless uuid
323
-
324
- data = construct_post_data(filename_and_path, true, uuid)
325
-
326
- path = '/api/content.json'
327
- headers = { 'Content-Type' => 'application/json', 'X-CSRF-Token' => @access_token, 'Cookie' => @session }
328
-
329
- res = @http.post(path, JSON.dump(data), headers)
330
-
331
- valid, json = evaluate_api_response(res)
332
-
333
- if valid
334
- # write out a receipt file into the same directory of the component with the same file name as
335
- # the component
336
- if write_receipt_file
337
- File.open("#{File.dirname(filename_and_path)}/#{File.basename(filename_and_path, '.tar.gz')}.receipt", 'w') do |file|
338
- file << Time.now.to_s
339
- end
340
- end
341
- end
342
-
343
- [valid, json]
344
- end
345
-
346
- def push_contents(array_of_components, skip_files_with_receipts, content_type)
347
- logs = []
348
- array_of_components.each do |comp|
349
- receipt_file = File.dirname(comp) + '/' + File.basename(comp, '.tar.gz') + '.receipt'
350
- log_message = ''
351
- if skip_files_with_receipts && File.exist?(receipt_file)
352
- log_message = "skipping because found receipt #{comp}"
353
- puts log_message
354
- else
355
- log_message = "pushing content #{File.basename(comp, '.tar.gz')}"
356
- puts log_message
357
- valid, res = push_content(comp, true, content_type)
358
- log_message += " #{valid} #{res.inspect.chomp}"
359
- end
360
- logs << log_message
361
- end
362
-
363
- logs
364
- end
365
-
366
78
  # Unpack the tarball in memory and extract the XML file to read the UUID and Version ID
367
79
  def uuid_vid_from_tarball(path_to_tarball)
368
80
  uuid = nil
@@ -422,43 +134,18 @@ module BCL
422
134
  [uuid, vid]
423
135
  end
424
136
 
425
- def update_contents(array_of_tarball_components, skip_files_with_receipts)
426
- logs = []
427
- array_of_tarball_components.each do |comp|
428
- receipt_file = File.dirname(comp) + '/' + File.basename(comp, '.tar.gz') + '.receipt'
429
- log_message = ''
430
- if skip_files_with_receipts && File.exist?(receipt_file)
431
- log_message = "skipping update because found receipt #{File.basename(comp)}"
432
- puts log_message
433
- else
434
- uuid, vid = uuid_vid_from_tarball(comp)
435
- if uuid.nil?
436
- log_message = "ERROR: uuid not found for #{File.basename(comp)}"
437
- puts log_message
438
- else
439
- log_message = "pushing updated content #{File.basename(comp)}"
440
- puts log_message
441
- valid, res = update_content(comp, true, uuid)
442
- log_message += " #{valid} #{res.inspect.chomp}"
443
- end
444
- end
445
- logs << log_message
446
- end
447
- logs
448
- end
449
-
450
137
  def search_by_uuid(uuid, vid = nil)
451
138
  full_url = '/api/search.json'
452
- action = nil
453
139
 
454
140
  # add api_version
455
- if @api_version < 2.0
456
- puts "WARNING: attempting to use search with api_version #{@api_version}. Use API v2.0 for this functionality."
141
+ if @api_version == 2.0
142
+ # uuid
143
+ full_url += "?api_version=#{@api_version}"
144
+ full_url += "&fq[]=ss_uuid:#{uuid}"
145
+ else
146
+ # uuid
147
+ full_url += "&fq=uuid:#{uuid}"
457
148
  end
458
- full_url += "?api_version=#{@api_version}"
459
-
460
- # uuid
461
- full_url += "&fq[]=ss_uuid:#{uuid}"
462
149
 
463
150
  res = @http.get(full_url)
464
151
  res = JSON.parse res.body
@@ -474,20 +161,9 @@ module BCL
474
161
  else
475
162
  content = content['component']
476
163
  end
477
-
478
- # TODO: check version_modified date if it exists?
479
- if !vid.nil? && content['vuuid'] == vid
480
- # no update needed
481
- action = 'noop'
482
- else
483
- # vid doesn't match: update existing
484
- action = 'update'
485
- end
486
- else
487
- # no uuid found: push new
488
- action = 'push'
489
164
  end
490
- action
165
+
166
+ content
491
167
  end
492
168
 
493
169
  # Simple method to search bcl and return the result as hash with symbols
@@ -508,22 +184,30 @@ module BCL
508
184
  full_url += '*.json'
509
185
  end
510
186
 
511
- # add api_version
512
- if @api_version < 2.0
513
- puts "WARNING: attempting to use search with api_version #{@api_version}. Use API v2.0 for this functionality."
187
+ # add api_version (legacy NREL is 2.0, otherwise use new syntax and ignore version)
188
+ if @api_version.nil?
189
+ # see if we can extract it from filter_str:
190
+ tmp = filter_str.match(/api_version=\d{1,}.\d{1,}/)
191
+ if tmp
192
+ @api_version = tmp.to_s.gsub(/api_version=/, '').to_f
193
+ puts "@api_version from filter_str: #{@api_version}"
194
+ end
514
195
  end
515
- full_url += "?api_version=#{@api_version}"
196
+
197
+ if @api_version == 2.0
198
+ full_url += "?api_version=#{@api_version}"
199
+ end
200
+ puts "@api_version: #{@api_version}"
516
201
 
517
202
  # add filters
518
- unless filter_str.nil?
519
- # strip out api_version from filters, if included
520
- if filter_str.include? 'api_version='
521
- filter_str = filter_str.gsub(/api_version=\d{1,}/, '')
522
- filter_str = filter_str.gsub(/&api_version=\d{1,}/, '')
203
+ if !filter_str.nil?
204
+ # strip out api_version from filters, if included & @api_version is defined
205
+ if (filter_str.include? 'api_version=')
206
+ filter_str = filter_str.gsub(/&api_version=\d{1,}.\d{1,}/, '')
207
+ filter_str = filter_str.gsub(/api_version=\d{1,}.\d{1,}/, '')
523
208
  end
524
209
  full_url = full_url + '&' + filter_str
525
210
  end
526
-
527
211
  # simple search vs. all results
528
212
  if !all
529
213
  puts "search url: #{full_url}"
@@ -572,14 +256,16 @@ module BCL
572
256
  receipt_file = File.dirname(comp) + '/' + File.basename(comp, '.tar.gz') + '.receipt'
573
257
  if File.exist?(receipt_file)
574
258
  FileUtils.remove_file(receipt_file)
575
-
576
259
  end
577
260
  end
578
261
  end
579
262
 
580
263
  def list_all_measures
581
- json = search(nil, 'fq[]=bundle%3Anrel_measure&show_rows=100')
582
-
264
+ if @api_version == 2.0
265
+ json = search(nil, 'fq[]=bundle%3Anrel_measure&show_rows=100')
266
+ else
267
+ json = search(nil, 'fq=bundle%3Ameasure&show_rows=100')
268
+ end
583
269
  json
584
270
  end
585
271
 
@@ -607,93 +293,27 @@ module BCL
607
293
  config_filename = File.expand_path('~/.bcl/config.yml')
608
294
 
609
295
  if File.exist?(config_filename)
610
- puts "loading config settings from #{config_filename}"
296
+ puts "loading URL config from #{config_filename}"
611
297
  @config = YAML.load_file(config_filename)
612
298
  else
613
- # location of template file
614
- FileUtils.mkdir_p(File.dirname(config_filename))
615
- File.open(config_filename, 'w') { |f| f << default_yaml.to_yaml }
616
- File.chmod(0o600, config_filename)
617
- puts "******** Please fill in user credentials in #{config_filename} file if you need to upload data **********"
618
- # fill in the @config data with the temporary data for now.
619
- @config = YAML.load_file(config_filename)
299
+ # use default URL
300
+ @config = {
301
+ server: {
302
+ url: 'https://bcl.nrel.gov'
303
+ }
304
+ }
620
305
  end
621
306
  end
622
307
 
308
+ # unused
623
309
  def default_yaml
624
310
  settings = {
625
311
  server: {
626
- url: 'https://bcl.nrel.gov',
627
- user: {
628
- username: 'ENTER_BCL_USERNAME',
629
- password: 'ENTER_BCL_PASSWORD',
630
- group: 'ENTER_GROUP_ID'
631
- }
312
+ url: 'https://bcl.nrel.gov'
632
313
  }
633
314
  }
634
315
 
635
316
  settings
636
317
  end
637
318
  end
638
-
639
- # TODO: make this extend the component_xml class (or create a super class around components)
640
-
641
- def self.gather_components(component_dir, chunk_size = 0, delete_previousgather = false, destination = nil)
642
- if destination.nil?
643
- @dest_filename = 'components'
644
- else
645
- @dest_filename = destination
646
- end
647
- @dest_file_ext = 'tar.gz'
648
-
649
- # store the starting directory
650
- current_dir = Dir.pwd
651
-
652
- # an array to hold reporting info about the batches
653
- gather_components_report = []
654
-
655
- # go to the directory containing the components
656
- Dir.chdir(component_dir)
657
-
658
- # delete any old versions of the component chunks
659
- FileUtils.rm_rf('./gather') if delete_previousgather
660
-
661
- # gather all the components into array
662
- targzs = Pathname.glob('./**/*.tar.gz')
663
- tar_cnt = 0
664
- chunk_cnt = 0
665
- targzs.each do |targz|
666
- if chunk_size != 0 && (tar_cnt % chunk_size) == 0
667
- chunk_cnt += 1
668
- end
669
- tar_cnt += 1
670
-
671
- destination_path = "./gather/#{chunk_cnt}"
672
- FileUtils.mkdir_p(destination_path)
673
- destination_file = "#{destination_path}/#{File.basename(targz.to_s)}"
674
- # puts "copying #{targz.to_s} to #{destination_file}"
675
- FileUtils.cp(targz.to_s, destination_file)
676
- end
677
-
678
- # gather all the .tar.gz files into a single tar.gz
679
- (1..chunk_cnt).each do |cnt|
680
- currentdir = Dir.pwd
681
-
682
- paths = []
683
- Pathname.glob("./gather/#{cnt}/*.tar.gz").each do |pt|
684
- paths << File.basename(pt.to_s)
685
- end
686
-
687
- Dir.chdir("./gather/#{cnt}")
688
- destination = "#{@dest_filename}_#{cnt}.#{@dest_file_ext}"
689
- puts "tarring batch #{cnt} of #{chunk_cnt} to #{@dest_filename}_#{cnt}.#{@dest_file_ext}"
690
- BCL.tarball(destination, paths)
691
- Dir.chdir(currentdir)
692
-
693
- # move the tarball back a directory
694
- FileUtils.move("./gather/#{cnt}/#{destination}", "./gather/#{destination}")
695
- end
696
-
697
- Dir.chdir(current_dir)
698
- end
699
319
  end
data/lib/bcl/core_ext.rb CHANGED
@@ -1,36 +1,6 @@
1
1
  # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) 2008-2021, Alliance for Sustainable Energy, LLC.
3
- # All rights reserved.
4
- # Redistribution and use in source and binary forms, with or without
5
- # modification, are permitted provided that the following conditions are met:
6
- #
7
- # (1) Redistributions of source code must retain the above copyright notice,
8
- # this list of conditions and the following disclaimer.
9
- #
10
- # (2) Redistributions in binary form must reproduce the above copyright notice,
11
- # this list of conditions and the following disclaimer in the documentation
12
- # and/or other materials provided with the distribution.
13
- #
14
- # (3) Neither the name of the copyright holder nor the names of any contributors
15
- # may be used to endorse or promote products derived from this software without
16
- # specific prior written permission from the respective party.
17
- #
18
- # (4) Other than as required in clauses (1) and (2), distributions in any form
19
- # of modifications or other derivative works may not use the "OpenStudio"
20
- # trademark, "OS", "os", or any other confusingly similar designation without
21
- # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
- #
23
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
- # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
- # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
- # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
- # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
- # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
- # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
- # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2
+ # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
3
+ # See also https://openstudio.net/license
34
4
  # *******************************************************************************
35
5
 
36
6
  class String