openstudio-extension 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.rubocop.yml +9 -0
  4. data/Gemfile +3 -1
  5. data/Jenkinsfile +10 -0
  6. data/README.md +230 -12
  7. data/Rakefile +88 -3
  8. data/bin/console +3 -3
  9. data/doc_templates/LICENSE.md +27 -0
  10. data/doc_templates/README.md.erb +42 -0
  11. data/doc_templates/copyright_erb.txt +36 -0
  12. data/doc_templates/copyright_js.txt +4 -0
  13. data/doc_templates/copyright_ruby.txt +34 -0
  14. data/init_templates/README.md +37 -0
  15. data/init_templates/gemspec.txt +32 -0
  16. data/init_templates/openstudio_module.rb +50 -0
  17. data/init_templates/spec.rb +47 -0
  18. data/init_templates/spec_helper.rb +49 -0
  19. data/init_templates/template_gemfile.txt +17 -0
  20. data/init_templates/template_rakefile.txt +15 -0
  21. data/init_templates/version.rb +40 -0
  22. data/lib/files/openstudio-extension-gem-test.ddy +536 -0
  23. data/lib/files/openstudio-extension-gem-test.epw +8768 -0
  24. data/lib/files/openstudio-extension-gem-test.stat +554 -0
  25. data/lib/measures/openstudio_extension_test_measure/LICENSE.md +27 -0
  26. data/lib/measures/openstudio_extension_test_measure/README.md +26 -0
  27. data/lib/measures/openstudio_extension_test_measure/README.md.erb +42 -0
  28. data/lib/measures/openstudio_extension_test_measure/measure.rb +72 -0
  29. data/lib/measures/openstudio_extension_test_measure/measure.xml +83 -0
  30. data/lib/measures/openstudio_extension_test_measure/resources/os_lib_helper_methods.rb +399 -0
  31. data/lib/measures/openstudio_extension_test_measure/tests/OpenStudioExtensionTestMeasure_Test.rb +75 -0
  32. data/lib/openstudio/extension.rb +220 -0
  33. data/lib/openstudio/extension/core/CreateResults.rb +879 -0
  34. data/lib/openstudio/extension/core/check_air_sys_temps.rb +190 -0
  35. data/lib/openstudio/extension/core/check_calibration.rb +155 -0
  36. data/lib/openstudio/extension/core/check_cond_zns.rb +84 -0
  37. data/lib/openstudio/extension/core/check_domestic_hot_water.rb +334 -0
  38. data/lib/openstudio/extension/core/check_envelope_conductance.rb +453 -0
  39. data/lib/openstudio/extension/core/check_eui_by_end_use.rb +162 -0
  40. data/lib/openstudio/extension/core/check_eui_reasonableness.rb +135 -0
  41. data/lib/openstudio/extension/core/check_fan_pwr.rb +98 -0
  42. data/lib/openstudio/extension/core/check_internal_loads.rb +393 -0
  43. data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +226 -0
  44. data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +326 -0
  45. data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +464 -0
  46. data/lib/openstudio/extension/core/check_mech_sys_type.rb +139 -0
  47. data/lib/openstudio/extension/core/check_part_loads.rb +451 -0
  48. data/lib/openstudio/extension/core/check_placeholder.rb +75 -0
  49. data/lib/openstudio/extension/core/check_plant_cap.rb +123 -0
  50. data/lib/openstudio/extension/core/check_plant_temps.rb +159 -0
  51. data/lib/openstudio/extension/core/check_plenum_loads.rb +87 -0
  52. data/lib/openstudio/extension/core/check_pump_pwr.rb +108 -0
  53. data/lib/openstudio/extension/core/check_sch_coord.rb +241 -0
  54. data/lib/openstudio/extension/core/check_schedules.rb +311 -0
  55. data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +158 -0
  56. data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +148 -0
  57. data/lib/openstudio/extension/core/check_weather_files.rb +132 -0
  58. data/lib/openstudio/extension/core/deer_vintages.rb +311 -0
  59. data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +491 -0
  60. data/lib/openstudio/extension/core/os_lib_cofee.rb +259 -0
  61. data/lib/openstudio/extension/core/os_lib_constructions.rb +378 -0
  62. data/lib/openstudio/extension/core/os_lib_geometry.rb +1022 -0
  63. data/lib/openstudio/extension/core/os_lib_helper_methods.rb +399 -0
  64. data/lib/openstudio/extension/core/os_lib_hvac.rb +2171 -0
  65. data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +214 -0
  66. data/lib/openstudio/extension/core/os_lib_model_generation.rb +817 -0
  67. data/lib/openstudio/extension/core/os_lib_model_simplification.rb +1049 -0
  68. data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +165 -0
  69. data/lib/openstudio/extension/core/os_lib_reporting.rb +4652 -0
  70. data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +200 -0
  71. data/lib/openstudio/extension/core/os_lib_schedules.rb +963 -0
  72. data/lib/openstudio/extension/rake_task.rb +149 -0
  73. data/lib/openstudio/extension/runner.rb +644 -0
  74. data/lib/openstudio/extension/version.rb +40 -0
  75. data/openstudio-extension.gemspec +20 -15
  76. metadata +150 -14
  77. data/.travis.yml +0 -7
  78. data/lib/OpenStudio/Extension/rake_task.rb +0 -84
  79. data/lib/OpenStudio/Extension/version.rb +0 -33
  80. data/lib/OpenStudio/extension.rb +0 -65
