openstudio-extension 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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