bcl 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 51f36ae11ea89ebfeba378d2473fc62494bcc905
4
- data.tar.gz: 35bddda57da88a7bbb4e8f77515586abb5d11d60
3
+ metadata.gz: d84cc1c93cc41cbcd5e66681619123bf8a173a98
4
+ data.tar.gz: 99519415353a6c8dcafe3e2b558b0f0ceba001e8
5
5
  SHA512:
6
- metadata.gz: 7f6457d15d1ac094d3616842dcb751051acf2ac86161b415f7caa1643dc53656ebe911d92b777875f5372659bd37927053d1cb1622af53de6774e0f412ecae9f
7
- data.tar.gz: 1d830da0c99a4a4db01b3559833e81008839b161df6c0d394d4f14094f6f82db3787eabd64cb32fd038cd7d6604504fbae881fa0bd80759a4a2f73415950a32c
6
+ metadata.gz: 5c78265464c68b61f6f01cbb8f312884b219e1d95f4b049ad73b2b65b8a1d5b33d3acb1bd1c92e78e5dce8549bb112d9cd0be7c24964b299a1bfd73306861183
7
+ data.tar.gz: b82a8f5e0cf75223377f414ae3fec6682c8cffba04cb1904fcd655377b0a0508da278bbc98701e1e08e1e4e862e1a35076c80a07feb3305cf0641eebff34c565
@@ -1,130 +1,132 @@
1
- require 'uuid' # gem install uuid
2
-
3
- module BCL
4
- ProvStruct = Struct.new(:author, :datetime, :comment)
5
- TagsStruct = Struct.new(:descriptor)
6
- AttrStruct = Struct.new(:name, :value, :datatype, :units)
7
- FileStruct = Struct.new(:version_software_program, :version_id, :fqp_file, :filename, :filetype, :usage_type, :checksum)
8
- CostStruct = Struct.new(:cost_name, :cost_type, :category, :value, :interval,
9
- :interval_units, :year, :location, :units, :currency, :source,
10
- :reference_component_name, :reference_component_id)
11
-
12
- ObjectStruct = Struct.new(:obj_type, :obj_instance)
13
-
14
- class BaseXml
15
- attr_accessor :name
16
- attr_accessor :description
17
- attr_accessor :uuid
18
- attr_accessor :vuid
19
-
20
- attr_accessor :attributes
21
- attr_accessor :files
22
- attr_accessor :costs
23
- attr_accessor :tags
24
- attr_accessor :provenances
25
-
26
- def initialize(save_path)
27
- @name = "" #this is also a unique identifier to the component...
28
- @description = ""
29
-
30
- @provenances = []
31
- @tags = []
32
- @attributes = []
33
- @files = []
34
-
35
- @schema_url = "schema.xsd"
36
- end
37
-
38
- def generate_uuid()
39
- @uuid = UUID.new.generate
40
- end
41
-
42
- def generate_vuid()
43
- @vuid = UUID.new.generate
44
- end
45
-
46
-
47
- def add_provenance(author, datetime, comment)
48
- prov = ProvStruct.new
49
- prov.author = author
50
- prov.datetime = datetime
51
- prov.comment = comment
52
-
53
- @provenances << prov
54
- end
55
-
56
- def add_tag(tag_name)
57
- tag = TagsStruct.new
58
- tag.descriptor = tag_name
59
-
60
- @tags << tag
61
- end
62
-
63
- def add_attribute(name, value, units, datatype = nil)
64
- attr = AttrStruct.new
65
- attr.name = name
66
- attr.value = value
67
-
68
- if !datatype.nil?
69
- attr.datatype = datatype
70
- else
71
- attr.datatype = get_datatype(value)
72
- end
73
- attr.units = units
74
-
75
- @attributes << attr
76
- end
77
-
78
- def add_file(version_sp, version_id, fqp_file, filename, filetype, usage_type = nil, checksum = nil)
79
- fs = FileStruct.new
80
- fs.version_software_program = version_sp
81
- fs.version_id = version_id
82
- fs.fqp_file = fqp_file
83
- fs.filename = filename
84
- fs.filetype = filetype
85
- fs.usage_type = usage_type if usage_type != nil
86
- fs.checksum = checksum if checksum != nil
87
-
88
- @files << fs
89
- end
90
-
91
- #return the title case of the string
92
- def tc(input)
93
- val = input.gsub(/\b\w/){$&.upcase}
94
- if val.downcase == "energyplus"
95
- val = "EnergyPlus"
96
- end
97
- return val
98
- end
99
-
100
- def get_attribute(attribute_name)
101
- result = nil
102
- @attributes.each do |attr|
103
- if attr.name == attribute_name
104
- result = attr
105
- end
106
- end
107
-
108
- result
109
- end
110
-
111
- def get_datatype(input_value)
112
- dt = 'undefined'
113
-
114
- # simple method to test if the input_value is a string, float, or integer.
115
- # First convert the value back to a string for testing (in case it was passed as a float/integer)
116
- test = input_value.to_s
117
- input_value = test.match('\.').nil? ? Integer(test) : Float(test) rescue test.to_s
118
-
119
- if input_value.is_a?(Fixnum) || input_value.is_a?(Bignum)
120
- dt = "int"
121
- elsif input_value.is_a?(Float)
122
- dt = "float"
123
- else
124
- dt = "string"
125
- end
126
-
127
- dt
128
- end
129
- end
130
- end
1
+ require 'uuid' # gem install uuid
2
+
3
+ module BCL
4
+ ProvStruct = Struct.new(:author, :datetime, :comment)
5
+ TagsStruct = Struct.new(:descriptor)
6
+ AttrStruct = Struct.new(:name, :value, :datatype, :units)
7
+ FileStruct = Struct.new(:version_software_program, :version_id, :fqp_file, :filename, :filetype, :usage_type, :checksum)
8
+ CostStruct = Struct.new(:cost_name, :cost_type, :category, :value, :interval,
9
+ :interval_units, :year, :location, :units, :currency, :source,
10
+ :reference_component_name, :reference_component_id)
11
+
12
+ ObjectStruct = Struct.new(:obj_type, :obj_instance)
13
+
14
+ class BaseXml
15
+ attr_accessor :name
16
+ attr_accessor :description
17
+ attr_accessor :modeler_description
18
+ attr_accessor :uuid
19
+ attr_accessor :vuid
20
+
21
+ attr_accessor :attributes
22
+ attr_accessor :files
23
+ attr_accessor :costs
24
+ attr_accessor :tags
25
+ attr_accessor :provenances
26
+
27
+ def initialize(save_path)
28
+ @name = "" #this is also a unique identifier to the component...
29
+ @description = ""
30
+ @modeler_description = ""
31
+
32
+ @provenances = []
33
+ @tags = []
34
+ @attributes = []
35
+ @files = []
36
+
37
+ @schema_url = "schema.xsd"
38
+ end
39
+
40
+ def generate_uuid()
41
+ @uuid = UUID.new.generate
42
+ end
43
+
44
+ def generate_vuid()
45
+ @vuid = UUID.new.generate
46
+ end
47
+
48
+ def add_provenance(author, datetime, comment)
49
+ prov = ProvStruct.new
50
+ prov.author = author
51
+ prov.datetime = datetime
52
+ prov.comment = comment
53
+
54
+ @provenances << prov
55
+ end
56
+
57
+ def add_tag(tag_name)
58
+ tag = TagsStruct.new
59
+ tag.descriptor = tag_name
60
+
61
+ @tags << tag
62
+ end
63
+
64
+ def add_attribute(name, value, units, datatype = nil)
65
+ attr = AttrStruct.new
66
+ attr.name = name
67
+ attr.value = value
68
+
69
+ if !datatype.nil?
70
+ attr.datatype = datatype
71
+ else
72
+ attr.datatype = get_datatype(value)
73
+ end
74
+ attr.units = units
75
+
76
+ @attributes << attr
77
+ end
78
+
79
+ def add_file(version_sp, version_id, fqp_file, filename, filetype, usage_type = nil, checksum = nil)
80
+ fs = FileStruct.new
81
+ fs.version_software_program = version_sp
82
+ fs.version_id = version_id
83
+ fs.fqp_file = fqp_file
84
+ fs.filename = filename
85
+ fs.filetype = filetype
86
+ fs.usage_type = usage_type if usage_type != nil
87
+ fs.checksum = checksum if checksum != nil
88
+
89
+ @files << fs
90
+ end
91
+
92
+ #return the title case of the string
93
+ def tc(input)
94
+ val = input.gsub(/\b\w/) { $&.upcase }
95
+ if val.downcase == "energyplus"
96
+ val = "EnergyPlus"
97
+ end
98
+
99
+ val
100
+ end
101
+
102
+ def get_attribute(attribute_name)
103
+ result = nil
104
+ @attributes.each do |attr|
105
+ if attr.name == attribute_name
106
+ result = attr
107
+ end
108
+ end
109
+
110
+ result
111
+ end
112
+
113
+ def get_datatype(input_value)
114
+ dt = 'undefined'
115
+
116
+ # simple method to test if the input_value is a string, float, or integer.
117
+ # First convert the value back to a string for testing (in case it was passed as a float/integer)
118
+ test = input_value.to_s
119
+ input_value = test.match('\.').nil? ? Integer(test) : Float(test) rescue test.to_s
120
+
121
+ if input_value.is_a?(Fixnum) || input_value.is_a?(Bignum)
122
+ dt = "int"
123
+ elsif input_value.is_a?(Float)
124
+ dt = "float"
125
+ else
126
+ dt = "string"
127
+ end
128
+
129
+ dt
130
+ end
131
+ end
132
+ end
@@ -34,6 +34,7 @@ module BCL
34
34
  attr_accessor :uid
