bcl 0.5.8 → 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bcl.rb +2 -1
  3. data/lib/bcl/base_xml.rb +12 -4
  4. data/lib/bcl/component.rb +6 -6
  5. data/lib/bcl/component_from_spreadsheet.rb +13 -10
  6. data/lib/bcl/component_methods.rb +113 -417
  7. data/lib/bcl/component_spreadsheet.rb +3 -6
  8. data/lib/bcl/core_ext.rb +17 -16
  9. data/lib/bcl/master_taxonomy.rb +12 -14
  10. data/lib/bcl/tar_ball.rb +1 -1
  11. data/lib/bcl/version.rb +2 -2
  12. data/lib/files/Components.xls +0 -0
  13. data/lib/files/downloads/40237ee0-5ea7-0130-ad9b-14109fdf0b37.tar.gz +0 -0
  14. data/lib/files/staged/components/Asphalt_Shingles_1_8_in/Asphalt_Shingles_1_8_in.tar.gz +0 -0
  15. data/lib/files/staged/components/Asphalt_Shingles_1_8_in/component.xml +81 -0
  16. data/lib/files/staged/components/Built_up_Roofing_3_8_in/Built_up_Roofing_3_8_in.tar.gz +0 -0
  17. data/lib/files/staged/components/Built_up_Roofing_3_8_in/component.xml +76 -0
  18. data/lib/files/staged/components/Metal_Surface_1_16_in/Metal_Surface_1_16_in.tar.gz +0 -0
  19. data/lib/files/staged/components/Metal_Surface_1_16_in/component.xml +76 -0
  20. data/lib/files/staged/components/Shingle_Backer_1_4_in/Shingle_Backer_1_4_in.tar.gz +0 -0
  21. data/lib/files/staged/components/Shingle_Backer_1_4_in/component.xml +81 -0
  22. data/lib/files/staged/components/Shingle_Backer_3_8_in/Shingle_Backer_3_8_in.tar.gz +0 -0
  23. data/lib/files/staged/components/Shingle_Backer_3_8_in/component.xml +81 -0
  24. data/lib/files/staged/components/Wood_Shingles_1_4_in/Wood_Shingles_1_4_in.tar.gz +0 -0
  25. data/lib/files/staged/components/Wood_Shingles_1_4_in/component.xml +81 -0
  26. data/lib/files/staged/gather/1/Asphalt_Shingles_1_8_in.tar.gz +0 -0
  27. data/lib/files/staged/gather/1/Built_up_Roofing_3_8_in.tar.gz +0 -0
  28. data/lib/files/staged/gather/1/Metal_Surface_1_16_in.tar.gz +0 -0
  29. data/lib/files/staged/gather/1/Shingle_Backer_1_4_in.tar.gz +0 -0
  30. data/lib/files/staged/gather/1/Shingle_Backer_3_8_in.tar.gz +0 -0
  31. data/lib/files/staged/gather/1/Wood_Shingles_1_4_in.tar.gz +0 -0
  32. data/lib/files/staged/gather/components_1.tar.gz +0 -0
  33. metadata +32 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 317f08f02e79fd41f98a711a6cff460d55125fd0
4
- data.tar.gz: 3c29a18af6c83f742537e00a96f422f5e5dc8500
3
+ metadata.gz: e73294b8462def2d2ff8831ec1b3e334efcf47e4
4
+ data.tar.gz: 535c60ceb7b0095ec0fe6952d3b4ef0788c9b5d9
5
5
  SHA512:
6
- metadata.gz: 9023fdac7c3cfbb86e8e38ac776eb2483b87123936c90785a62f98c00870dde73bafa3e0f1cd56f1cd415b357032921befaa9b81fd6b104689722f50ae53ca12
7
- data.tar.gz: 98e2043d5d609d8a29cfc080c76d2269d836221e3a94c33e41102f85d57031882815abe747ceae0b73a341ee47f582df2c393ad0ca5e03f20cd5b2822ce4dbcf
6
+ metadata.gz: 8ebe642bcbf101803db171a0af1916a8bb9b4b883924bc07fd30c25c16614f34ef75733d333607eb1a511a1a70fcf5a8ec8ebae63c0893184f84d0059a54f1fd
7
+ data.tar.gz: 7f61168eb018bd9db461ecbd8f96fd0b17d86fe600deaa908e88a1b6675101fb21901322bed63e81710d469bd9df3d51e668a7f2e2f7552385eb5958e0049828
data/lib/bcl.rb CHANGED
@@ -15,6 +15,7 @@ require 'multi_json'
15
15
  require 'builder'
16
16
  require 'uuid'
17
17
  require 'net/https'
18
+ require 'rexml/document'
18
19
 
19
20
  # TODO: can we condense these into one?
20
21
  require 'archive/tar/minitar'
@@ -22,7 +23,7 @@ require 'zlib'
22
23
  require 'zip'
23
24
 
24
25
  # ability to write spreadsheets
25
- require 'rubyXL'
26
+ require 'spreadsheet'
26
27
 
27
28
  require 'bcl/core_ext'
28
29
  require 'bcl/base_xml'
@@ -1,5 +1,5 @@
1
1
  ######################################################################
