bcl 0.5.7.pre → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/bcl.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'bcl/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'bcl'
8
+ spec.version = BCL::VERSION
9
+ spec.platform = Gem::Platform::RUBY
10
+ spec.authors = ['Daniel Macumber', 'Nicholas Long', 'Andrew Parker', 'Katherine Fleming']
11
+ spec.email = 'Nicholas.Long@nrel.gov'
12
+
13
+ spec.homepage = 'http://bcl.nrel.gov'
14
+ spec.summary = 'Classes for creating component XML files and manageing measures for the BCL'
15
+ spec.description = 'This gem contains helper methods for generating the Component XML file needed to upload files to the Building Component Library. It also contains the classes needed for logging in via the api and uploading generating components and measures.'
16
+ spec.license = 'LGPL'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.required_ruby_version = '~> 2.7.0'
26
+
27
+ spec.add_dependency 'builder', '3.2.4'
28
+ spec.add_dependency 'faraday', '~> 1.0.1'
29
+ spec.add_dependency 'minitar', '~> 0.9'
30
+ # Measure tester is not used in this project, but this will force dependencies to match versions
31
+ # requested by OpenStudio.
32
+ spec.add_dependency 'openstudio_measure_tester', '~> 0.3.0'
33
+ spec.add_dependency 'rexml', '3.2.4'
34
+ spec.add_dependency 'rubyzip', '~> 2.3.0'
35
+ spec.add_dependency 'spreadsheet', '1.2.6'
36
+ spec.add_dependency 'uuid', '~> 2.3.9'
37
+ spec.add_dependency 'yamler', '0.1.0'
38
+ spec.add_dependency 'zliby', '0.0.5'
39
+ end
data/lib/bcl.rb CHANGED
@@ -1,3 +1,22 @@
1
+ ######################################################################
2
+ # Copyright (c) 2008-2021, Alliance for Sustainable Energy.
3
+ # All rights reserved.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ ######################################################################
19
+
1
20
  require 'pathname'
2
21
  require 'base64'
3
22
 
@@ -11,27 +30,26 @@ end
11
30
 
12
31
  # file formatters
13
32
  require 'yaml'
14
- require 'multi_json'
33
+ require 'json'
15
34
  require 'builder'
16
35
  require 'uuid'
17
36
  require 'net/https'
37
+ require 'rexml/document'
18
38
 
19
39
  # TODO: can we condense these into one?
20
- require 'archive/tar/minitar'
40
+ require 'minitar'
21
41
  require 'zlib'
22
42
  require 'zip'
23
43
 
24
44
  # ability to write spreadsheets
25
- require 'rubyXL'
45
+ require 'spreadsheet'
26
46
 
27
47
  require 'bcl/core_ext'
28
48
  require 'bcl/base_xml'
29
- require 'bcl/component_spreadsheet'
30
49
  require 'bcl/component_from_spreadsheet'
31
50
  require 'bcl/component'
32
51
  require 'bcl/component_methods'
33
52
  require 'bcl/tar_ball'
34
- require 'bcl/master_taxonomy'
35
53
  require 'bcl/version'
36
54
 
37
55
  # Some global structures
data/lib/bcl/base_xml.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  ######################################################################
2
- # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
2
+ # Copyright (c) 2008-2021, 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'
data/lib/bcl/component.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  ######################################################################
2
- # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
2
+ # Copyright (c) 2008-2021, 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
@@ -384,4 +384,4 @@ module BCL
384
384
  xmlfile.close
385
385
  end
386
386
  end
387
- end # module BCL
387
+ end
@@ -1,5 +1,5 @@
1
1
  ######################################################################
2
- # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
2
+ # Copyright (c) 2008-2021, 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
@@ -30,15 +30,14 @@ module BCL
30
30
  class ComponentFromSpreadsheet
31
31
  @@changed = false
32
32
 
33
- public
34
-
35
33
  # initialize with Excel spreadsheet to read
34
+ # seems to only be working with xls spreadsheets
36
35
  def initialize(xlsx_path, worksheet_names = ['all'])
37
36
  @xlsx_path = Pathname.new(xlsx_path).realpath.to_s
38
37
  @worksheets = []
39
38
 
40
39
  begin
41
- xlsx = RubyXL::Parser.parse(@xlsx_path)
40
+ xlsx = Spreadsheet.open(@xlsx_path)
42
41
 
43
42
  # by default, operate on all worksheets
