bcl 0.5.8 → 0.5.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bcl.rb +2 -1
- data/lib/bcl/base_xml.rb +12 -4
- data/lib/bcl/component.rb +6 -6
- data/lib/bcl/component_from_spreadsheet.rb +13 -10
- data/lib/bcl/component_methods.rb +113 -417
- data/lib/bcl/component_spreadsheet.rb +3 -6
- data/lib/bcl/core_ext.rb +17 -16
- data/lib/bcl/master_taxonomy.rb +12 -14
- data/lib/bcl/tar_ball.rb +1 -1
- data/lib/bcl/version.rb +2 -2
- data/lib/files/Components.xls +0 -0
- data/lib/files/downloads/40237ee0-5ea7-0130-ad9b-14109fdf0b37.tar.gz +0 -0
- data/lib/files/staged/components/Asphalt_Shingles_1_8_in/Asphalt_Shingles_1_8_in.tar.gz +0 -0
- data/lib/files/staged/components/Asphalt_Shingles_1_8_in/component.xml +81 -0
- data/lib/files/staged/components/Built_up_Roofing_3_8_in/Built_up_Roofing_3_8_in.tar.gz +0 -0
- data/lib/files/staged/components/Built_up_Roofing_3_8_in/component.xml +76 -0
- data/lib/files/staged/components/Metal_Surface_1_16_in/Metal_Surface_1_16_in.tar.gz +0 -0
- data/lib/files/staged/components/Metal_Surface_1_16_in/component.xml +76 -0
- data/lib/files/staged/components/Shingle_Backer_1_4_in/Shingle_Backer_1_4_in.tar.gz +0 -0
- data/lib/files/staged/components/Shingle_Backer_1_4_in/component.xml +81 -0
- data/lib/files/staged/components/Shingle_Backer_3_8_in/Shingle_Backer_3_8_in.tar.gz +0 -0
- data/lib/files/staged/components/Shingle_Backer_3_8_in/component.xml +81 -0
- data/lib/files/staged/components/Wood_Shingles_1_4_in/Wood_Shingles_1_4_in.tar.gz +0 -0
- data/lib/files/staged/components/Wood_Shingles_1_4_in/component.xml +81 -0
- data/lib/files/staged/gather/1/Asphalt_Shingles_1_8_in.tar.gz +0 -0
- data/lib/files/staged/gather/1/Built_up_Roofing_3_8_in.tar.gz +0 -0
- data/lib/files/staged/gather/1/Metal_Surface_1_16_in.tar.gz +0 -0
- data/lib/files/staged/gather/1/Shingle_Backer_1_4_in.tar.gz +0 -0
- data/lib/files/staged/gather/1/Shingle_Backer_3_8_in.tar.gz +0 -0
- data/lib/files/staged/gather/1/Wood_Shingles_1_4_in.tar.gz +0 -0
- data/lib/files/staged/gather/components_1.tar.gz +0 -0
- metadata +32 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e73294b8462def2d2ff8831ec1b3e334efcf47e4
|
4
|
+
data.tar.gz: 535c60ceb7b0095ec0fe6952d3b4ef0788c9b5d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 '
|
26
|
+
require 'spreadsheet'
|
26
27
|
|
27
28
|
require 'bcl/core_ext'
|
28
29
|
require 'bcl/base_xml'
|
data/lib/bcl/base_xml.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
######################################################################
|
2
|
-
# Copyright (c) 2008-
|
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.
|
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 =
|
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?(
|
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-
|
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(
|
102
|
-
destination =
|
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 =
|
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
|
-
|
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' =>
|
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-
|
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 '
|
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 =
|
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
|
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
|
-
|
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
|
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
|
-
|
158
|
-
|
159
|
-
|
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-
|
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
|
-
|
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? ||
|
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}
|
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 "
|
576
|
-
puts '
|
577
|
-
puts
|
578
|
-
puts "
|
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 "
|
583
|
-
|
584
|
-
|
585
|
-
puts
|
586
|
-
|
587
|
-
|
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 "
|
591
|
-
|
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
|
-
|
639
|
-
|
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
|
-
|
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
|
-
|
303
|
+
raise "uuid of #{uuid} is invalid"
|
680
304
|
end
|
681
305
|
end
|
682
|
-
|
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
|
-
|
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
|
-
|
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.
|
741
|
-
v = xml_file.
|
366
|
+
u = xml_file.elements['component/uid']
|
367
|
+
v = xml_file.elements['component/version_id']
|
742
368
|
else
|
743
|
-
u = xml_file.
|
744
|
-
v = xml_file.
|
369
|
+
u = xml_file.elements['measure/uid']
|
370
|
+
v = xml_file.elements['measure/version_id']
|
745
371
|
end
|
746
|
-
|
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.
|
750
|
-
vid = v ? v.
|
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
|
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(
|
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
|
|