35
35
  attr_accessor :comp_version_id
36
36
  attr_accessor :description
37
+ attr_accessor :modeler_description
37
38
  attr_accessor :fidelity_level
38
39
  attr_accessor :source_manufacturer
39
40
  attr_accessor :source_model
@@ -98,7 +99,13 @@ module BCL
98
99
  #the directory
99
100
 
100
101
  Dir.chdir("#{resolve_path}")
101
- destination = "#{@name.gsub(/\W/,'_').gsub(/___/,'_').gsub(/__/,'_').chomp('_').strip}.tar.gz"
102
+ destination = "#{@name.gsub(/\W/,'_').gsub(/___/,'_').gsub(/__/,'_').chomp('_').strip}"
103
+ # truncate filenames for paths that are longer than 256 characters (with .tar.gz appended)
104
+ unless (@path + destination + destination).size < 249
105
+ destination = "#{@uid}"
106
+ puts "truncating filename...using uid instead of name"
107
+ end
108
+ destination = destination + ".tar.gz"
102
109
 
103
110
  File.delete(destination) if File.exists?(destination)
104
111
 
@@ -148,7 +155,15 @@ module BCL
148
155
 
149
156
  def resolve_path
150
157
  FileUtils.mkdir_p(@path) unless File.directory?(@path)