44
43
  if worksheet_names == ['all']
@@ -47,7 +46,7 @@ module BCL
47
46
  end
48
47
  else # if specific worksheets are specified, operate on them
49
48
  worksheet_names.each do |worksheet_name|
50
- parse_xlsx_worksheet(xlsx[worksheet_name])
49
+ parse_xlsx_worksheet(xlsx.worksheet(worksheet_name))
51
50
  end
52
51
  end
53
52
 
@@ -79,13 +78,14 @@ module BCL
79
78
 
80
79
  puts " headers: #{component.headers}"
81
80
  component.headers.each do |header|
82
- if /description/i.match(header.name)
81
+ if /description/i.match?(header.name)
83
82
  name = values.delete_at(0) # name, uid already processed
84
83
  uid = values.delete_at(0)
84
+ component_xml.comp_version_id = values.delete_at(0)
85
85
  description = values.delete_at(0)
86
86
  component_xml.modeler_description = values.delete_at(0)
87
87
  component_xml.description = description
88
- elsif /provenance/i.match(header.name)
88
+ elsif /provenance/i.match?(header.name)
89
89
  author = values.delete_at(0)
90
90
  datetime = values.delete_at(0)
91
91
  if datetime.nil?
@@ -95,10 +95,10 @@ module BCL
95
95
 
96
96
  comment = values.delete_at(0)
97
97
  component_xml.add_provenance(author.to_s, datetime.strftime('%Y-%m-%d'), comment.to_s)
98
- elsif /tag/i.match(header.name)
98
+ elsif /tag/i.match?(header.name)
99
99
  value = values.delete_at(0)
100
100
  component_xml.add_tag(value)
101
- elsif /attribute/i.match(header.name)
101
+ elsif /attribute/i.match?(header.name)
102
102
  value = values.delete_at(0)
103
103
  name = header.children[0]
104
104
  units = ''
@@ -107,7 +107,7 @@ module BCL
107
107
  units = match_data[2].strip
108
108
  end
109
109
  component_xml.add_attribute(name, value, units)
110
- elsif /source/i.match(header.name)
110
+ elsif /source/i.match?(header.name)
111
111
  manufacturer = values.delete_at(0)
112
112
  model = values.delete_at(0)
113
113
  serial_no = values.delete_at(0)
@@ -118,7 +118,7 @@ module BCL
118
118
  component_xml.source_serial_no = serial_no
119
119
  component_xml.source_year = year
120
120
  component_xml.source_url = url
121
- elsif /file/i.match(header.name)
121
+ elsif /file/i.match?(header.name)
122
122
  software_program = values.delete_at(0)
123
123
  version = values.delete_at(0)
124
124
  filename = values.delete_at(0)
@@ -126,6 +126,7 @@ module BCL
126
126
  filepath = values.delete_at(0)
127
127
  # not all components(rows) have all files; skip if filename "" or nil
128
128
  next if filename == '' || filename.nil?
129
+
129
130
  # skip the file if it doesn't exist at the specified location
130
131
  unless File.exist?(filepath)
131
132
  puts "[ComponentFromSpreadsheet] ERROR #{filepath} -> File does not exist, will not be included in component xml"
@@ -133,7 +134,7 @@ module BCL
133
134
  end
134
135
  component_xml.add_file(software_program, version, filepath, filename, filetype)
135
136
  else
136
- fail "Unknown section #{header.name}"
137
+ raise "Unknown section #{header.name}"
137
138
  end
138
139
  end
139
140
 
@@ -148,15 +149,15 @@ module BCL
148
149
 
149
150
  def parse_xlsx_worksheet(xlsx_worksheet)
150
151
  worksheet = WorksheetStruct.new
151
- worksheet.name = xlsx_worksheet[0][0].value # get A1, order is: row, col
152
+ worksheet.name = xlsx_worksheet.row(0)[0] # get A1, order is: row, col
152
153
  worksheet.components = []
153
154
  puts "[ComponentFromSpreadsheet] Starting parsing components of type #{worksheet.name}"
154
155
 
155
156
  # 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 "***********************"
157
+ xlsx_data = []
158
+ xlsx_worksheet.each do |ws|
159
+ xlsx_data << ws
160
+ end
160
161
 
161
162
  num_rows = xlsx_data.size
162
163
  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-2021, 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, secret = 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,10 +53,10 @@ module BCL
53
53
  url = url.gsub('http://', '')