@@ -0,0 +1,149 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ require 'rake'
37
+ require 'rake/tasklib'
38
+ require 'rake/testtask'
39
+ require_relative '../extension'
40
+
41
+ module OpenStudio
42
+ module Extension
43
+ class RakeTask < Rake::TaskLib
44
+ attr_accessor :name, :measures_dir, :core_dir, :doc_templates_dir, :files_dir
45
+
46
+ def initialize(*args, &task_block)
47
+ @name = args.shift || :openstudio
48
+
49
+ setup_subtasks(@name)
50
+ end
51
+
52
+ def set_extension_class(extension_class)
53
+ @extension_class = extension_class
54
+ @extension = extension_class.new
55
+ @root_dir = @extension.root_dir
56
+ @measures_dir = @extension.measures_dir
57
+ @core_dir = @extension.core_dir
58
+ @doc_templates_dir = @extension.doc_templates_dir
59
+ @files_dir = @extension.files_dir
60
+ end
61
+
62
+ private
63
+
64
+ def setup_subtasks(name)
65
+ namespace name do
66
+ desc 'Run the CLI task to check for measure updates'
67
+ task update_measures: ['measures:add_license', 'measures:add_readme', 'measures:copy_resources', 'update_copyright'] do
68
+ puts 'updating measures...'
69
+ runner = OpenStudio::Extension::Runner.new(Dir.pwd)
70
+ runner.update_measures(@measures_dir)
71
+ end
72
+
73
+ desc 'List measures'
74
+ task :list_measures do
75
+ puts 'Listing measures...'
76
+ runner = OpenStudio::Extension::Runner.new(Dir.pwd)
77
+ runner.list_measures(@measures_dir)
78
+ end
79
+
80
+ desc 'Use openstudio system ruby to run tests'
81
+ task :test_with_openstudio do
82
+ # puts Dir.pwd
83
+ # puts Rake.original_dir
84
+ puts 'testing with openstudio'
85
+ runner = OpenStudio::Extension::Runner.new(Dir.pwd)
86
+ result = runner.test_measures_with_cli(@measures_dir)
87
+
88
+ if !result
89
+ exit 1
90
+ end
91
+ end
92
+
93
+ desc 'Use openstudio docker image to run tests'
94
+ task :test_with_docker do
95
+ puts 'testing with docker'
96
+ end
97
+
98
+ # namespace for measure operations
99
+ namespace 'measures' do
100
+ desc 'Copy the resources files to individual measures'
101
+ task :copy_resources do
102
+ # make sure we don't have conflicting resource file names
103
+ OpenStudio::Extension.check_for_name_conflicts
104
+
105
+ puts 'Copying resource files from the core library to individual measures'
106
+ runner = OpenStudio::Extension::Runner.new(Dir.pwd)
107
+ runner.copy_core_files(@measures_dir, @core_dir)
108
+ end
109
+
110
+ desc 'Add License File to measures'
111
+ task :add_license do
112
+ # copy license file
113
+ puts 'Adding license file to measures'
114
+ runner = OpenStudio::Extension::Runner.new(Dir.pwd)
115
+ runner.add_measure_license(@measures_dir, @doc_templates_dir)
116
+ end
117
+
118
+ desc 'Add README.md.erb file if it and README.md do not already exist for a measure'
119
+ task :add_readme do
120
+ # copy README.md.erb file
121
+ puts 'Adding README.md.erb to measures where it and README.md do not exist.'
122
+ puts 'Only files that have actually been changed will be listed.'
123
+ runner = OpenStudio::Extension::Runner.new(Dir.pwd)
124
+ runner.add_measure_readme(@measures_dir, @doc_templates_dir)
125
+ end
126
+ end
127
+
128
+ desc 'Update copyright on files'
129
+ task :update_copyright do
130
+ # update copyright
131
+ puts 'Updating COPYRIGHT in files'
132
+ runner = OpenStudio::Extension::Runner.new(Dir.pwd)
133
+ runner.update_copyright(@root_dir, @doc_templates_dir)
134
+ end
135
+
136
+ desc 'Copy the measures to a location that can be uploaded to BCL'
137
+ task :stage_bcl do
138
+ puts 'Staging measures for BCL'
139
+ end
140
+
141
+ desc 'Upload measures from the specified location.'
142
+ task :push_bcl do
143
+ puts 'Push measures to BCL'
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,644 @@
1
+
2
+ # *******************************************************************************
3
+ # OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
4
+ # All rights reserved.
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # (1) Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # (3) Neither the name of the copyright holder nor the names of any contributors
16
+ # may be used to endorse or promote products derived from this software without
17
+ # specific prior written permission from the respective party.
18
+ #
19
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
20
+ # of modifications or other derivative works may not use the "OpenStudio"
21
+ # trademark, "OS", "os", or any other confusingly similar designation without
22
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
23
+ #
24
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
25
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
26
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
28
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
29
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
31
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ # *******************************************************************************
36
+
37
+ require 'bundler'
38
+ require 'fileutils'
39
+ require 'json'
40
+ require 'open3'
41
+ require 'openstudio'
42
+ require 'yaml'
43
+ require 'fileutils'
44
+ require 'parallel'
45
+
46
+ module OpenStudio
47
+ module Extension
48
+ ##
49
+ # The Runner class provides functionality to run various commands including calls to the OpenStudio CLI.
50
+ #
51
+ class Runner
52
+ attr_reader :gemfile_path, :bundle_install_path
53
+ ##
54
+ # When initialized with a directory containing a Gemfile, the Runner will attempt to create a bundle
55
+ # compatible with the OpenStudio CLI.
56
+ ##
57
+ # @param [String] dirname Directory to run commands in, defaults to Dir.pwd. If directory includes a Gemfile then create a local bundle.
58
+ def initialize(dirname = Dir.pwd, bundle_without = [])
59
+ # DLM: I am not sure if we want to use the main root directory to create these bundles
60
+ # had the idea of passing in a Gemfile name/alias and path to Gemfile, then doing the bundle in ~/OpenStudio/#{alias} or something like that?
61
+
62
+ puts "Initializing runner with dirname: '#{dirname}'"
63
+ @dirname = File.absolute_path(dirname)
64
+ @gemfile_path = File.join(@dirname, 'Gemfile')
65
+ @bundle_install_path = File.join(@dirname, '.bundle/install/')
66
+ @original_dir = Dir.pwd
67
+
68
+ @bundle_without = bundle_without
69
+ @bundle_without_string = bundle_without.join(' ')
70
+ puts "@bundle_without_string = '#{@bundle_without_string}'"
71
+
72
+ raise "#{@dirname} does not exist" if !File.exist?(@dirname)
73
+ raise "#{@dirname} is not a directory" if !File.directory?(@dirname)
74
+
75
+ if !File.exist?(@gemfile_path)
76
+ # if there is no gemfile set these to nil
77
+ @gemfile_path = nil
78
+ @bundle_install_path = nil
79
+ else
80
+ # there is a gemfile, attempt to create a bundle
81
+ original_dir = Dir.pwd
82
+ begin
83
+ # go to the directory with the gemfile
84
+ Dir.chdir(@dirname)
85
+
86
+ # test to see if bundle is installed
87
+ check_bundle = run_command('bundle -v', get_clean_env)
88
+ if !check_bundle
89
+ raise "Failed to run command 'bundle -v', check that bundle is installed" if !File.exist?(@dirname)
90
+ end
91
+
92
+ # TODO: check that ruby version is correct
93
+
94
+ # check existing config
95
+ needs_config = true
96
+ if File.exist?('./.bundle/config')
97
+ puts 'config exists'
98
+ needs_config = false
99
+ config = YAML.load_file('./.bundle/config')
100
+
101
+ if config['BUNDLE_PATH'] != @bundle_install_path
102
+ needs_config = true
103
+ end
104
+
105
+ # if config['BUNDLE_WITHOUT'] != @bundle_without_string
106
+ # needs_config = true
107
+ # end
108
+ end
109
+
110
+ # check existing platform
111
+ needs_platform = true
112
+ if File.exist?('Gemfile.lock')
113
+ puts 'Gemfile.lock exists'
114
+ gemfile_lock = Bundler::LockfileParser.new(Bundler.read_file('Gemfile.lock'))
115
+ if gemfile_lock.platforms.include?('ruby')
116
+ # already been configured, might not be up to date
117
+ needs_platform = false
118
+ end
119
+ end
120
+
121
+ puts "needs_config = #{needs_config}"
122
+ if needs_config
123
+ run_command("bundle config --local path '#{@bundle_install_path}'", get_clean_env)
124
+ # run_command("bundle config --local without '#{@bundle_without_string}'", get_clean_env)
125
+ end
126
+
127
+ puts "needs_platform = #{needs_platform}"
128
+ if needs_platform
129
+ run_command('bundle lock --add_platform ruby', get_clean_env)
130
+ end
131
+
132
+ needs_update = needs_config || needs_platform
133
+ if !needs_update
134
+ if !File.exist?('Gemfile.lock') || File.mtime(@gemfile_path) > File.mtime('Gemfile.lock')
135
+ needs_update = true
136
+ end
137
+ end
138
+
139
+ puts "needs_update = #{needs_update}"
140
+ if needs_update
141
+ run_command('bundle update', get_clean_env)
142
+ end
143
+ ensure
144
+ Dir.chdir(@original_dir)
145
+ end
146
+ end
147
+ end
148
+
149
+ ##
150
+ # Returns a hash of environment variables that can be merged with the current environment to prevent automatic bundle activation.
151
+ #
152
+ # DLM: should this be a module or class method?
153
+ ##
154
+ # @return [Hash]
155
+ def get_clean_env
156
+ # blank out bundler and gem path modifications, will be re-setup by new call
157
+ new_env = {}
158
+ new_env['BUNDLER_ORIG_MANPATH'] = nil
159
+ new_env['BUNDLER_ORIG_PATH'] = nil
160
+ new_env['BUNDLER_VERSION'] = nil
161
+ new_env['BUNDLE_BIN_PATH'] = nil
162
+ new_env['RUBYLIB'] = nil
163
+ new_env['RUBYOPT'] = nil
164
+
165
+ # DLM: preserve GEM_HOME and GEM_PATH set by current bundle because we are not supporting bundle
166
+ # requires to ruby gems will work, will fail if we require a native gem
167
+ new_env['GEM_PATH'] = nil
168
+ new_env['GEM_HOME'] = nil
169
+
170
+ # DLM: for now, ignore current bundle in case it has binary dependencies in it
171
+ # bundle_gemfile = ENV['BUNDLE_GEMFILE']
172
+ # bundle_path = ENV['BUNDLE_PATH']
173
+ # if bundle_gemfile.nil? || bundle_path.nil?
174
+ new_env['BUNDLE_GEMFILE'] = nil
175
+ new_env['BUNDLE_PATH'] = nil
176
+ new_env['BUNDLE_WITHOUT'] = nil
177
+ # else
178
+ # new_env['BUNDLE_GEMFILE'] = bundle_gemfile
179
+ # new_env['BUNDLE_PATH'] = bundle_path
180
+ # end
181
+
182
+ return new_env
183
+ end
184
+
185
+ ##
186
+ # Run a command after merging the current environment with env. Command is run in @dirname, returns to Dir.pwd after completion.
187
+ # Returns true if the command completes successfully, false otherwise.
188
+ # Standard Out, Standard Error, and Status Code are collected and printed, but not returned.
189
+ ##
190
+ # @return [Boolean]
191
+ def run_command(command, env = {})
192
+ result = false
193
+ original_dir = Dir.pwd
194
+ begin
195
+ Dir.chdir(@dirname)
196
+
197
+ # DLM: using popen3 here can result in deadlocks
198
+ stdout_str, stderr_str, status = Open3.capture3(env, command)
199
+ if status.success?
200
+ # puts "Command completed successfully"
201
+ # puts "stdout: #{stdout_str}"
202
+ # puts "stderr: #{stderr_str}"
203
+ # STDOUT.flush
204
+ result = true
205
+ else
206
+ puts "Error running command: '#{command}'"
207
+ puts "stdout: #{stdout_str}"
208
+ puts "stderr: #{stderr_str}"
209
+ STDOUT.flush
210
+ result = false
211
+ end
212
+ ensure
213
+ Dir.chdir(original_dir)
214
+ return result
215
+ end
216
+
217
+ return result
218
+ end
219
+
220
+ ##
221
+ # Get path to all measures found under measure dir.
222
+ ##
223
+ # @param [String] measures_dir Measures directory
224
+ ##
225
+ # @return [Array] returns path to all measure directories found under measure dir
226
+ def get_measures_in_dir(measures_dir)
227
+ measures = Dir.glob(File.join(measures_dir, '**/measure.rb'))
228
+ if measures.empty?
229
+ # also try nested 2-deep to support openstudio-measures
230
+ measures = Dir.glob(File.join(measures_dir, '**/**/measure.rb'))
231
+ end
232
+
233
+ result = []
234
+ measures.each { |m| result << File.dirname(m) }
235
+ return result
236
+ end
237
+
238
+ ##
239
+ # Get path to all measures dirs found under measure dir.
240
+ ##
241
+ # @param [String] measures_dir Measures directory
242
+ ##
243
+ # @return [Array] returns path to all directories containing measures found under measure dir
244
+ def get_measure_dirs_in_dir(measures_dir)
245
+ measures = Dir.glob(File.join(measures_dir, '**/measure.rb'))
246
+ if measures.empty?
247
+ # also try nested 2-deep to support openstudio-measures
248
+ measures = Dir.glob(File.join(measures_dir, '**/**/measure.rb'))
249
+ end
250
+
251
+ result = []
252
+ measures.each { |m| result << File.dirname(File.dirname(m)) }
253
+
254
+ return result.uniq
255
+ end
256
+
257
+ ##
258
+ # Run the OpenStudio CLI command to test measures on given directory
259
+ # Returns true if the command completes successfully, false otherwise.
260
+ # measures_dir configured in rake_task
261
+ ##
262
+ # @return [Boolean]
263
+ def test_measures_with_cli(measures_dir)
264
+ puts 'Testing measures with CLI system call'
265
+ if measures_dir.nil? || measures_dir.empty?
266
+ puts 'Measures dir is nil or empty'
267
+ return true
268
+ end
269
+
270
+ puts "measures path: #{measures_dir}"
271
+
272
+ cli = OpenStudio.getOpenStudioCLI
273
+
274
+ the_call = ''
275
+ if @gemfile_path
276
+ if @bundle_without_string.empty?
277
+ the_call = "#{cli} --verbose --bundle '#{@gemfile_path}' --bundle_path '#{@bundle_install_path}' measure -r '#{measures_dir}'"
278
+ else
279
+ the_call = "#{cli} --verbose --bundle '#{@gemfile_path}' --bundle_path '#{@bundle_install_path}' --bundle_without '#{@bundle_without_string}' measure -r '#{measures_dir}'"
280
+ end
281
+ else
282
+ the_call = "#{cli} --verbose measure -r #{measures_dir}"
283
+ end
284
+
285
+ puts 'SYSTEM CALL:'
286
+ puts the_call
287
+ STDOUT.flush
288
+ result = run_command(the_call, get_clean_env)
289
+ puts "DONE, result = #{result}"
290
+ STDOUT.flush
291
+
292
+ return result
293
+ end
294
+
295
+ ##
296
+ # Run the OpenStudio CLI command to update measures on given directory
297
+ # Returns true if the command completes successfully, false otherwise.
298
+ ##
299
+ # @return [Boolean]
300
+ def update_measures(measures_dir)
301
+ puts 'Updating measures with CLI system call'
302
+ if measures_dir.nil? || measures_dir.empty?
303
+ puts 'Measures dir is nil or empty'
304
+ return true
305
+ end
306
+
307
+ result = true
308
+ # DLM: this is a temporary workaround to handle OpenStudio-Measures
309
+ get_measure_dirs_in_dir(measures_dir).each do |measures_dir|
310
+ puts "measures path: #{measures_dir}"
311
+
312
+ cli = OpenStudio.getOpenStudioCLI
313
+
314
+ the_call = ''
315
+ if @gemfile_path
316
+ if @bundle_without_string.empty?
317
+ the_call = "#{cli} --verbose --bundle '#{@gemfile_path}' --bundle_path '#{@bundle_install_path}' measure -t '#{measures_dir}'"
318
+ else
319
+ the_call = "#{cli} --verbose --bundle '#{@gemfile_path}' --bundle_path '#{@bundle_install_path}' --bundle_without '#{@bundle_without_string}' measure -t '#{measures_dir}'"
320
+ end
321
+ else
322
+ the_call = "#{cli} --verbose measure -t '#{measures_dir}'"
323
+ end
324
+
325
+ puts 'SYSTEM CALL:'
326
+ puts the_call
327
+ STDOUT.flush
328
+ result &&= run_command(the_call, get_clean_env)
329
+ puts "DONE, result = #{result}"
330
+ STDOUT.flush
331
+ end
332
+
333
+ return result
334
+ end
335
+
336
+ ##
337
+ # List measures in given directory
338
+ # Returns true if the command completes successfully, false otherwise.
339
+ ##
340
+ # @return [Boolean]
341
+
342
+ ##
343
+ def list_measures(measures_dir)
344
+ puts 'Listing measures'
345
+ if measures_dir.nil? || measures_dir.empty?
346
+ puts 'Measures dir is nil or empty'
347
+ return true
348
+ end
349
+
350
+ puts "measures path: #{measures_dir}"
351
+
352
+ # this is to accommodate a single measures dir (like most gems)
353
+ # or a repo with multiple directories fo measures (like OpenStudio-measures)
354
+ measures = Dir.glob(File.join(measures_dir, '**/measure.rb'))
355
+ if measures.empty?
356
+ # also try nested 2-deep to support openstudio-measures
357
+ measures = Dir.glob(File.join(measures_dir, '**/**/measure.rb'))
358
+ end
359
+ puts "#{measures.length} MEASURES FOUND"
360
+ measures.each do |measure|
361
+ name = measure.split('/')[-2]
362
+ puts name.to_s
363
+ end
364
+ end
365
+
366
+ # Update measures by copying in the latest resource files from the Extension gem into
367
+ # the measures' respective resources folders.
368
+ # measures_dir and core_dir configured in rake_task
369
+ # Returns true if the command completes successfully, false otherwise.
370
+ #
371
+ # @return [Boolean]
372
+ def copy_core_files(measures_dir, core_dir)
373
+ puts 'Copying measure resources'
374
+ if measures_dir.nil? || measures_dir.empty?
375
+ puts 'Measures dir is nil or empty'
376
+ return true
377
+ end
378
+
379
+ result = false
380
+ puts 'Copying updated resource files from extension core directory to individual measures.'
381
+ puts 'Only files that have actually been changed will be listed.'
382
+
383
+ # get all resource files in the core dir
384
+ resource_files = Dir.glob(File.join(core_dir, '/*.*'))
385
+
386
+ # this is to accommodate a single measures dir (like most gems)
387
+ # or a repo with multiple directories fo measures (like OpenStudio-measures)
388
+ measures = Dir.glob(File.join(measures_dir, '**/resources/*.rb'))
389
+ if measures.empty?
390
+ # also try nested 2-deep to support openstudio-measures
391
+ measures = Dir.glob(File.join(measures_dir, '**/**/resources/*.rb'))
392
+ end
393
+
394
+ # Note: some older measures like AEDG use 'OsLib_SomeName' instead of 'os_lib_some_name'
395
+ # this script isn't replacing those copies
396
+
397
+ # loop through resource files
398
+ resource_files.each do |resource_file|
399
+ # loop through measure dirs looking for matching file
400
+ measures.each do |measure|
401
+ next unless File.basename(measure) == File.basename(resource_file)
402
+ next if FileUtils.identical?(resource_file, File.path(measure))
403
+ puts "Replacing #{measure} with #{resource_file}."
404
+ FileUtils.cp(resource_file, File.path(measure))
405
+ end
406
+ end
407
+ result = true
408
+
409
+ return result
410
+ end
411
+
412
+ # Update measures by adding license file
413
+ # measures_dir and doc_templates_dir configured in rake_task
414
+ # Returns true if the command completes successfully, false otherwise.
415
+ ##
416
+ # @return [Boolean]
417
+ def add_measure_license(measures_dir, doc_templates_dir)
418
+ puts 'Adding measure licenses'
419
+ if measures_dir.nil? || measures_dir.empty?
420
+ puts 'Measures dir is nil or empty'
421
+ return true
422
+ elsif doc_templates_dir.nil? || doc_templates_dir.empty?
423
+ puts 'Doc templates dir is nil or empty'
424
+ return false
425
+ end
426
+
427
+ result = false
428
+ license_file = File.join(doc_templates_dir, 'LICENSE.md')
429
+ puts "License file path: #{license_file}"
430
+
431
+ raise "License file not found '#{license_file}'" if !File.exist?(license_file)
432
+
433
+ measures = Dir["#{measures_dir}/**/measure.rb"]
434
+ if measures.empty?
435
+ # also try nested 2-deep to support openstudio-measures
436
+ measures = Dir["#{measures_dir}/**/**/measure.rb"]
437
+ end
438
+ measures.each do |measure|
439
+ FileUtils.cp(license_file, "#{File.dirname(measure)}/LICENSE.md")
440
+ end
441
+ result = true
442
+ return result
443
+ end
444
+
445
+ # Update measures by adding license file
446
+ # measures_dir and doc_templates_dir configured in rake_task
447
+ # Returns true if the command completes successfully, false otherwise.
448
+ ##
449
+ # @return [Boolean]
450
+ def add_measure_readme(measures_dir, doc_templates_dir)
451
+ puts 'Adding measure readmes'
452
+ if measures_dir.nil? || measures_dir.empty?
453
+ puts 'Measures dir is nil or empty'
454
+ return true
455
+ elsif doc_templates_dir.nil? || doc_templates_dir.empty?
456
+ puts 'Measures files dir is nil or empty'
457
+ return false
458
+ end
459
+
460
+ result = false
461
+ readme_file = File.join(doc_templates_dir, 'README.md.erb')
462
+ puts "Readme file path: #{readme_file}"
463
+
464
+ raise "Readme file not found '#{readme_file}'" if !File.exist?(readme_file)
465
+
466
+ measures = Dir["#{measures_dir}/**/measure.rb"]
467
+ if measures.empty?
468
+ # also try nested 2-deep to support openstudio-measures
469
+ measures = Dir["#{measures_dir}/**/**/measure.rb"]
470
+ end
471
+ measures.each do |measure|
472
+ next if File.exist?("#{File.dirname(measure)}/README.md.erb")
473
+ next if File.exist?("#{File.dirname(measure)}/README.md")
474
+ puts "adding template README to #{measure}"
475
+ FileUtils.cp(readme_file, "#{File.dirname(measure)}/README.md.erb")
476
+ end
477
+ result = true
478
+ return result
479
+ end
480
+
481
+ def update_copyright(root_dir, doc_templates_dir)
482
+ if root_dir.nil? || root_dir.empty?
483
+ puts 'Root dir is nil or empty'
484
+ return false
485
+ elsif doc_templates_dir.nil? || doc_templates_dir.empty?
486
+ puts 'Doc templates dir is nil or empty'
487
+ return false
488
+ end
489
+
490
+ if File.exist?(File.join(doc_templates_dir, 'LICENSE.md'))
491
+ if File.exist?(File.join(root_dir, 'LICENSE.md'))
492
+ puts 'updating LICENSE.md in root dir'
493
+ FileUtils.cp(File.join(doc_templates_dir, 'LICENSE.md'), File.join(root_dir, 'LICENSE.md'))
494
+ end
495
+ end
496
+
497
+ ruby_regex = /^\#\s?[\#\*]{12,}.*copyright.*?\#\s?[\#\*]{12,}\s*$/mi
498
+ erb_regex = /^<%\s*\#\s?[\#\*]{12,}.*copyright.*?\#\s?[\#\*]{12,}\s*%>$/mi
499
+ js_regex = /^\/\* @preserve.*copyright.*license.{2}\*\//mi
500
+
501
+ filename = File.join(doc_templates_dir, 'copyright_ruby.txt')
502
+ puts "Copyright file path: #{filename}"
503
+ raise "Copyright file not found '#{filename}'" if !File.exist?(filename)
504
+ file = File.open(filename, 'r')
505
+ ruby_header_text = file.read
506
+ file.close
507
+ ruby_header_text.strip!
508
+ ruby_header_text += "\n"
509
+
510
+ filename = File.join(doc_templates_dir, 'copyright_erb.txt')
511
+ puts "Copyright file path: #{filename}"
512
+ raise "Copyright file not found '#{filename}'" if !File.exist?(filename)
513
+ file = File.open(filename, 'r')
514
+ erb_header_text = file.read
515
+ file.close
516
+ erb_header_text.strip!
517
+ erb_header_text += "\n"
518
+
519
+ filename = File.join(doc_templates_dir, 'copyright_js.txt')
520
+ puts "Copyright file path: #{filename}"
521
+ raise "Copyright file not found '#{filename}'" if !File.exist?(filename)
522
+ file = File.open(filename, 'r')
523
+ js_header_text = file.read
524
+ file.close
525
+ js_header_text.strip!
526
+ js_header_text += "\n"
527
+
528
+ raise 'bad copyright_ruby.txt' if ruby_header_text !~ ruby_regex
529
+ raise 'bad copyright_erb.txt' if erb_header_text !~ erb_regex
530
+ raise 'bad copyright_js.txt' if js_header_text !~ js_regex
531
+
532
+ # look for .rb, .html.erb, and .js.erb
533
+ paths = [
534
+ { glob: "#{root_dir}/**/*.rb", license: ruby_header_text, regex: ruby_regex },
535
+ { glob: "#{root_dir}/**/*.html.erb", license: erb_header_text, regex: erb_regex },
536
+ { glob: "#{root_dir}/**/*.js.erb", license: js_header_text, regex: js_regex }
537
+ ]
538
+
539
+ puts "Encoding.default_external = #{Encoding.default_external}"
540
+ puts "Encoding.default_internal = #{Encoding.default_internal}"
541
+
542
+ paths.each do |path|
543
+ Dir[path[:glob]].each do |file|
544
+ puts "Updating license in file #{file}"
545
+ f = File.read(file)
546
+ if f =~ path[:regex]
547
+ puts ' License found -- updating'
548
+ File.open(file, 'w') { |write| write << f.gsub(path[:regex], path[:license]) }
549
+ elsif f =~ /\(C\)/i || f =~ /\(Copyright\)/i
550
+ puts ' File already has copyright -- skipping'
551
+ else
552
+ puts ' No license found -- adding'
553
+ if f =~ /#!/
554
+ puts ' CANNOT add license to file automatically, add it manually and it will update automatically in the future'
555
+ next
556
+ end
557
+ File.open(file, 'w') { |write| write << f.insert(0, path[:license] + "\n") }
558
+ end
559
+ end
560
+ end
561
+ end
562
+
563
+ ##
564
+ # Run the OpenStudio CLI on an OSW. The OSW is configured to include measure and file locations for all loaded OpenStudio Extensions.
565
+ ##
566
+ # @param [String, Hash] in_osw If string this is the path to an OSW file on disk, if Hash it is loaded JSON with symbolized keys
567
+ # @param [String] run_dir Directory to run the OSW in, will be created if does not exist
568
+ ##
569
+ # @return [Boolean] True if command succeeded, false otherwise # DLM: should this return path to out.osw instead?
570
+ def run_osw(in_osw, run_dir)
571
+ run_dir = File.absolute_path(run_dir)
572
+
573
+ if in_osw.is_a?(String)
574
+ in_osw_path = in_osw
575
+ raise "'#{in_osw_path}' does not exist" if !File.exist?(in_osw_path)
576
+
577
+ in_osw = {}
578
+ File.open(in_osw_path, 'r') do |file|
579
+ in_osw = JSON.parse(file.read, symbolize_names: true)
580
+ end
581
+ end
582
+
583
+ osw = OpenStudio::Extension.configure_osw(in_osw)
584
+ osw[:run_directory] = run_dir
585
+
586
+ FileUtils.mkdir_p(run_dir)
587
+
588
+ run_osw_path = File.join(run_dir, 'in.osw')
589
+ File.open(run_osw_path, 'w') do |file|
590
+ file.puts JSON.pretty_generate(osw)
591
+ end
592
+
593
+ cli = OpenStudio.getOpenStudioCLI
594
+ out_log = run_osw_path + '.log'
595
+ if Gem.win_platform?
596
+ # out_log = "nul"
597
+ else
598
+ # out_log = "/dev/null"
599
+ end
600
+
601
+ the_call = ''
602
+ if @gemfile_path
603
+ if @bundle_without_string.empty?
604
+ the_call = "#{cli} --verbose --bundle '#{@gemfile_path}' --bundle_path '#{@bundle_install_path}' run -w '#{run_osw_path}' 2>&1 > \"#{out_log}\""
605
+ else
606
+ the_call = "#{cli} --verbose --bundle '#{@gemfile_path}' --bundle_path '#{@bundle_install_path}' --bundle_without '#{@bundle_without_string}' run -w '#{run_osw_path}' 2>&1 > \"#{out_log}\""
607
+ end
608
+ else
609
+ the_call = "#{cli} --verbose run -w '#{run_osw_path}' 2>&1 > \"#{out_log}\""
610
+ end
611
+
612
+ puts 'SYSTEM CALL:'
613
+ puts the_call
614
+ STDOUT.flush
615
+ result = run_command(the_call, get_clean_env)
616
+ puts "DONE, result = #{result}"
617
+ STDOUT.flush
618
+
619
+ # DLM: this does not always return false for failed CLI runs, consider checking for failed.job file as backup test
620
+
621
+ return result
622
+ end
623
+
624
+ # run osws, return any failure messages
625
+ def run_osws(osw_files, num_parallel = 1, max_to_run = Float::INFINITY)
626
+ failures = []
627
+
628
+ osw_files = osw_files.slice(0, [osw_files.size, max_to_run].min)
629
+
630
+ Parallel.each(osw_files, in_threads: num_parallel) do |osw|
631
+ # osw_files.each do |osw|
632
+
633
+ result = run_osw(osw, File.dirname(osw))
634
+
635
+ if !result
636
+ failures << "Failed to run OSW '#{osw}'"
637
+ end
638
+ end
639
+
640
+ return failures
641
+ end
642
+ end
643
+ end
644
+ end