2
- # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
2
+ # Copyright (c) 2008-2019, Alliance for Sustainable Energy.
3
3
  # All rights reserved.
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
@@ -17,6 +17,10 @@
17
17
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
  ######################################################################
19
19
 
20
+ # KAF 2/13/2018
21
+ # This functionality is being kept in case we need in to recreate weather files in the future
22
+ # It is very out of date and would need a major reworking wrt to the updated schema
23
+
20
24
  module BCL
21
25
  ProvStruct = Struct.new(:author, :datetime, :comment)
22
26
  TagsStruct = Struct.new(:descriptor)
@@ -109,7 +113,7 @@ module BCL
109
113
  # return the title case of the string
110
114
  def tc(input)
111
115
  val = input.gsub(/\b\w/) { $&.upcase }
112
- if val.downcase == 'energyplus'
116
+ if val.casecmp('energyplus').zero?
113
117
  val = 'EnergyPlus'
114
118
  end
115
119
 
@@ -133,9 +137,13 @@ module BCL
133
137
  # simple method to test if the input_value is a string, float, or integer.
134
138
  # First convert the value back to a string for testing (in case it was passed as a float/integer)
135
139
  test = input_value.to_s
136
- input_value = test.match('\.').nil? ? Integer(test) : Float(test) rescue test.to_s
140
+ input_value = begin
141
+ test.match('\.').nil? ? Integer(test) : Float(test)
142
+ rescue StandardError
143
+ test.to_s
144
+ end
137
145
 
138
- if input_value.is_a?(Fixnum) || input_value.is_a?(Bignum)
146
+ if input_value.is_a?(Integer) || input_value.is_a?(Integer)
139
147
  dt = 'int'
140
148
  elsif input_value.is_a?(Float)
141
149
  dt = 'float'
@@ -1,5 +1,5 @@
1
1
  ######################################################################
2
- # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
2
+ # Copyright (c) 2008-2019, Alliance for Sustainable Energy.
3
3
  # All rights reserved.
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
@@ -98,11 +98,11 @@ module BCL
98
98
  # take all the files and tar.gz them -- name the file the same as
99
99
  # the directory
100
100
 
101
- Dir.chdir("#{resolve_path}")
102
- destination = "#{@name.gsub(/\W/, '_').gsub(/___/, '_').gsub(/__/, '_').chomp('_').strip}"
101
+ Dir.chdir(resolve_path.to_s)
102
+ destination = @name.gsub(/\W/, '_').gsub(/___/, '_').gsub(/__/, '_').chomp('_').strip.to_s
103
103
  # truncate filenames for paths that are longer than 256 characters (with .tar.gz appended)
104
104
  unless (@path + destination + destination).size < 249
105
- destination = "#{@uid}"
105
+ destination = @uid.to_s
106
106
  puts 'truncating filename...using uid instead of name'
107
107
  end
108
108
  destination += '.tar.gz'
@@ -126,7 +126,7 @@ module BCL
126
126
  end
127
127
 
128
128
  def add_cost(cost_name, cost_type, category, value, units, interval, interval_units, year, location, currency,
129
- source, reference_component_name, reference_component_id)
129
+ source, reference_component_name, reference_component_id)
130
130
  cs = CostStruct.new
131
131
  cs.cost_name = cost_name
132
132
  cs.cost_type = cost_type
@@ -284,7 +284,7 @@ module BCL
284
284
  # setup the xml file
285
285
  comp_xml.instruct! :xml, version: '1.0', encoding: 'UTF-8'
286
286
  comp_xml.component('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
287
- 'xsi:noNamespaceSchemaLocation' => "#{@schema_url}") do
287
+ 'xsi:noNamespaceSchemaLocation' => @schema_url.to_s) do
288
288
  comp_xml.name @name
289
289
  comp_xml.uid @uuid
290
290
  comp_xml.version_id @vuid
@@ -1,5 +1,5 @@
1
1
  ######################################################################
2
- # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
2
+ # Copyright (c) 2008-2019, Alliance for Sustainable Energy.
3
3
  # All rights reserved.
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
@@ -19,7 +19,7 @@
19
19
 
20
20
  # Converts a custom Excel spreadsheet format to BCL components for upload
21
21
 
22
- require 'rubyXL'
22
+ require 'spreadsheet'
23
23
  require 'bcl'
24
24
 
25
25
  module BCL
@@ -33,12 +33,13 @@ module BCL
33
33
  public
34
34
 
35
35
  # initialize with Excel spreadsheet to read
36
+ # seems to only be working with xls spreadsheets
36
37
  def initialize(xlsx_path, worksheet_names = ['all'])
37
38
  @xlsx_path = Pathname.new(xlsx_path).realpath.to_s
38
39
  @worksheets = []
39
40
 
40
41
  begin
41
- xlsx = RubyXL::Parser.parse(@xlsx_path)
42
+ xlsx = Spreadsheet.open(@xlsx_path)
42
43
 
43
44
  # by default, operate on all worksheets
44
45
  if worksheet_names == ['all']
@@ -47,7 +48,7 @@ module BCL
47
48
  end
48
49
  else # if specific worksheets are specified, operate on them
