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.
- checksums.yaml +5 -5
- data/.gitignore +21 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +75 -0
- data/Gemfile +11 -0
- data/License.txt +65 -0
- data/README.md +40 -0
- data/Rakefile +312 -0
- data/bcl.gemspec +39 -0
- data/lib/bcl.rb +23 -5
- data/lib/bcl/base_xml.rb +12 -4
- data/lib/bcl/component.rb +7 -7
- data/lib/bcl/component_from_spreadsheet.rb +19 -18
- data/lib/bcl/component_methods.rb +135 -441
- data/lib/bcl/core_ext.rb +17 -16
- data/lib/bcl/tar_ball.rb +1 -1
- data/lib/bcl/version.rb +2 -2
- data/lib/files/Components.xls +0 -0
- data/schemas/v2/component_2012_11_08.xsd +291 -0
- data/schemas/v2/measure_2013_3_26.xsd +153 -0
- data/schemas/v3/common_bcl_v3.xsd +422 -0
- data/schemas/v3/component_v3.xsd +445 -0
- data/schemas/v3/measure_v3.xsd +496 -0
- metadata +73 -76
- data/lib/bcl/component_spreadsheet.rb +0 -290
- data/lib/bcl/master_taxonomy.rb +0 -530
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 '
|
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 '
|
40
|
+
require 'minitar'
|
21
41
|
require 'zlib'
|
22
42
|
require 'zip'
|
23
43
|
|
24
44
|
# ability to write spreadsheets
|
25
|
-
require '
|
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-
|
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.
|
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-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(
|
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
|
@@ -384,4 +384,4 @@ module BCL
|
|
384
384
|
xmlfile.close
|
385
385
|
end
|
386
386
|
end
|
387
|
-
end
|
387
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
######################################################################
|
2
|
-
# Copyright (c) 2008-
|
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 '
|
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 =
|
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
|
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
|
-
|
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
|
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
|
-
|
158
|
-
|
159
|
-
|
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-
|
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
|
-
|
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? ||
|
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]
|
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}","
|
77
|
+
data = %({"username":"#{username}","password":"#{password}"})
|
78
78
|
|
79
|
-
login_path = '/api/user/
|
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
|
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
|
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 =
|
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}
|
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 =
|
187
|
+
result = JSON.parse api_response.body
|
572
188
|
valid = true
|
573
189
|
end
|
574
190
|
when '404'
|
575
|
-
puts "
|
576
|
-
puts '
|
577
|
-
puts
|
578
|
-
puts "
|
579
|
-
result =
|
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 "
|
583
|
-
|
584
|
-
|
585
|
-
puts
|
586
|
-
|
587
|
-
|
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 "
|
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}"
|
@@ -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,
|
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
|
|
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,
|
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
|
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
|
670
294
|
if uuid.nil?
|
671
295
|
puts File.extname(filename_and_path).downcase
|
672
|
-
if filename_and_path
|
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
|
679
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
-
if entry.name
|
740
|
-
u = xml_file.
|
741
|
-
v = xml_file.
|
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.
|
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.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
|
-
|
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 =
|
537
|
+
res = JSON.parse response.body, symbolize_names: true
|
845
538
|
|
846
|
-
if res[
|
539
|
+
if res[:result].count > 0
|
847
540
|
pagecnt += 1
|
848
|
-
res[
|
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
|
-
|
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
|
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(
|
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
|
-
|
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
|
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
|
683
|
+
end
|