151
- new_path = "#{@path}/#{name.gsub(/\W/,'_').gsub(/___/,'_').gsub(/__/,'_').chomp('_').strip}"
158
+
159
+ # TODO: should probably save all components with uid instead of name to avoid path length limitation issues
160
+ # for now, switch to uid instead of name if larger than arbitrary number of characters
161
+ if @name.size < 75
162
+ new_path = "#{@path}/#{name.gsub(/\W/,'_').gsub(/___/,'_').gsub(/__/,'_').chomp('_').strip}"
163
+ else
164
+ new_path = "#{@path}/#{@uid}"
165
+ end
166
+
152
167
  FileUtils.mkdir_p(new_path) unless File.directory?(new_path)
153
168
  result = new_path
154
169
  end
@@ -275,9 +290,10 @@ module BCL
275
290
  comp_xml.uid @uuid
276
291
  comp_xml.version_id @vuid
277
292
  comp_xml.description @description if @description != ""
293
+ comp_xml.modeler_description @modeler_description if @modeler_description != ""
278
294
  comp_xml.comment @comment if @comment != ""
279
295
 
280
- comp_xml.fidelity_level @fidelity_level if @fidelity_level != ""
296
+ #comp_xml.fidelity_level @fidelity_level if @fidelity_level != ""
281
297
 
