bcl 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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