49
50
  worksheet_names.each do |worksheet_name|
50
- parse_xlsx_worksheet(xlsx[worksheet_name])
51
+ parse_xlsx_worksheet(xlsx.worksheet(worksheet_name))
51
52
  end
52
53
  end
53
54
 
@@ -82,6 +83,7 @@ module BCL
82
83
  if /description/i.match(header.name)
83
84
  name = values.delete_at(0) # name, uid already processed
84
85
  uid = values.delete_at(0)
86
+ component_xml.comp_version_id = values.delete_at(0)
85
87
  description = values.delete_at(0)
86
88
  component_xml.modeler_description = values.delete_at(0)
87
89
  component_xml.description = description
@@ -126,6 +128,7 @@ module BCL
126
128
  filepath = values.delete_at(0)
127
129
  # not all components(rows) have all files; skip if filename "" or nil
128
130
  next if filename == '' || filename.nil?
131
+
129
132
  # skip the file if it doesn't exist at the specified location
130
133
  unless File.exist?(filepath)
131
134
  puts "[ComponentFromSpreadsheet] ERROR #{filepath} -> File does not exist, will not be included in component xml"
@@ -133,7 +136,7 @@ module BCL
133
136
  end
134
137
  component_xml.add_file(software_program, version, filepath, filename, filetype)
135
138
  else
136
- fail "Unknown section #{header.name}"
139
+ raise "Unknown section #{header.name}"
137
140
  end
138
141
  end
139
142
 
@@ -148,15 +151,15 @@ module BCL
148
151
 
149
152
  def parse_xlsx_worksheet(xlsx_worksheet)
150
153
  worksheet = WorksheetStruct.new
151
- worksheet.name = xlsx_worksheet[0][0].value # get A1, order is: row, col
154
+ worksheet.name = xlsx_worksheet.row(0)[0] # get A1, order is: row, col
152
155
  worksheet.components = []
153
156
  puts "[ComponentFromSpreadsheet] Starting parsing components of type #{worksheet.name}"
154
157
 
155
158
  # find number of rows, first column should be name, should not be empty
156
-
157
- xlsx_data = xlsx_worksheet.extract_data
158
- # puts "Data: #{xlsx_data.inspect}"
159
- # puts "***********************"
159
+ xlsx_data = []
160
+ xlsx_worksheet.each do |ws|
161
+ xlsx_data << ws
162
+ end
160
163
 
161
164
  num_rows = xlsx_data.size
162
165
  puts "Number of Rows: #{xlsx_data.size}"
@@ -1,5 +1,5 @@
1
1
  ######################################################################
2
- # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
2
+ # Copyright (c) 2008-2019, Alliance for Sustainable Energy.
3
3
  # All rights reserved.
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
@@ -38,7 +38,7 @@ module BCL
38
38
  load_config
39
39
  end
40
40
 
41
- def login(username = nil, password = nil, url = nil, group_id = nil)
41
+ def login(username = nil, password = nil, url = nil, group_id = nil)
42
42
  # figure out what url to use
43
43
  if url.nil?
44
44
  url = @config[:server][:url]
@@ -53,7 +53,7 @@ module BCL
53
53
  url = url.gsub('http://', '')
54
54
  url = url.gsub('https://', '')
55
55
 
56
- if username.nil? || passwrord.nil?
56
+ if username.nil? || password.nil?
57
57
  # log in via cached credentials
58
58
  username = @config[:server][:user][:username]
59
59
  password = @config[:server][:user][:password]
@@ -145,390 +145,6 @@ module BCL
145
145
  end
146
146
  end
147
147
 