54
54
  url = url.gsub('https://', '')
55
55
 
56
- if username.nil? || secret.nil?
56
+ if username.nil? || password.nil?
57
57
  # log in via cached credentials
58
58
  username = @config[:server][:user][:username]
59
- secret = @config[:server][:user][:secret]
59
+ password = @config[:server][:user][:password]
60
60
  @group_id = group_id || @config[:server][:user][:group]
61
61
  puts "logging in using credentials in .bcl/config.yml: Connecting to #{url} on port #{port} as #{username} with group #{@group_id}"
62
62
  else
@@ -74,9 +74,9 @@ module BCL
74
74
  @http.use_ssl = true
75
75
  end
76
76
 
77
- data = %({"username":"#{username}","secret":"#{secret}"})
77
+ data = %({"username":"#{username}","password":"#{password}"})
78
78
 
79
- login_path = '/api/user/loginsso.json'
79
+ login_path = '/api/user/login.json'
80
80
  headers = { 'Content-Type' => 'application/json' }
81
81
 
82
82
  res = @http.post(login_path, data, headers)
@@ -93,13 +93,13 @@ module BCL
93
93
  bni = ''
94
94
  junkout = res['set-cookie'].split(';')
95
95
  junkout.each do |line|
96
- if line =~ /BNES_SESS/
96
+ if line.match?(/BNES_SESS/)
97
97
  bnes = line.match(/(BNES_SESS.*)/)[0]
98
98
  end
99
99
  end
100
100
 
101
101
  junkout.each do |line|
102
- if line =~ /BNI/
102
+ if line.match?(/BNI/)
103
103
  bni = line.match(/(BNI.*)/)[0]
104
104
  end
105
105
  end
@@ -107,7 +107,7 @@ module BCL
107
107
  # puts "DATA: #{data}"
108
108
  session_name = ''
109
109
  sessid = ''
110
- json = MultiJson.load(res.body)
110
+ json = JSON.parse(res.body)
111
111
  json.each do |key, val|
112
112
  if key == 'session_name'
113
113
  session_name = val
@@ -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'
@@ -568,27 +184,35 @@ module BCL
568
184
  valid = false
569
185
  else
570
186
  puts ' 200 - Successful Upload'
571
- result = MultiJson.load api_response.body
187
+ result = JSON.parse api_response.body
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"
579
- result = MultiJson.load api_response.body
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"
195
+ result = JSON.parse 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 = JSON.parse 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}"
@@ -633,17 +257,17 @@ module BCL
633
257
  end
634
258
 
635
259
  # pushes component to the bcl and publishes them (if logged-in as BCL Website Admin user).
636
- # username, secret, and group_id are set in the ~/.bcl/config.yml file
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
 
643
267
  path = '/api/content.json'
644
268
  headers = { 'Content-Type' => 'application/json', 'X-CSRF-Token' => @access_token, 'Cookie' => @session }
645
269
 
646
- res = @http.post(path, MultiJson.dump(data), headers)
270
+ res = @http.post(path, JSON.dump(data), headers)
647
271
 
648
272
  valid, json = evaluate_api_response(res)
649
273
 
@@ -661,32 +285,32 @@ module BCL
661
285
  end
662
286
 
663
287
  # pushes updated content to the bcl and publishes it (if logged-in as BCL Website Admin user).
664
- # username and secret set in ~/.bcl/config.yml file
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
670
294
  if uuid.nil?
671
295
  puts File.extname(filename_and_path).downcase
672
- if filename_and_path =~ /^.*.tar.gz$/i
296
+ if filename_and_path.match?(/^.*.tar.gz$/i)
673
297
  uuid, version_id = uuid_vid_from_tarball(filename_and_path)
674
298
  puts "Parsed uuid out of tar.gz file with value #{uuid}"
675
299
  end
676
300
  else
677
301
  # verify the uuid via regex
678
- 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"
302
+ unless uuid.match?(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/)
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
 
686
310
  path = '/api/content.json'
687
311
  headers = { 'Content-Type' => 'application/json', 'X-CSRF-Token' => @access_token, 'Cookie' => @session }
688
312
 
689
- res = @http.post(path, MultiJson.dump(data), headers)
313
+ res = @http.post(path, JSON.dump(data), headers)
690
314
 
691
315
  valid, json = evaluate_api_response(res)
692
316
 