282
298
  comp_xml.provenances {
283
299
  @provenances.each do |prov|
@@ -0,0 +1,243 @@
1
+ ######################################################################
2
+ # Copyright (c) 2008-2013, 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
+
20
+ # Converts a custom Excel spreadsheet format to BCL components for upload
21
+
22
+ require 'rubyXL'
23
+ require 'bcl'
24
+
25
+ module BCL
26
+
27
+ WorksheetStruct = Struct.new(:name, :components)
28
+ HeaderStruct = Struct.new(:name, :children)
29
+ ComponentStruct = Struct.new(:row, :name, :uid, :version_id, :headers, :values)
30
+
31
+ class ComponentFromSpreadsheet
32
+
33
+ @@changed = false
34
+
35
+ public
36
+
37
+ #initialize with Excel spreadsheet to read
38
+ def initialize(xlsx_path, worksheet_names =["all"])
39
+
40
+ @xlsx_path = Pathname.new(xlsx_path).realpath.to_s
41
+ @worksheets = []
42
+
43
+ begin
44
+ xlsx= RubyXL::Parser.parse(@xlsx_path)
45
+
46
+ #by default, operate on all worksheets
47
+ if worksheet_names == ["all"]
48
+ xlsx.worksheets.each do |xlsx_worksheet|
49
+ parse_xlsx_worksheet(xlsx_worksheet)
50
+ end
51
+ else #if specific worksheets are specified, operate on them
52
+ worksheet_names.each do |worksheet_name|
53
+ parse_xlsx_worksheet(xlsx[worksheet_name])
54
+ end
55
+ end
56
+
57
+ #save spreadsheet if changes have been made
58
+ if @@changed
59
+ xlsx.write(@xlsx_path)
60
+ puts "[ComponentFromSpreadsheet] Spreadsheet changes saved"
61
+ end
62
+ ensure
63
+ xlsx=nil
64
+ end
65
+ end
66
+
67
+ def save(save_path, chunk_size = 1000, delete_old_gather = false)
68
+ #FileUtils.rm_rf(save_path) if File.exists?(save_path) and File.directory?(save_path)
69
+ # TODO: validate against taxonomy
70
+
71
+ @worksheets.each do |worksheet|
72
+ worksheet.components.each do |component|
73
+ component_xml = Component.new("#{save_path}/components")
74
+ component_xml.name = component.name
75
+ component_xml.uid = component.uid
76
+
77
+ # this tag is how we know where this goes in the taxonomy
78
+ component_xml.add_tag(worksheet.name)
79
+ puts "tag: #{worksheet.name}"
80
+
81
+ values = component.values
82
+
83
+ component.headers.each do |header|
84
+ if /description/i.match(header.name)
85
+ name = values.delete_at(0) # name, uid already processed
86
+ uid = values.delete_at(0)
87
+ description = values.delete_at(0)
88
+ component_xml.modeler_description = values.delete_at(0)
89
+ component_xml.description = description
90
+ elsif /provenance/i.match(header.name)
91
+ author = values.delete_at(0)
92
+ datetime = values.delete_at(0)
93
+ if datetime.nil?
94
+ #puts "[ComponentSpreadsheet] WARNING missing the date in the datetime column in the spreadsheet - assuming today"
95
+ datetime = DateTime.new
96
+ end
97
+
98
+ comment = values.delete_at(0)
99
+ component_xml.add_provenance(author.to_s, datetime.strftime("%Y-%m-%d"), comment.to_s)
100
+ elsif /tag/i.match(header.name)
101
+ value = values.delete_at(0)
102
+ component_xml.add_tag(value)
103
+ elsif /attribute/i.match(header.name)
104
+ value = values.delete_at(0)
105
+ name = header.children[0]
106
+ units = ""
107
+ if match_data = /(.*)\((.*)\)/.match(name)
108
+ name = match_data[1].strip
109
+ units = match_data[2].strip
110
+ end
111
+ component_xml.add_attribute(name, value, units)
112
+ elsif /source/i.match(header.name)
113
+ manufacturer = values.delete_at(0)
114
+ model = values.delete_at(0)
115
+ serial_no = values.delete_at(0)
116
+ year = values.delete_at(0)
117
+ url = values.delete_at(0)
118
+ component_xml.source_manufacturer = manufacturer
119
+ component_xml.source_model = model
120
+ component_xml.source_serial_no = serial_no
121
+ component_xml.source_year = year
122
+ component_xml.source_url = url
123
+ elsif /file/i.match(header.name)
124
+ software_program = values.delete_at(0)
125
+ version = values.delete_at(0)
126
+ filename = values.delete_at(0)
127
+ filetype = values.delete_at(0)
128
+ filepath = values.delete_at(0)
129
+ #not all components(rows) have all files; skip if filename "" or nil
130
+ next if filename == "" or filename == nil
131
+ #skip the file if it doesn't exist at the specified location
132
+ if not File.exists?(filepath)
133
+ puts "[ComponentFromSpreadsheet] ERROR #{filepath} -> File does not exist, will not be included in component xml"
134
+ next #go to the next file
135
+ end
136
+ component_xml.add_file(software_program, version, filepath, filename, filetype)
137
+ else
138
+ fail "Unknown section #{header.name}"
139
+ end
140
+ end
141
+
142
+ component_xml.save_tar_gz(false)
143
+ end
144
+ end
145
+
146
+ BCL.gather_components(save_path, chunk_size, delete_old_gather)
147
+ end
148
+
149
+ private
150
+
151
+ def parse_xlsx_worksheet(xlsx_worksheet)
152
+ worksheet = WorksheetStruct.new
153
+ worksheet.name = xlsx_worksheet[0][0].value #get A1, order is: row, col
154
+ worksheet.components = []
155
+ puts "[ComponentFromSpreadsheet] Starting parsing components of type #{worksheet.name}"
156
+
157
+ # find number of rows, first column should be name, should not be empty
158
+
159
+ xlsx_data = xlsx_worksheet.extract_data
160
+ #puts "Data: #{xlsx_data.inspect}"
161
+
162
+ num_rows = xlsx_data.size
163
+ #puts "Number of Rows: #{xlsx_data.size}"
164
+ num_rows = 2
165
+ while true do
166
+ test = xlsx_data[num_rows][0]
167
+ if test.nil? or test.empty?
168
+ #num_rows -= 1
169
+ break
170
+ end
171
+ num_rows += 1
172
+ end
173
+
174
+ # scan number of columns
175
+ headers = []
176
+ header = nil
177
+ max_col = nil
178
+
179
+ xlsx_data[0].each_with_index do |col, index|
180
+ value1 = xlsx_data[0][index]
181
+ value2 = xlsx_data[1][index]
182
+
183
+ if not value1.nil? and not value1.empty?
184
+ if not header.nil?
185
+ headers << header
186
+ end
187
+ header = HeaderStruct.new
188
+ header.name = value1
189
+ header.children = []
190
+ end
191
+
192
+ if not value2.nil? and not value2.empty?
193
+ if not header.nil?
194
+ header.children << value2
195
+ end
196
+ end
197
+
198
+ if (value1.nil? or value1.empty?) and (value2.nil? or value2.empty?)
199
+ break
200
+ end
201
+
202
+ max_col = index
203
+ end
204
+
205
+ if not header.nil?
206
+ headers << header
207
+ end
208
+
209
+ if not headers.empty?
210
+ headers[0].name = "description"
211
+ end
212
+
213
+ puts " Found #{num_rows - 2} components"
214
+
215
+ components = []
216
+ for i in 2..num_rows-1 do
217
+ component = ComponentStruct.new
218
+ component.row = i
219
+
220
+ # get name
221
+ component.name = xlsx_data[i][0]
222
+
223
+ # get uid, if empty set it
224
+ component.uid = xlsx_data[i][1]
225
+ if component.uid.nil? or component.uid.empty?
226
+ component.uid = UUID.new.generate
227
+ puts "#{component.name} uid missing; creating new one"
228
+ xlsx_worksheet.add_cell(i, 1, component.uid)
229
+ @@changed = true
230
+
231
+ end
232
+
233
+ component.headers = headers
234
+ component.values = xlsx_data[i][0..max_col]
235
+ worksheet.components << component
236
+ end
237
+
238
+ @worksheets << worksheet
239
+
240
+ puts "[ComponentFromSpreadsheet] Finished parsing components of type #{worksheet.name}"
241
+ end
242
+ end
243
+ end
@@ -17,8 +17,6 @@
17
17
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
  ######################################################################
19
19
 
20
-
21
- # required gems
22
20
  require 'net/https'
23
21
 
24
22
  module BCL
@@ -42,7 +40,6 @@ module BCL
42
40
  load_config
43
41
  end
44
42
 
45
-
46
43
  def login(username=nil, password=nil, url=nil, group_id = nil)
47
44
  #figure out what url to use
48
45
  if url.nil?
@@ -65,11 +62,12 @@ module BCL
65
62
  @group_id = group_id || @config[:server][:user][:group]
66
63
  puts "logging in using credentials in .bcl/config.yml: Connecting to #{url} on port #{port} as #{username}"
67
64
  else
68
- puts "logging in using credentials in function arguments: Connecting to #{url} on port #{port} as #{username}"
65
+ @group_id = group_id
66
+ puts "logging in using credentials in function arguments: Connecting to #{url} on port #{port} as #{username} with group #{@group_id}"
69
67
  end
70
68
 
71
69
  if @group_id.nil?
72
- puts "[WARNING] You did not set a group ID in your config.yml file. You can retrieve your group ID from the node number of your group page (e.g., https://bcl.nrel.gov/node/32). Will continue, but you will not be able to upload content."
70
+ puts "[WARNING] You did not set a group ID in your config.yml file or pass in a group ID. You can retrieve your group ID from the node number of your group page (e.g., https://bcl.nrel.gov/node/32). Will continue, but you will not be able to upload content."
73
71
  end
74
72
 
75
73
  @http = Net::HTTP.new(url, port)
@@ -150,32 +148,115 @@ module BCL
150
148
  end
151
149
  end
152
150
 
153
- #retrieve, parse, and save metadata for BCL measures
151
+ # retrieve, parse, and save metadata for BCL measures
154
152
  def measure_metadata(search_term = nil, filter_term=nil, return_all_pages = false)
155
-
156
- #setup results directory
153
+ # setup results directory
157
154
  if !File.exists?(@parsed_measures_path)
158
155
  FileUtils.mkdir_p(@parsed_measures_path)
159
156
  end
160
157
  puts "...storing parsed metadata in #{@parsed_measures_path}"
161
158
 
162
- #retrieve measures
163
- puts "retrieving measures that match search_term: #{search_term.nil? ? "nil" :search_term} and filters: #{filter_term.nil? ? "nil" :filter_term}"
159
+ # retrieve measures
160
+ puts "retrieving measures that match search_term: #{search_term.nil? ? "nil" : search_term} and filters: #{filter_term.nil? ? "nil" : filter_term}"
164
161
  retrieve_measures(search_term, filter_term, return_all_pages) do |measure|
165
- #parse and save
162
+ # parse and save
166
163
  parse_measure_metadata(measure)
167
164
  end
168
165
 
169
- return true
166
+ true
170
167
  end
171
168
 
169
+ # Read in an exisitng measure.rb file and extract the arguments. Note that the measure_name (display name)
170
+ # does not exist in the .rb file, so you have to pass this in.
171
+ def parse_measure_file(measure_name, measure_filename)
172
+ measure_hash = {}
173
+ if File.exists? measure_filename
174
+ # read in the measure file and extract some information
175
+ measure_string = File.read(measure_filename)
176
+
177
+ measure_hash[:classname] = measure_string.match(/class (.*) </)[1]
178
+ measure_hash[:path] = "#{@parsed_measures_path}/#{measure_hash[:classname]}"
179
+ #measure_hash[:display_name] = clean(measure_name)
180
+ measure_hash[:name] = measure_hash[:classname].to_underscore
181
+ measure_hash[:display_name] = clean(measure_hash[:name].titleize)
182
+ if measure_string =~ /OpenStudio::Ruleset::WorkspaceUserScript/
183
+ measure_hash[:measure_type] = "EnergyPlusMeasure"
184
+ elsif measure_string =~ /OpenStudio::Ruleset::ModelUserScript/
185
+ measure_hash[:measure_type] = "RubyMeasure"
186
+ elsif measure_string =~ /OpenStudio::Ruleset::ReportingUserScript/
187
+ measure_hash[:measure_type] = "ReportingMeasure"
188
+ else
189
+ raise "measure type is unknown with an inherited class in #{measure_filename}: #{measure_hash.inspect}"
190
+ end
191
+
192
+ measure_hash[:arguments] = []
193
+
194
+ args = measure_string.scan(/(.*).*=.*OpenStudio::Ruleset::OSArgument.*make(.*)Argument\((.*).*\)/)
195
+ args.each do |arg|
196
+ new_arg = {}
197
+ new_arg[:local_variable] = arg[0].strip
198
+ new_arg[:variable_type] = arg[1]
199
+ arg_params = arg[2].split(",")
200
+ new_arg[:name] = arg_params[0].gsub(/"|'/, "")
201
+ next if new_arg[:name] == 'info_widget'
202
+ choice_vector = arg_params[1] ? arg_params[1].strip : nil
203
+
204
+ # local variable name to get other attributes
205
+ new_arg[:display_name] = measure_string.match(/#{new_arg[:local_variable]}.setDisplayName\((.*)\)/)[1]
206
+ new_arg[:display_name].gsub!(/"|'/, "") if new_arg[:display_name]
207
+ new_arg[:display_name] = clean(new_arg[:display_name])
208
+
209
+ if measure_string =~ /#{new_arg[:local_variable]}.setDefaultValue/
210
+ new_arg[:default_value] = measure_string.match(/#{new_arg[:local_variable]}.setDefaultValue\((.*)\)/)[1]
211
+ else
212
+ puts "[WARNING] #{measure_hash[:name]}:#{new_arg[:name]} has no default value... will try to continue"
213
+ end
214
+
215
+ case new_arg[:variable_type]
216
+ when "Choice"
217
+ # Choices to appear to only be strings?
218
+ puts "Choice vector appears to be #{choice_vector}"
219
+ new_arg[:default_value].gsub!(/"|'/, "") if new_arg[:default_value]
172
220
 
173
- # Read the measure's information to pull out the metadata and to move into a more
174
- # friendly directory name.
175
- # option measure is a JSON
221
+ # parse the choices from the measure
222
+ possible_choices = measure_string.scan(/#{choice_vector}.*<<.*("|')(.*)("|')/)
223
+ puts "Possible choices are #{possible_choices}"
224
+
225
+ if possible_choices.empty?
226
+ new_arg[:choices] = []
227
+ else
228
+ new_arg[:choices] = possible_choices.map { |c| c[1] }
229
+ end
230
+
231
+ # if the choices are inherited from the model, then need to just display the default value which
232
+ # somehow magically works because that is the display name
233
+ if new_arg[:default_value]
234
+ new_arg[:choices] << new_arg[:default_value] unless new_arg[:choices].include?(new_arg[:default_value])
235
+ end
236
+ when "String"
237
+ new_arg[:default_value].gsub!(/"|'/, "") if new_arg[:default_value]
238
+ when "Bool"
239
+ new_arg[:default_value] = new_arg[:default_value].downcase == "true" ? true : false
240
+ when "Integer"
241
+ new_arg[:default_value] = new_arg[:default_value].to_i if new_arg[:default_value]
242
+ when "Double"
243
+ new_arg[:default_value] = new_arg[:default_value].to_f if new_arg[:default_value]
244
+ else
245
+ raise "unknown variable type of #{new_arg[:variable_type]}"
246
+ end
247
+
248
+ measure_hash[:arguments] << new_arg
249
+ end
250
+ end
251
+
252
+ measure_hash
253
+ end
254
+
255
+ # Read the measure's information to pull out the metadata and to move into a more friendly directory name.
256
+ # option measure is a JSON
176
257
  def parse_measure_metadata(measure)
177
258
 
178
- #check for valid measure
259
+ # check for valid measure
179
260
  if measure[:measure][:name] && measure[:measure][:uuid]
180
261
 
181
262
  file_data = download_component(measure[:measure][:uuid])
@@ -184,14 +265,14 @@ module BCL
184
265
  save_file = File.expand_path("#{@parsed_measures_path}/#{measure[:measure][:name].downcase.gsub(" ", "_")}.zip")
185
266
  File.open(save_file, 'wb') { |f| f << file_data }
186
267
 
187
- #unzip file and delete zip.
188
- #TODO check that something was downloaded here before extracting zip
189
- if File.exists?(save_file)
268
+ # unzip file and delete zip.
269
+ # TODO check that something was downloaded here before extracting zip
270
+ if File.exist? save_file
190
271
  BCL.extract_zip(save_file, @parsed_measures_path, true)
191
272
 
192
273
  # catch a weird case where there is an extra space in an unzip file structure but not in the measure.name
193
274
  if measure[:measure][:name] == "Add Daylight Sensor at Center of Spaces with a Specified Space Type Assigned"
194
- if !File.exists? "#{@parsed_measures_path}/#{measure[:measure][:name]}"
275
+ unless File.exists? "#{@parsed_measures_path}/#{measure[:measure][:name]}"
195
276
  temp_dir_name = "#{@parsed_measures_path}/Add Daylight Sensor at Center of Spaces with a Specified Space Type Assigned"
196
277
  FileUtils.move(temp_dir_name, "#{@parsed_measures_path}/#{measure[:measure][:name]}")
197
278
  end
@@ -202,92 +283,15 @@ module BCL
202
283
  # Read the measure.rb file
203
284
  #puts "save dir name #{temp_dir_name}"
204
285
  measure_filename = "#{temp_dir_name}/measure.rb"
205
- if File.exists?(measure_filename)
206
- measure_hash = {}
207
- # read in the measure file and extract some information
208
- measure_string = File.read(measure_filename)
209
-
210
- measure_hash[:classname] = measure_string.match(/class (.*) </)[1]
211
- measure_hash[:path] = "#{@parsed_measures_path}/#{measure_hash[:classname]}"
212
- measure_hash[:name] = measure[:measure][:name]
213
- if measure_string =~ /OpenStudio::Ruleset::WorkspaceUserScript/
214
- measure_hash[:measure_type] = "EnergyPlusMeasure"
215
- elsif measure_string =~ /OpenStudio::Ruleset::ModelUserScript/
216
- measure_hash[:measure_type] = "RubyMeasure"
217
- elsif measure_string =~ /OpenStudio::Ruleset::ReportingUserScript/
218
- measure_hash[:measure_type] = "ReportingMeasure"
219
- else
220
- raise "measure type is unknown with an inherited class in #{measure_filename}: #{measure_hash.inspect}"
221
- end
286
+ measure_hash = parse_measure_file(measure[:measure][:name], measure_filename)
222
287
 
288
+ unless measure_hash.empty?
223
289
  # move the directory to the class name
224
290
  FileUtils.rm_rf(measure_hash[:path]) if File.exists?(measure_hash[:path]) && temp_dir_name != measure_hash[:path]
225
291
  FileUtils.move(temp_dir_name, measure_hash[:path]) unless temp_dir_name == measure_hash[:path]
226
292
 
227
- measure_hash[:arguments] = []
228
-
229
- args = measure_string.scan(/(.*).*=.*OpenStudio::Ruleset::OSArgument::make(.*)Argument\((.*).*\)/)
230
- #puts "found #{args.size} arguments for measure '#{measure[:measure][:name]}'"
231
- args.each do |arg|
232
-
233
-
234
- new_arg = {}
235
- new_arg[:local_variable] = arg[0].strip
236
- new_arg[:variable_type] = arg[1]
237
- arg_params = arg[2].split(",")
238
- new_arg[:name] = arg_params[0].gsub(/"|'/, "")
239
- next if new_arg[:name] == 'info_widget'
240
- choice_vector = arg_params[1] ? arg_params[1].strip : nil
241
-
242
- # local variable name to get other attributes
243
- new_arg[:display_name] = measure_string.match(/#{new_arg[:local_variable]}.setDisplayName\((.*)\)/)[1]
244
- new_arg[:display_name].gsub!(/"|'/, "") if new_arg[:display_name]
245
-
246
- if measure_string =~ /#{new_arg[:local_variable]}.setDefaultValue/
247
- new_arg[:default_value] = measure_string.match(/#{new_arg[:local_variable]}.setDefaultValue\((.*)\)/)[1]
248
- else
249
- puts "[WARNING] #{measure_hash[:name]}:#{new_arg[:name]} has no default value... will try to continue"
250
- end
251
-
252
- case new_arg[:variable_type]
253
- when "Choice"
254
- # Choices to appear to only be strings?
255
- puts "Choice vector appears to be #{choice_vector}"
256
- new_arg[:default_value].gsub!(/"|'/, "") if new_arg[:default_value]
257
-
258
- # parse the choices from the measure
259
- possible_choices = measure_string.scan(/#{choice_vector}.*<<.*("|')(.*)("|')/)
260
- puts "Possible choices are #{possible_choices}"
261
-
262
- if possible_choices.empty?
263
- new_arg[:choices] = []
264
- else
265
- new_arg[:choices] = possible_choices.map { |c| c[1] }
266
- end
267
-
268
- # if the choices are inherited from the model, then need to just display the default value which
269
- # somehow magically works because that is the display name
270
- if new_arg[:default_value]
271
- new_arg[:choices] << new_arg[:default_value] unless new_arg[:choices].include?(new_arg[:default_value])
272
- end
273
- when "String"
274
- new_arg[:default_value].gsub!(/"|'/, "") if new_arg[:default_value]
275
- when "Bool"
276
- new_arg[:default_value] = new_arg[:default_value].downcase == "true" ? true : false
277
- when "Integer"
278
- new_arg[:default_value] = new_arg[:default_value].to_i if new_arg[:default_value]
279
- when "Double"
280
- new_arg[:default_value] = new_arg[:default_value].to_f if new_arg[:default_value]
281
- else
282
- raise "unknown variable type of #{new_arg[:variable_type]}"
283
- end
284
-
285
- measure_hash[:arguments] << new_arg
286
- end
287
-
288
293
  # create a new measure.json file for parsing later if need be
289
294
  File.open("#{measure_hash[:path]}/measure.json", 'w') { |f| f << MultiJson.dump(measure_hash, :pretty => true) }
290
-
291
295
  end
292
296
  else
293
297
  puts "Problems downloading #{measure[:measure][:name]}... moving on"
@@ -296,6 +300,41 @@ module BCL
296
300
  end
297
301
  end
298
302
 
303
+ # clean name
304
+ def clean(name)
305
+ # TODO: save/display errors
306
+ errors = ""
307
+ m = nil
308
+
309
+ clean_name = name
310
+ # remove everything btw parentheses
311
+ m = clean_name.match(/\((.+?)\)/)
312
+ unless m.nil?
313
+ errors = errors + " removing parentheses,"
314
+ clean_name = clean_name.gsub(/\((.+?)\)/, "")
315
+ end
316
+
317
+ # remove everything btw brackets
318
+ m = nil
319
+ m = clean_name.match(/\[(.+?)\]/)
320
+ unless m.nil?
321
+ errors = errors + " removing brackets,"
322
+ clean_name = clean_name.gsub(/\[(.+?)\]/, "")
323
+ end
324
+
325
+ # remove characters
326
+ m = nil
327
+ m = clean_name.match(/(\?|\.|\#).+?/)
328
+ unless m.nil?
329
+ errors = errors + " removing any of following: ?.#"
330
+ clean_name = clean_name.gsub(/(\?|\.|\#).+?/, "")
331
+ end
332
+ clean_name = clean_name.gsub(".", "")
333
+ clean_name = clean_name.gsub("?", "")
334
+
335
+ clean_name
336
+ end
337
+
299
338
  # retrieve measures for parsing metadata.
300
339
  # specify a search term to narrow down search or leave nil to retrieve all
301
340
  # set all_pages to true to iterate over all pages of results
@@ -461,7 +500,7 @@ module BCL
461
500
  path = "/api/content.json"
462
501
  headers = {'Content-Type' => 'application/json', 'Cookie' => @session, 'X-CSRF-Token' => @access_token}
463
502
 
464
- res = @http.post(path, @data.to_json, headers)
503
+ res = @http.post(path, MultiJson.dump(@data), headers)
465
504
 
466
505
  res_j = "could not get json from http post response"
467
506
  if res.code == '200'
@@ -0,0 +1,15 @@
1
+ class String
2
+ def to_underscore
3
+ self.gsub(/::/, '/').
4
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
5
+ gsub(/([a-z\d])([A-Z])/, '\1_\2').
6
+ tr("-", "_").
7
+ downcase
8
+ end
9
+
10
+ # simple method to create titles
11
+ def titleize
12
+ arr = ['a', 'an', 'the']
13
+ self.gsub('_', ' ').gsub(/\w+/) { |match| arr.include?(match) ? match : match.capitalize }
14
+ end
15
+ end
@@ -37,7 +37,10 @@ module BCL
37
37
 
38
38
  # each TermStruct represents a row in the master taxonomy
39
39
  TermStruct = Struct.new(:first_level, :second_level, :third_level, :level_hierarchy, :name, :description,
40
- :abbr, :data_type, :enums, :ip_written, :ip_symbol, :ip_mask, :si_written, :si_symbol, :si_mask, :unit_conversion, :default_val, :min_val, :max_val, :allow_multiple, :row, :tp_include, :tp_required, :tp_use_in_search, :tp_use_in_facets, :tp_show_data_to_data_users, :tp_third_party_testing, :tp_additional_web_dev_info, :tp_additional_data_user_info, :tp_additional_data_submitter_info)
40
+ :abbr, :data_type, :enums, :ip_written, :ip_symbol, :ip_mask, :si_written, :si_symbol, :si_mask,
41
+ :unit_conversion, :default_val, :min_val, :max_val, :allow_multiple, :row, :tp_include,
42
+ :tp_required, :tp_use_in_search, :tp_use_in_facets, :tp_show_data_to_data_users, :tp_third_party_testing,
43
+ :tp_additional_web_dev_info, :tp_additional_data_user_info, :tp_additional_data_submitter_info)
41
44
 
42
45
 
43
46
  # class for parsing, validating, and querying the master taxonomy document
data/lib/bcl/tar_ball.rb CHANGED
@@ -1,4 +1,4 @@
1
- ######################################################################
1
+ ######################################################################
2
2
  # Copyright (c) 2008-2013, Alliance for Sustainable Energy.
3
3
  # All rights reserved.
4
4
  #
@@ -21,8 +21,8 @@ module BCL
21
21
 
22
22
  module_function
23
23
 
24
+ # tarball multiple paths recursively to destination
24
25
  def tarball(destination, paths)
25
-
26
26
  #check for filepath length limit
27
27
  full_destination = File.expand_path(destination)
28
28
  if full_destination.length > 259 #256 chars max; "C:\" doesn't count
@@ -45,13 +45,12 @@ module BCL
45
45
  end
46
46
 
47
47
  def extract_tarball(filename, destination)
48
- Zlib::GzipReader.open(filename) { |gz|
48
+ Zlib::GzipReader.open(filename) do |gz|
49
49
  Archive::Tar::Minitar.unpack(gz, destination)
50
- }
50
+ end
51
51
  end
52
52
 
53
53
  def create_zip(destination, paths)
54
-
55
54
  Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile|
56
55
  paths.each do |fi|
57
56
  # Two arguments:
@@ -60,17 +59,16 @@ module BCL
60
59
  zipfile.add(fi.basename, fi)
61
60
  end
62
61
  end
63
-
64
62
  end
65
63
 
66
64
  def extract_zip(filename, destination, delete_zip = false)
67
- Zip::File.open(filename) { |zip_file|
68
- zip_file.each { |f|
65
+ Zip::File.open(filename) do |zip_file|
66
+ zip_file.each do |f|
69
67
  f_path=File.join(destination, f.name)
70
68
  FileUtils.mkdir_p(File.dirname(f_path))
71
- zip_file.extract(f, f_path) unless File.exist?(f_path)
72
- }
73
- }
69
+ zip_file.extract(f, f_path) unless File.exist? f_path
70
+ end
71
+ end
74
72
 
75
73
  if delete_zip
76
74
  fileList = Array.new
@@ -79,4 +77,4 @@ module BCL
79
77
  end
80
78
  end
81
79
 
82
- end # module BCL
80
+ end
data/lib/bcl/version.rb CHANGED
@@ -1,22 +1,22 @@
1
- ######################################################################
2
- # Copyright (c) 2008-2013, 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
-
20
- module BCL
21
- VERSION = "0.5.2"
22
- end
1
+ ######################################################################
2
+ # Copyright (c) 2008-2013, 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
+
20
+ module BCL
21
+ VERSION = "0.5.3"
22
+ end
data/lib/bcl.rb CHANGED
@@ -1,6 +1,14 @@
1
1
  require 'pathname'
2
2
  require 'base64'
3
3
 
4
+ begin
5
+ require 'openstudio'
6
+ $openstudio_gem = true
7
+ rescue LoadError => e
8
+ $openstudio_gem = false
9
+ puts 'OpenStudio did not load, but most functionality is still available. Will continue...'
10
+ end
11
+
4
12
  # file formatters
5
13
  require 'yaml'
6
14
  require 'multi_json'
@@ -12,11 +20,14 @@ require 'archive/tar/minitar'
12
20
  require 'zlib'
13
21
  require 'zip'
14
22
 
15
- require 'bcl/bcl_xml'
23
+ require 'rubyXL'
24
+
25
+ require 'bcl/core_ext'
26
+ require 'bcl/base_xml'
16
27
  require 'bcl/component_spreadsheet'
17
- require 'bcl/component_xml'
28
+ require 'bcl/component_from_spreadsheet'
29
+ require 'bcl/component'
18
30
  require 'bcl/component_methods'
19
- require 'bcl/measure_xml'
20
31
  require 'bcl/tar_ball'
21
32
  require 'bcl/master_taxonomy'
22
33
  require 'bcl/version'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bcl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Macumber
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2014-04-02 00:00:00.000000000 Z
14
+ date: 2014-06-16 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: uuid
@@ -167,6 +167,20 @@ dependencies:
167
167
  - - '>='
168
168
  - !ruby/object:Gem::Version
169
169
  version: '0'
170
+ - !ruby/object:Gem::Dependency
171
+ name: rubyXL
172
+ requirement: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - '>='
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ type: :runtime
178
+ prerelease: false
179
+ version_requirements: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - '>='
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
170
184
  description: This gem contains helper methods for generating the Component XML file
171
185
  needed to upload files to the Building Component Library. It also contains the classes
172
186
  needed for logging in via the api and uploading generating components
@@ -175,14 +189,15 @@ executables: []
175
189
  extensions: []
176
190
  extra_rdoc_files: []
177
191
  files:
178
- - lib/bcl/bcl_xml.rb
192
+ - lib/bcl/base_xml.rb
193
+ - lib/bcl/component.rb
194
+ - lib/bcl/component_from_spreadsheet.rb
179
195
  - lib/bcl/component_methods.rb
180
196
  - lib/bcl/component_spreadsheet.rb
181
- - lib/bcl/component_xml.rb
197
+ - lib/bcl/core_ext.rb
182
198
  - lib/bcl/current_taxonomy.json
183
199
  - lib/bcl/current_taxonomy.xml
184
200
  - lib/bcl/master_taxonomy.rb
185
- - lib/bcl/measure_xml.rb
186
201
  - lib/bcl/tar_ball.rb
187
202
  - lib/bcl/version.rb
188
203
  - lib/bcl.rb
@@ -1,37 +0,0 @@
1
- ######################################################################
2
- # Copyright (c) 2008-2013, 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 th e 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
-
20
- # Provides programmatic access to the component.xsd schema needed for
21
- # generating the component information that will be uploaded to
22
- # the Building Component Library.
23
-
24
- module BCL
25
- class Measure
26
- def initialize(save_path)
27
- super(save_path)
28
-
29
- end
30
-
31
- def read_measure_xml(filepath)
32
- xmlfile = File.open(filepath, 'r').read
33
-
34
- @xml = LibXML::XML::Document.string(xmlfile)
35
- end
36
- end
37
- end