148
- # retrieve, parse, and save metadata for BCL measures
149
- def measure_metadata(search_term = nil, filter_term = nil, return_all_pages = false)
150
- # setup results directory
151
- unless File.exist?(@parsed_measures_path)
152
- FileUtils.mkdir_p(@parsed_measures_path)
153
- end
154
- puts "...storing parsed metadata in #{@parsed_measures_path}"
155
-
156
- # retrieve measures
157
- puts "retrieving measures that match search_term: #{search_term.nil? ? 'nil' : search_term} and filters: #{filter_term.nil? ? 'nil' : filter_term}"
158
- measures = []
159
- retrieve_measures(search_term, filter_term, return_all_pages) do |m|
160
- begin
161
- r = parse_measure_metadata(m)
162
- measures << r if r
163
- rescue => e
164
- puts "[ERROR] Parsing measure #{e.message}:#{e.backtrace.join("\n")}"
165
- end
166
- end
167
-
168
- measures
169
- end
170
-
171
- # Read in an existing measure.rb file and extract the arguments.
172
- # TODO: deprecate the _measure_name. This is used in the openstudio analysis gem, so it has to be carefully removed or renamed
173
- def parse_measure_file(_measure_name, measure_filename)
174
- measure_hash = {}
175
-
176
- if File.exist? measure_filename
177
- # read in the measure file and extract some information
178
- measure_string = File.read(measure_filename)
179
-
180
- measure_hash[:classname] = measure_string.match(/class (.*) </)[1]
181
- measure_hash[:name] = measure_hash[:classname].to_underscore
182
- measure_hash[:display_name] = nil
183
- measure_hash[:display_name_titleized] = measure_hash[:name].titleize
184
- measure_hash[:display_name_from_measure] = nil
185
-
186
- if measure_string =~ /OpenStudio::Ruleset::WorkspaceUserScript/
187
- measure_hash[:measure_type] = 'EnergyPlusMeasure'
188
- elsif measure_string =~ /OpenStudio::Ruleset::ModelUserScript/
189
- measure_hash[:measure_type] = 'RubyMeasure'
190
- elsif measure_string =~ /OpenStudio::Ruleset::ReportingUserScript/
191
- measure_hash[:measure_type] = 'ReportingMeasure'
192
- elsif measure_string =~ /OpenStudio::Ruleset::UtilityUserScript/
193
- measure_hash[:measure_type] = 'UtilityUserScript'
194
- else
195
- fail "measure type is unknown with an inherited class in #{measure_filename}: #{measure_hash.inspect}"
196
- end
197
-
198
- # New versions of measures have name, description, and modeler description methods
199
- n = measure_string.scan(/def name(.*?)end/m).first
200
- if n
201
- n = n.first.strip
202
- n.gsub!('return', '')
203
- n.gsub!(/"|'/, '')
204
- n.strip!
205
- measure_hash[:display_name_from_measure] = n
206
- end
207
-
208
- # New versions of measures have name, description, and modeler description methods
209
- n = measure_string.scan(/def description(.*?)end/m).first
210
- if n
211
- n = n.first.strip
212
- n.gsub!('return', '')
213
- n.gsub!(/"|'/, '')
214
- n.strip!
215
- measure_hash[:description] = n
216
- end
217
-
218
- # New versions of measures have name, description, and modeler description methods
219
- n = measure_string.scan(/def modeler_description(.*?)end/m).first
220
- if n
221
- n = n.first.strip
222
- n.gsub!('return', '')
223
- n.gsub!(/"|'/, '')
224
- n.strip!
225
- measure_hash[:modeler_description] = n
226
- end
227
-
228
- measure_hash[:arguments] = []
229
-
230
- args = measure_string.scan(/(.*).*=.*OpenStudio::Ruleset::OSArgument.*make(.*)Argument\((.*).*\)/)
231
- args.each do |arg|
232
- new_arg = {}
233
- new_arg[:name] = nil
234
- new_arg[:display_name] = nil
235
- new_arg[:variable_type] = nil
236
- new_arg[:local_variable] = nil
237
- new_arg[:units] = nil
238
- new_arg[:units_in_name] = nil
239
-
240
- new_arg[:local_variable] = arg[0].strip
241
- new_arg[:variable_type] = arg[1]
242
- arg_params = arg[2].split(',')
243
- new_arg[:name] = arg_params[0].gsub(/"|'/, '')
244
- next if new_arg[:name] == 'info_widget'
245
- choice_vector = arg_params[1] ? arg_params[1].strip : nil
246
-
247
- # try find the display name of the argument
248
- reg = /#{new_arg[:local_variable]}.setDisplayName\((.*)\)/
249
- if measure_string =~ reg
250
- new_arg[:display_name] = measure_string.match(reg)[1]
251
- new_arg[:display_name].gsub!(/"|'/, '') if new_arg[:display_name]
252
- else
253
- new_arg[:display_name] = new_arg[:name]
254
- end
255
-
256
- p = parse_measure_name(new_arg[:display_name])
257
- new_arg[:display_name] = p[0]
258
- new_arg[:units_in_name] = p[1]
259
-
260
- # try to get the units
261
- reg = /#{new_arg[:local_variable]}.setUnits\((.*)\)/
262
- if measure_string =~ reg
263
- new_arg[:units] = measure_string.match(reg)[1]
264
- new_arg[:units].gsub!(/"|'/, '') if new_arg[:units]
265
- end
266
-
267
- if measure_string =~ /#{new_arg[:local_variable]}.setDefaultValue/
268
- new_arg[:default_value] = measure_string.match(/#{new_arg[:local_variable]}.setDefaultValue\((.*)\)/)[1]
269
- else
270
- puts "[WARNING] #{measure_hash[:name]}:#{new_arg[:name]} has no default value... will continue"
271
- end
272
-
273
- case new_arg[:variable_type]
274
- when 'Choice'
275
- # Choices to appear to only be strings?
276
- # puts "Choice vector appears to be #{choice_vector}"
277
- new_arg[:default_value].gsub!(/"|'/, '') if new_arg[:default_value]
278
-
279
- # parse the choices from the measure
280
- # scan from where the "instance has been created to the measure"
281
- possible_choices = nil
282
- possible_choice_block = measure_string # .scan(/#{choice_vector}.*=(.*)#{new_arg[:local_variable]}.*=/mi)
283
- if possible_choice_block
284
- # puts "possible_choice_block is #{possible_choice_block}"
285
- possible_choices = possible_choice_block.scan(/#{choice_vector}.*<<.*(')(.*)(')/)
286
- possible_choices += possible_choice_block.scan(/#{choice_vector}.*<<.*(")(.*)(")/)
287
- end
288
-
289
- # puts "Possible choices are #{possible_choices}"
290
-
291
- if possible_choices.nil? || possible_choices.empty?
292
- new_arg[:choices] = []
293
- else
294
- new_arg[:choices] = possible_choices.map { |c| c[1] }
295
- end
296
-
297
- # if the choices are inherited from the model, then need to just display the default value which
298
- # somehow magically works because that is the display name
299
- if new_arg[:default_value]
300
- new_arg[:choices] << new_arg[:default_value] unless new_arg[:choices].include?(new_arg[:default_value])
301
- end
302
- when 'String', 'Path'
303
- new_arg[:default_value].gsub!(/"|'/, '') if new_arg[:default_value]
304
- when 'Bool'
305
- if new_arg[:default_value]
306
- new_arg[:default_value] = new_arg[:default_value].downcase == 'true' ? true : false
307
- end
308
- when 'Integer'
309
- new_arg[:default_value] = new_arg[:default_value].to_i if new_arg[:default_value]
310
- when 'Double'
311
- new_arg[:default_value] = new_arg[:default_value].to_f if new_arg[:default_value]
312
- else
313
- fail "unknown variable type of #{new_arg[:variable_type]}"
314
- end
315
-
316
- measure_hash[:arguments] << new_arg
317
- end
318
- end
319
-
320
- # check if there is a measure.xml file?
321
- measure_xml_filename = "#{File.join(File.dirname(measure_filename), File.basename(measure_filename, '.*'))}.xml"
322
- if File.exist? measure_xml_filename
323
- f = File.open measure_xml_filename
324
- doc = Nokogiri::XML(f)
325
-
326
- # pull out some information
327
- measure_hash[:name_xml] = doc.xpath('/measure/name').first.content
328
- measure_hash[:uid] = doc.xpath('/measure/uid').first.content
329
- measure_hash[:version_id] = doc.xpath('/measure/version_id').first.content
330
- measure_hash[:tags] = doc.xpath('/measure/tags/tag').map(&:content)
331
-
332
- measure_hash[:modeler_description_xml] = doc.xpath('/measure/modeler_description').first.content
333
-
334
- measure_hash[:description_xml] = doc.xpath('/measure/description').first.content
335
-
336
- f.close
337
- end
338
-
339
- # validate the measure information
340
-
341
- validate_measure_hash(measure_hash)
342
-
343
- measure_hash
344
- end
345
-
346
- # Validate the measure hash to make sure that it is meets the style guide. This will also perform the selection
347
- # of which data to use for the "actual metadata"
348
- #
349
- # @param h [Hash] Measure hash
350
- def validate_measure_hash(h)
351
- if h.key? :name_xml
352
- if h[:name_xml] != h[:name]
353
- puts "[ERROR] {Validation}. Snake-cased name and the name in the XML do not match. Will default to automatic snake-cased measure name. #{h[:name_xml]} <> #{h[:name]}"
354
- end
355
- end
356
-
357
- puts '[WARNING] {Validation} Could not find measure description in measure.' unless h[:description]
358
- puts '[WARNING] {Validation} Could not find modeler description in measure.' unless h[:modeler_description]
359
- puts '[WARNING] {Validation} Could not find measure name method in measure.' unless h[:name_from_measure]
360
-
361
- # check the naming conventions
362
- if h[:display_name_from_measure]
363
- if h[:display_name_from_measure] != h[:display_name_titleized]
364
- puts '[WARNING] {Validation} Display name from measure and automated naming do not match. Will default to the automated name until all measures use the name method because of potential conflicts due to bad copy/pasting.'
365
- end
366
- h[:display_name] = h.delete :display_name_titleized
367
- else
368
- h[:display_name] = h.delete :display_name_titleized
369
- end
370
- h.delete :display_name_from_measure
371
-
372
- if h.key?(:description) && h.key?(:description_xml)
373
- if h[:description] != h[:description_xml]
374
- puts '[ERROR] {Validation} Measure description and XML description differ. Will default to description in measure'
375
- end
376
- h.delete(:description_xml)
377
- end
378
-
379
- if h.key?(:modeler_description) && h.key?(:modeler_description_xml)
380
- if h[:modeler_description] != h[:modeler_description_xml]
381
- puts '[ERROR] {Validation} Measure modeler description and XML modeler description differ. Will default to modeler description in measure'
382
- end
383
- h.delete(:modeler_description_xml)
384
- end
385
-
386
- h[:arguments].each do |arg|
387
- if arg[:units_in_name]
388
- puts "[ERROR] {Validation} It appears that units are embedded in the argument name for #{arg[:name]}."
389
-
390
- if arg[:units]
391
- if arg[:units] != arg[:units_in_name]
392
- puts '[ERROR] {Validation} Units in argument name do not match units in setUnits method. Using setUnits.'
393
- arg.delete :units_in_name
394
- end
395
- else
396
- puts '[ERROR] {Validation} Units appear to be in measure name. Please use setUnits.'
397
- arg[:units] = arg.delete :units_in_name
398
- end
399
- else
400
- # make sure to delete if null
401
- arg.delete :units_in_name
402
- end
403
- end
404
- end
405
-
406
- def translate_measure_hash_to_csv(measure_hash)
407
- csv = []
408
- csv << [false, measure_hash[:display_name], measure_hash[:classname], measure_hash[:classname], measure_hash[:measure_type]]
409
-
410
- measure_hash[:arguments].each do |argument|
411
- values = []
412
- values << ''
413
- values << 'argument'
414
- values << ''
415
- values << argument[:display_name]
416
- values << argument[:name]
417
- values << argument[:display_name] # this is the default short display name
418
- values << argument[:variable_type]
419
- values << argument[:units]
420
-
421
- # watch out because :default_value can be a boolean
422
- argument[:default_value].nil? ? values << '' : values << argument[:default_value]
423
- choices = ''
424
- if argument[:choices]
425
- choices << "|#{argument[:choices].join(',')}|" unless argument[:choices].empty?
426
- end
427
- values << choices
428
-
429
- csv << values
430
- end
431
-
432
- csv
433
- end
434
-
435
- # Read the measure's information to pull out the metadata and to move into a more friendly directory name.
436
- # argument of measure is a hash
437
- def parse_measure_metadata(measure)
438
- m_result = nil
439
- # check for valid measure
440
- if measure[:measure][:name] && measure[:measure][:uuid]
441
-
442
- file_data = download_component(measure[:measure][:uuid])
443
-
444
- if file_data
445
- save_file = File.expand_path("#{@parsed_measures_path}/#{measure[:measure][:name].downcase.gsub(' ', '_')}.zip")
446
- File.open(save_file, 'wb') { |f| f << file_data }
447
-
448
- # unzip file and delete zip.
449
- # TODO: check that something was downloaded here before extracting zip
450
- if File.exist? save_file
451
- BCL.extract_zip(save_file, @parsed_measures_path, true)
452
-
453
- # catch a weird case where there is an extra space in an unzip file structure but not in the measure.name
454
- if measure[:measure][:name] == 'Add Daylight Sensor at Center of Spaces with a Specified Space Type Assigned'
455
- unless File.exist? "#{@parsed_measures_path}/#{measure[:measure][:name]}"
456
- temp_dir_name = "#{@parsed_measures_path}/Add Daylight Sensor at Center of Spaces with a Specified Space Type Assigned"
457
- FileUtils.move(temp_dir_name, "#{@parsed_measures_path}/#{measure[:measure][:name]}")
458
- end
459
- end
460
-
461
- temp_dir_name = File.join(@parsed_measures_path, measure[:measure][:name])
462
-
463
- # Read the measure.rb file
464
- # puts "save dir name #{temp_dir_name}"
465
- measure_filename = "#{temp_dir_name}/measure.rb"
466
- measure_hash = parse_measure_file(nil, measure_filename)
467
-
468
- if measure_hash.empty?
469
- puts 'Measure Hash was empty... moving on'
470
- else
471
- # puts measure_hash.inspect
472
- m_result = measure_hash
473
- # move the directory to the class name
474
- new_dir_name = File.join(@parsed_measures_path, measure_hash[:classname])
475
- # puts "Moving #{temp_dir_name} to #{new_dir_name}"
476
- if temp_dir_name == new_dir_name
477
- puts 'Destination directory is the same as the processed directory'
478
- else
479
- FileUtils.rm_rf(new_dir_name) if File.exist?(new_dir_name) && temp_dir_name != measure_hash[:classname]
480
- FileUtils.move(temp_dir_name, new_dir_name) unless temp_dir_name == measure_hash[:classname]
481
- end
482
- # create a new measure.json file for parsing later if need be
483
- File.open(File.join(new_dir_name, 'measure.json'), 'w') { |f| f << MultiJson.dump(measure_hash, pretty: true) }
484
- end
485
- else
486
- puts "Problems downloading #{measure[:measure][:name]}... moving on"
487
- end
488
- end
489
- end
490
-
491
- m_result
492
- end
493
-
494
- # parse measure name
495
- def parse_measure_name(name)
496
- # TODO: save/display errors
497
- errors = ''
498
- m = nil
499
-
500
- clean_name = name
501
- units = nil
502
-
503
- # remove everything btw parentheses
504
- m = clean_name.match(/\((.+?)\)/)
505
- unless m.nil?
506
- errors += ' removing parentheses,'
507
- units = m[1]
508
- clean_name = clean_name.gsub(/\((.+?)\)/, '')
509
- end
510
-
511
- # remove everything btw brackets
512
- m = nil
513
- m = clean_name.match(/\[(.+?)\]/)
514
- unless m.nil?
515
- errors += ' removing brackets,'
516
- clean_name = clean_name.gsub(/\[(.+?)\]/, '')
517
- end
518
-
519
- # remove characters
520
- m = nil
521
- m = clean_name.match(/(\?|\.|\#).+?/)
522
- unless m.nil?
523
- errors += ' removing any of following: ?.#'
524
- clean_name = clean_name.gsub(/(\?|\.|\#).+?/, '')
525
- end
526
- clean_name = clean_name.gsub('.', '')
527
- clean_name = clean_name.gsub('?', '')
528
-
529
- [clean_name.strip, units]
530
- end
531
-
532
148
  # retrieve measures for parsing metadata.
533
149
  # specify a search term to narrow down search or leave nil to retrieve all
534
150
  # set all_pages to true to iterate over all pages of results
@@ -560,7 +176,7 @@ module BCL
560
176
  result = { error: 'could not get json from http post response' }
561
177
  case api_response.code
562
178
  when '200'
563
- puts " Response Code: #{api_response.code} - #{api_response.body}"
179
+ puts " Response Code: #{api_response.code}"
564
180
  if api_response.body.empty?
565
181
  puts ' 200 BUT ERROR: Returned body was empty. Possible causes:'
566
182
  puts ' - BSD tar on Mac OSX vs gnutar'
@@ -572,23 +188,31 @@ module BCL
572
188
  valid = true
573
189
  end
574
190
  when '404'
575
- puts " Response: #{api_response.code} - #{api_response.body}"
576
- puts ' 404 - check these common causes first:'
577
- puts ' - the filename contains periods (other than the ones before the file extension)'
578
- puts " - you are not an 'administrator member' of the group you're trying to upload to"
191
+ puts " Error Code: #{api_response.code} - #{api_response.body}"
192
+ puts ' - check these common causes first:'
193
+ puts " - you are trying to update content that doesn't exist"
194
+ puts " - you are not an 'administrator member' of the group you're trying to upload to"
579
195
  result = MultiJson.load api_response.body
580
196
  valid = false
581
197
  when '406'
582
- puts " Response: #{api_response.code} - #{api_response.body}"
583
- puts ' 406 - check these common causes first:'
584
- puts ' - the UUID of the item that you are uploading is already on the BCL'
585
- puts ' - the group_id is not correct in the config.yml (go to group on site, and copy the number at the end of the URL)'
586
- puts " - you are not an 'administrator member' of the group you're trying to upload to"
587
- result = MultiJson.load api_response.body
198
+ puts " Error Code: #{api_response.code}"
199
+ # try to parse the response a bit
200
+ error = MultiJson.load api_response.body
201
+ puts "temp error: #{error}"
202
+ if error.key?('form_errors')
203
+ if error['form_errors'].key?('field_tar_file')
204
+ result = { error: error['form_errors']['field_tar_file'] }
205
+ elsif error['form_errors'].key?('og_group_ref][und][0][default')
206
+ result = { error: error['form_errors']['og_group_ref][und][0][default'] }
207
+ end
208
+ else
209
+ result = error
210
+ end
588
211
  valid = false
589
212
  when '500'
590
- puts " Response: #{api_response.code} - #{api_response.body}"
591
- fail 'server exception'
213
+ puts " Error Code: #{api_response.code}"
214
+ result = { error: api_response.message }
215
+ # fail 'server exception'
592
216
  valid = false
593
217
  else
594
218
  puts " Response: #{api_response.code} - #{api_response.body}"
@@ -635,8 +259,8 @@ module BCL
635
259
  # pushes component to the bcl and publishes them (if logged-in as BCL Website Admin user).
636
260
  # username, password, and group_id are set in the ~/.bcl/config.yml file
637
261
  def push_content(filename_and_path, write_receipt_file, content_type)
638
- fail 'Please login before pushing components' if @session.nil?
639
- fail 'Do not have a valid access token; try again' if @access_token.nil?
262
+ raise 'Please login before pushing components' if @session.nil?
263
+ raise 'Do not have a valid access token; try again' if @access_token.nil?
640
264
 
641
265
  data = construct_post_data(filename_and_path, false, content_type)
642
266
 
@@ -663,7 +287,7 @@ module BCL
663
287
  # pushes updated content to the bcl and publishes it (if logged-in as BCL Website Admin user).
664
288
  # username and password set in ~/.bcl/config.yml file
665
289
  def update_content(filename_and_path, write_receipt_file, uuid = nil)
666
- fail 'Please login before pushing components' unless @session
290
+ raise 'Please login before pushing components' unless @session
667
291
 
668
292
  # get the UUID if zip or xml file
669
293
  version_id = nil
@@ -676,10 +300,10 @@ module BCL
676
300
  else
677
301
  # verify the uuid via regex
678
302
  unless uuid =~ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
679
- fail "uuid of #{uuid} is invalid"
303
+ raise "uuid of #{uuid} is invalid"
680
304
  end
681
305
  end
682
- fail 'Please pass in a tar.gz file or pass in the uuid' unless uuid
306
+ raise 'Please pass in a tar.gz file or pass in the uuid' unless uuid
683
307
 
684
308
  data = construct_post_data(filename_and_path, true, uuid)
685
309
 
@@ -728,26 +352,29 @@ module BCL
728
352
  uuid = nil
729
353
  vid = nil
730
354
 
731
- fail "File does not exist #{path_to_tarball}" unless File.exist? path_to_tarball
355
+ raise "File does not exist #{path_to_tarball}" unless File.exist? path_to_tarball
356
+
732
357
  tgz = Zlib::GzipReader.open(path_to_tarball)
733
358
  Archive::Tar::Minitar::Reader.open(tgz).each do |entry|
734
359
  # If taring with tar zcf ameasure.tar.gz -C measure_dir .
735
360
  if entry.name =~ /^.{0,2}component.xml$/ || entry.name =~ /^.{0,2}measure.xml$/
736
- xml_file = Nokogiri::XML(entry.read)
361
+ # xml_to_parse = File.new( entry.read )
362
+ xml_file = REXML::Document.new entry.read
737
363
 
738
364
  # pull out some information
739
365
  if entry.name =~ /component/
740
- u = xml_file.xpath('/component/uid').first
741
- v = xml_file.xpath('/component/version_id').first
366
+ u = xml_file.elements['component/uid']
367
+ v = xml_file.elements['component/version_id']
742
368
  else
743
- u = xml_file.xpath('/measure/uid').first
744
- v = xml_file.xpath('/measure/version_id').first
369
+ u = xml_file.elements['measure/uid']
370
+ v = xml_file.elements['measure/version_id']
745
371
  end
746
- fail "Could not find UUID in XML file #{path_to_tarball}" unless u
372
+ raise "Could not find UUID in XML file #{path_to_tarball}" unless u
373
+
747
374
  # Don't error on version not existing.
748
375
 
749
- uuid = u.content
750
- vid = v ? v.content : nil
376
+ uuid = u.text
377
+ vid = v ? v.text : nil
751
378
 
752
379
  # puts "uuid = #{uuid}; vid = #{vid}"
753
380
  end
@@ -756,6 +383,29 @@ module BCL
756
383
  [uuid, vid]
757
384
  end
758
385
 
386
+ def uuid_vid_from_xml(path_to_xml)
387
+ uuid = nil
388
+ vid = nil
389
+
390
+ raise "File does not exist #{path_to_xml}" unless File.exist? path_to_xml
391
+
392
+ xml_to_parse = File.new(path_to_xml)
393
+ xml_file = REXML::Document.new xml_to_parse
394
+
395
+ if path_to_xml.to_s.split('/').last =~ /component.xml/
396
+ u = xml_file.elements['component/uid']
397
+ v = xml_file.elements['component/version_id']
398
+ else
399
+ u = xml_file.elements['measure/uid']
400
+ v = xml_file.elements['measure/version_id']
401
+ end
402
+ raise "Could not find UUID in XML file #{path_to_tarball}" unless u
403
+
404
+ uuid = u.text
405
+ vid = v ? v.text : nil
406
+ [uuid, vid]
407
+ end
408
+
759
409
  def update_contents(array_of_tarball_components, skip_files_with_receipts)
760
410
  logs = []
761
411
  array_of_tarball_components.each do |comp|
@@ -781,6 +431,50 @@ module BCL
781
431
  logs
782
432
  end
783
433
 
434
+ def search_by_uuid(uuid, vid = nil)
435
+ full_url = '/api/search/*.json'
436
+ action = nil
437
+
438
+ # add api_version
439
+ if @api_version < 2.0
440
+ puts "WARNING: attempting to use search with api_version #{@api_version}. Use API v2.0 for this functionality."
441
+ end
442
+ full_url += "?api_version=#{@api_version}"
443
+
444
+ # uuid
445
+ full_url += "&fq[]=ss_uuid:#{uuid}"
446
+ # puts "search url: #{full_url}"
447
+
448
+ res = @http.get(full_url)
449
+ res = MultiJson.load(res.body)
450
+
451
+ if res['result'].count > 0
452
+ # found content, check version
453
+ content = res['result'].first
454
+ # puts "first result: #{content}"
455
+
456
+ # parse out measure vs component
457
+ if content['measure']
458
+ content = content['measure']
459
+ else
460
+ content = content['component']
461
+ end
462
+
463
+ # TODO: check version_modified date if it exists?
464
+ if !vid.nil? && content['vuuid'] == vid
465
+ # no update needed
466
+ action = 'noop'
467
+ else
468
+ # vid doesn't match: update existing
469
+ action = 'update'
470
+ end
471
+ else
472
+ # no uuid found: push new
473
+ action = 'push'
474
+ end
475
+ action
476
+ end
477
+
784
478
  # Simple method to search bcl and return the result as hash with symbols
785
479
  # If all = true, iterate over pages of results and return all
786
480
  # JSON ONLY
@@ -883,12 +577,12 @@ module BCL
883
577
  # look at response code
884
578
  if result.code == '200'
885
579
  # puts 'Download Successful'
886
- result.body ? result.body : nil
580
+ result.body || nil
887
581
  else
888
582
  puts "Download fail. Error code #{result.code}"
889
583
  nil
890
584
  end
891
- rescue
585
+ rescue StandardError
892
586
  puts "Couldn't download uid(s): #{uid}...skipping"
893
587
  nil
894
588
  end
@@ -905,8 +599,10 @@ module BCL
905
599
  # location of template file
906
600
  FileUtils.mkdir_p(File.dirname(config_filename))
907
601
  File.open(config_filename, 'w') { |f| f << default_yaml.to_yaml }
908
- File.chmod(0600, config_filename)
602
+ File.chmod(0o600, config_filename)
909
603
  puts "******** Please fill in user credentials in #{config_filename} file if you need to upload data **********"
604
+ # fill in the @config data with the temporary data for now.
605
+ @config = YAML.load_file(config_filename)
910
606
  end
911
607
  end
912
608