@@ -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
- if entry.name =~ /component/
740
- u = xml_file.xpath('/component/uid').first
741
- v = xml_file.xpath('/component/version_id').first
365
+ if entry.name.match?(/component/)
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.match?(/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,49 @@ 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
+
447
+ res = @http.get(full_url)
448
+ res = JSON.parse res.body
449
+
450
+ if res['result'].count > 0
451
+ # found content, check version
452
+ content = res['result'].first
453
+ # puts "first result: #{content}"
454
+
455
+ # parse out measure vs component
456
+ if content['measure']
457
+ content = content['measure']
458
+ else
459
+ content = content['component']
460
+ end
461
+
462
+ # TODO: check version_modified date if it exists?
463
+ if !vid.nil? && content['vuuid'] == vid
464
+ # no update needed
465
+ action = 'noop'
466
+ else
467
+ # vid doesn't match: update existing
468
+ action = 'update'
469
+ end
470
+ else
471
+ # no uuid found: push new
472
+ action = 'push'
473
+ end
474
+ action
475
+ end
476
+
784
477
  # Simple method to search bcl and return the result as hash with symbols
785
478
  # If all = true, iterate over pages of results and return all
786
479
  # JSON ONLY
@@ -820,7 +513,7 @@ module BCL
820
513
  puts "search url: #{full_url}"
821
514
  res = @http.get(full_url)
822
515
  # return unparsed
823
- MultiJson.load(res.body, symbolize_keys: true)
516
+ JSON.parse res.body, symbolize_names: true
824
517
  else
825
518
  # iterate over result pages
826
519
  # modify filter_str for show_rows=200 for maximum returns
@@ -841,11 +534,11 @@ module BCL
841
534
  puts "search url: #{full_url_all}"
842
535
  response = @http.get(full_url_all)
843
536
  # parse here so you can build results array
844
- res = MultiJson.load(response.body)
537
+ res = JSON.parse response.body, symbolize_names: true
845
538
 
846
- if res['result'].count > 0
539
+ if res[:result].count > 0
847
540
  pagecnt += 1
848
- res['result'].each do |r|
541
+ res[:result].each do |r|
849
542
  results << r
850
543
  end
851
544
  else
@@ -853,8 +546,7 @@ module BCL
853
546
  end
854
547
  end
855
548
  # return unparsed b/c that is what is expected
856
- formatted_results = { 'result' => results }
857
- results_to_return = MultiJson.load(MultiJson.dump(formatted_results), symbolize_keys: true)
549
+ return { result: results }
858
550
  end
859
551
  end
860
552
 
@@ -883,12 +575,12 @@ module BCL
883
575
  # look at response code
884
576
  if result.code == '200'
885
577
  # puts 'Download Successful'
886
- result.body ? result.body : nil
578
+ result.body || nil
887
579
  else
888
580
  puts "Download fail. Error code #{result.code}"
889
581
  nil
890
582
  end
891
- rescue
583
+ rescue StandardError
892
584
  puts "Couldn't download uid(s): #{uid}...skipping"
893
585
  nil
894
586
  end
@@ -905,8 +597,10 @@ module BCL
905
597
  # location of template file
906
598
  FileUtils.mkdir_p(File.dirname(config_filename))
907
599
  File.open(config_filename, 'w') { |f| f << default_yaml.to_yaml }
908
- File.chmod(0600, config_filename)
600
+ File.chmod(0o600, config_filename)
909
601
  puts "******** Please fill in user credentials in #{config_filename} file if you need to upload data **********"
602
+ # fill in the @config data with the temporary data for now.
603
+ @config = YAML.load_file(config_filename)
910
604
  end
911
605
  end
912
606
 
@@ -916,7 +610,7 @@ module BCL
916
610
  url: 'https://bcl.nrel.gov',
917
611
  user: {
918
612
  username: 'ENTER_BCL_USERNAME',
919
- secret: 'ENTER_BCL_SECRET',
613
+ password: 'ENTER_BCL_PASSWORD',
920
614
  group: 'ENTER_GROUP_ID'
921
615
  }
922
616
  }
@@ -924,7 +618,7 @@ module BCL
924
618
 
925
619
  settings
926
620
  end
927
- end # class ComponentMethods
621
+ end
928
622
 
929
623
  # TODO: make this extend the component_xml class (or create a super class around components)
930
624
 
@@ -986,4 +680,4 @@ module BCL
986
680
 
987
681
  Dir.chdir(current_dir)
988
682
  end
989
- end # module BCL
683
+ end