ndr_support 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +14 -0
  3. data/.rubocop.yml +27 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +22 -0
  6. data/CODE_OF_CONDUCT.md +13 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +16 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +91 -0
  11. data/Rakefile +12 -0
  12. data/code_safety.yml +258 -0
  13. data/gemfiles/Gemfile.rails32 +6 -0
  14. data/gemfiles/Gemfile.rails32.lock +108 -0
  15. data/gemfiles/Gemfile.rails41 +6 -0
  16. data/gemfiles/Gemfile.rails41.lock +111 -0
  17. data/gemfiles/Gemfile.rails42 +6 -0
  18. data/gemfiles/Gemfile.rails42.lock +111 -0
  19. data/lib/ndr_support.rb +21 -0
  20. data/lib/ndr_support/array.rb +52 -0
  21. data/lib/ndr_support/concerns/working_days.rb +94 -0
  22. data/lib/ndr_support/date_and_time_extensions.rb +103 -0
  23. data/lib/ndr_support/daterange.rb +196 -0
  24. data/lib/ndr_support/fixnum/calculations.rb +15 -0
  25. data/lib/ndr_support/fixnum/julian_date_conversions.rb +14 -0
  26. data/lib/ndr_support/hash.rb +52 -0
  27. data/lib/ndr_support/integer.rb +12 -0
  28. data/lib/ndr_support/nil.rb +38 -0
  29. data/lib/ndr_support/ourdate.rb +97 -0
  30. data/lib/ndr_support/ourtime.rb +51 -0
  31. data/lib/ndr_support/regexp_range.rb +65 -0
  32. data/lib/ndr_support/safe_file.rb +185 -0
  33. data/lib/ndr_support/safe_path.rb +268 -0
  34. data/lib/ndr_support/string/cleaning.rb +136 -0
  35. data/lib/ndr_support/string/conversions.rb +137 -0
  36. data/lib/ndr_support/tasks.rb +1 -0
  37. data/lib/ndr_support/time/conversions.rb +13 -0
  38. data/lib/ndr_support/utf8_encoding.rb +72 -0
  39. data/lib/ndr_support/utf8_encoding/control_characters.rb +53 -0
  40. data/lib/ndr_support/utf8_encoding/force_binary.rb +44 -0
  41. data/lib/ndr_support/utf8_encoding/object_support.rb +31 -0
  42. data/lib/ndr_support/version.rb +5 -0
  43. data/lib/ndr_support/yaml/serialization_migration.rb +65 -0
  44. data/lib/tasks/audit_code.rake +423 -0
  45. data/ndr_support.gemspec +39 -0
  46. data/test/array_test.rb +20 -0
  47. data/test/concerns/working_days_test.rb +122 -0
  48. data/test/daterange_test.rb +194 -0
  49. data/test/fixnum/calculations_test.rb +28 -0
  50. data/test/hash_test.rb +84 -0
  51. data/test/integer_test.rb +14 -0
  52. data/test/nil_test.rb +40 -0
  53. data/test/ourdate_test.rb +27 -0
  54. data/test/ourtime_test.rb +27 -0
  55. data/test/regexp_range_test.rb +135 -0
  56. data/test/resources/filesystem_paths.yml +37 -0
  57. data/test/safe_file_test.rb +597 -0
  58. data/test/safe_path_test.rb +168 -0
  59. data/test/string/cleaning_test.rb +176 -0
  60. data/test/string/conversions_test.rb +353 -0
  61. data/test/test_helper.rb +41 -0
  62. data/test/time/conversions_test.rb +15 -0
  63. data/test/utf8_encoding/control_characters_test.rb +84 -0
  64. data/test/utf8_encoding/force_binary_test.rb +64 -0
  65. data/test/utf8_encoding_test.rb +170 -0
  66. data/test/yaml/serialization_test.rb +145 -0
  67. metadata +295 -0
@@ -0,0 +1,31 @@
1
+ require 'ndr_support/utf8_encoding'
2
+
3
+ # Allows any object (if supported) to have all related
4
+ # strings encoded in place to UTF-8.
5
+ module UTF8Encoding
6
+ module ObjectSupport
7
+ # Recursively ensure the correct encoding is being used:
8
+ def ensure_utf8_object!(object)
9
+ case object
10
+ when String
11
+ ensure_utf8!(object)
12
+ when Hash
13
+ ensure_utf8_hash!(object)
14
+ when Array
15
+ ensure_utf8_array!(object)
16
+ else
17
+ object
18
+ end
19
+ end
20
+
21
+ # Ensures all values of the given `hash` are UTF-8, where possible.
22
+ def ensure_utf8_hash!(hash)
23
+ hash.each_value { |value| ensure_utf8_object!(value) }
24
+ end
25
+
26
+ # Ensures all elements of the given `array` are UTF-8, where possible.
27
+ def ensure_utf8_array!(array)
28
+ array.each { |element| ensure_utf8_object!(element) }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ # This defines the NdrSupport version. If you change it, rebuild and commit the gem.
2
+ # Use "rake build" to build the gem, see rake -T for all bundler rake tasks (and our own).
3
+ module NdrSupport
4
+ VERSION = '3.1.1'
5
+ end
@@ -0,0 +1,65 @@
1
+ require 'yaml'
2
+ require 'ndr_support/utf8_encoding'
3
+
4
+ module NdrSupport
5
+ module YAML
6
+ # Lightweight wrapper around YAML serialization, to provide any
7
+ # necessary support for YAML engines and string encodings.
8
+ module SerializationMigration
9
+ include UTF8Encoding
10
+
11
+ # Wrapper around: YAML.load(string)
12
+ def load_yaml(string, coerce_invalid_chars = false)
13
+ fix_encoding!(string, coerce_invalid_chars)
14
+
15
+ # Achieve same behaviour using `syck` and `psych`:
16
+ handle_special_characters!(string, coerce_invalid_chars)
17
+ fix_encoding!(string, coerce_invalid_chars)
18
+
19
+ object = Psych.load(string)
20
+
21
+ # Ensure that any string related to the object
22
+ # we've loaded is also valid UTF-8.
23
+ ensure_utf8_object!(object)
24
+
25
+ # We escape all non-printing control chars:
26
+ escape_control_chars_in_object!(object)
27
+ end
28
+
29
+ # Wrapper around: YAML.dump(object)
30
+ def dump_yaml(object)
31
+ # Psych produces UTF-8 encoded output; we'd rather
32
+ # have YAML that can be safely stored in stores with
33
+ # other encodings. If #load_yaml is used, the binary
34
+ # encoding of the object will be reversed on load.
35
+ Psych.dump binary_encode_any_high_ascii(object)
36
+ end
37
+
38
+ private
39
+
40
+ # Makes `string` valid UTF-8. If `coerce` is true,
41
+ # any invalid characters will be escaped - if false,
42
+ # they will trigger an UTF8Encoding::UTF8CoercionError.
43
+ def fix_encoding!(string, coerce)
44
+ coerce ? coerce_utf8!(string) : ensure_utf8!(string)
45
+ end
46
+
47
+ # Within double quotes, YAML allows special characters.
48
+ # While `psych` emits UTF-8 YAML, `syck` double escapes
49
+ # higher characters. We need to unescape any we find:
50
+ def handle_special_characters!(string, coerce_invalid_chars)
51
+ # Replace any encoded hex chars with their actual value:
52
+ string.gsub!(/((?:\\x[0-9A-F]{2})+)/) do
53
+ byte_sequence = $1.scan(/[0-9A-F]{2}/)
54
+ byte_sequence.pack('H2' * byte_sequence.length).tap do |sequence|
55
+ fix_encoding!(sequence, coerce_invalid_chars)
56
+ end
57
+ end
58
+
59
+ # Re-escape any non-printing control characters,
60
+ # as they can break the YAML parser:
61
+ escape_control_chars_in_object!(string)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,423 @@
1
+ SAFETY_FILE =
2
+ if defined?(Rails)
3
+ Rails.root.join('config', 'code_safety.yml')
4
+ else
5
+ 'code_safety.yml'
6
+ end
7
+
8
+ # Temporary overrides to only audit external access files
9
+ SAFETY_REPOS = [['/svn/era', '/svn/extra/era/external-access']]
10
+
11
+ require 'yaml'
12
+
13
+
14
+ # Parameter max_print is number of entries to print before truncating output
15
+ # (negative value => print all)
16
+ def audit_code_safety(max_print = 20, ignore_new = false, show_diffs = false, show_in_priority = false, user_name = 'usr')
17
+ puts 'Running source code safety audit script.'
18
+ puts
19
+
20
+ max_print = 1_000_000 if max_print < 0
21
+ safety_cfg = File.exist?(SAFETY_FILE) ? YAML.load_file(SAFETY_FILE) : {}
22
+ file_safety = safety_cfg["file safety"]
23
+ if file_safety.nil?
24
+ puts "Creating new 'file safety' block in #{SAFETY_FILE}"
25
+ safety_cfg["file safety"] = file_safety = {}
26
+ end
27
+ file_safety.each do |k, v|
28
+ rev = v['safe_revision']
29
+ v['safe_revision'] = rev.to_s if rev.is_a?(Integer)
30
+ end
31
+ orig_count = file_safety.size
32
+
33
+ safety_repo = trunk_repo = get_trunk_repo
34
+
35
+ # TODO: below is broken for git-svn
36
+ # Is it needed?
37
+
38
+ SAFETY_REPOS.each{|suffix, alt|
39
+ # Temporarily override to only audit a different file list
40
+ if safety_repo.end_with?(suffix)
41
+ safety_repo = safety_repo[0...-suffix.length]+alt
42
+ break
43
+ end
44
+ }
45
+
46
+ if ignore_new
47
+ puts "Not checking for new files in #{safety_repo}"
48
+ else
49
+ puts "Checking for new files in #{safety_repo}"
50
+ new_files = get_new_files(safety_repo)
51
+ # Ignore subdirectories, and exclude code_safety.yml by default.
52
+ new_files.delete_if{|f| f =~ /[\/\\]$/ || f == SAFETY_FILE}
53
+ new_files.each{|f|
54
+ unless file_safety.has_key?(f)
55
+ file_safety[f] = {
56
+ "comments" => nil,
57
+ "reviewed_by" => nil,
58
+ "safe_revision" => nil }
59
+ end
60
+ }
61
+ File.open(SAFETY_FILE, 'w') do |file|
62
+ # Consistent file diffs, as ruby preserves Hash insertion order since v1.9
63
+ safety_cfg["file safety"] = Hash[file_safety.sort]
64
+ YAML.dump(safety_cfg, file) # Save changes before checking latest revisions
65
+ end
66
+ end
67
+ puts "Updating latest revisions for #{file_safety.size} files"
68
+ set_last_changed_revision(trunk_repo, file_safety, file_safety.keys)
69
+ puts "\nSummary:"
70
+ puts "Number of files originally in #{SAFETY_FILE}: #{orig_count}"
71
+ puts "Number of new files added: #{file_safety.size - orig_count}"
72
+
73
+ # Now generate statistics:
74
+ unknown = file_safety.values.select{|x| x["safe_revision"].nil?}
75
+ unsafe = file_safety.values.select{|x|
76
+ !x["safe_revision"].nil? && x["safe_revision"] != -1 &&
77
+ x["last_changed_rev"] != x['safe_revision'] &&
78
+ !(x["last_changed_rev"] =~ /^[0-9]+$/ && x['safe_revision'] =~ /^[0-9]+$/ &&
79
+ x["last_changed_rev"].to_i < x['safe_revision'].to_i)
80
+ }
81
+ puts "Number of files with no safe version: #{unknown.size}"
82
+ puts "Number of files which are no longer safe: #{unsafe.size}"
83
+ puts
84
+ printed = []
85
+ # We also print a third category: ones which are no longer in the repository
86
+ if show_in_priority
87
+ file_list = file_safety.sort_by {|k,v| v.nil? ? -100 : v["last_changed_rev"].to_i}.map(&:first)
88
+ else
89
+ file_list = file_safety.keys.sort
90
+ end
91
+
92
+ file_list.each{|f|
93
+ if print_file_safety(file_safety, trunk_repo, f, false, printed.size >= max_print)
94
+ printed << f
95
+ end
96
+ }
97
+ puts "... and #{printed.size - max_print} others" if printed.size > max_print
98
+ if show_diffs
99
+ puts
100
+ printed.each{|f|
101
+ print_file_diffs(file_safety, trunk_repo, f, user_name)
102
+ }
103
+ end
104
+ end
105
+
106
+ # Print summary details of a file's known safety
107
+ # If not verbose, only prints details for unsafe files
108
+ # Returns true if anything printed (or would have been printed if silent),
109
+ # or false otherwise.
110
+ def print_file_safety(file_safety, repo, fname, verbose = false, silent = false)
111
+ msg = "#{fname}\n "
112
+ entry = file_safety[fname]
113
+ msg += "File not in audit list" if entry.nil?
114
+
115
+ if entry["safe_revision"].nil?
116
+ msg += "No safe revision known"
117
+ msg += ", last changed #{entry['last_changed_rev']}" unless entry["last_changed_rev"].nil?
118
+ else
119
+ repolatest = entry["last_changed_rev"] # May have been prepopulated en mass
120
+ msg += 'Not in repository: ' if entry['last_changed_rev'] == -1
121
+ if (repolatest != entry['safe_revision']) &&
122
+ !(repolatest =~ /^[0-9]+$/ && entry['safe_revision'] =~ /^[0-9]+$/ &&
123
+ repolatest.to_i < entry['safe_revision'].to_i)
124
+ # (Allow later revisions to be treated as safe for svn)
125
+ msg += "No longer safe since revision #{repolatest}: "
126
+ else
127
+ return false unless verbose
128
+ msg += "Safe: "
129
+ end
130
+ msg += "revision #{entry['safe_revision']} reviewed by #{entry['reviewed_by']}"
131
+ end
132
+ msg += "\n Comments: #{entry['comments']}" if entry['comments']
133
+ puts msg unless silent
134
+ return true
135
+ end
136
+
137
+ def flag_file_as_safe(release, reviewed_by, comments, f)
138
+ safety_cfg = YAML.load_file(SAFETY_FILE)
139
+ file_safety = safety_cfg["file safety"]
140
+
141
+ unless File.exist?(f)
142
+ abort("Error: Unable to flag non-existent file as safe: #{f}")
143
+ end
144
+ unless file_safety.has_key?(f)
145
+ file_safety[f] = {
146
+ "comments" => nil,
147
+ "reviewed_by" => :dummy, # dummy value, will be overwritten
148
+ "safe_revision" => nil }
149
+ end
150
+ entry = file_safety[f]
151
+ entry_orig = entry.dup
152
+ if comments.to_s.length > 0 && entry["comments"] != comments
153
+ entry["comments"] = if entry["comments"].to_s.empty?
154
+ comments
155
+ else
156
+ "#{entry["comments"]}#{'.' unless entry["comments"].end_with?('.')} Revision #{release}: #{comments}"
157
+ end
158
+ end
159
+ if entry["safe_revision"]
160
+ unless release
161
+ abort("Error: File already has safe revision #{entry["safe_revision"]}: #{f}")
162
+ end
163
+ if release.is_a?(Integer) && release < entry["safe_revision"]
164
+ puts("Warning: Rolling back safe revision from #{entry['safe_revision']} to #{release} for #{f}")
165
+ end
166
+ end
167
+ entry["safe_revision"] = release
168
+ entry["reviewed_by"] = reviewed_by
169
+ if entry == entry_orig
170
+ puts "No changes when updating safe_revision to #{release || '[none]'} for #{f}"
171
+ else
172
+ File.open(SAFETY_FILE, 'w') do |file|
173
+ # Consistent file diffs, as ruby preserves Hash insertion order since v1.9
174
+ safety_cfg["file safety"] = Hash[file_safety.sort]
175
+ YAML.dump(safety_cfg, file) # Save changes before checking latest revisions
176
+ end
177
+ puts "Updated safe_revision to #{release || '[none]'} for #{f}"
178
+ end
179
+ end
180
+
181
+ # Determine the type of repository
182
+ def repository_type
183
+ return 'svn' if Dir.exists?('.svn')
184
+ return 'git-svn' if Dir.exists?('.git') && open('.git/config').grep(/svn/).any?
185
+ return 'git' if Dir.exists?('.git') && open('.git/config').grep(/git/).any?
186
+ return 'not known'
187
+ end
188
+
189
+ def get_trunk_repo
190
+ case repository_type
191
+ when 'svn'
192
+ repo_info = %x[svn info]
193
+ puts 'svn case'
194
+ return repo_info.split("\n").select{|x| x=~ /^URL: /}.collect{|x| x[5..-1]}.first
195
+ when 'git-svn'
196
+ puts 'git-svn case'
197
+ repo_info = %x[git svn info]
198
+ return repo_info.split("\n").select{|x| x =~ /^URL: /}.collect{|x| x[5..-1]}.first
199
+ when 'git'
200
+ puts 'git case'
201
+ repo_info = %x[git remote -v]
202
+ return repo_info.split("\n").first[7..-9]
203
+ else
204
+ return 'Information not available. Unknown repository type'
205
+ end
206
+ end
207
+
208
+ def get_new_files(safety_repo)
209
+ case repository_type
210
+ when 'svn', 'git-svn'
211
+ %x[svn ls -R "#{safety_repo}"].split("\n")
212
+ when 'git'
213
+ #%x[git ls-files --modified].split("\n")
214
+ %x[git ls-files].split("\n")
215
+
216
+ # TODO: Below is for remote repository - for testing use local files
217
+ #new_files = %x[git ls-files --modified #{safety_repo}].split("\n")
218
+ # TODO: Do we need the --modified option?
219
+ #new_files = %x[git ls-files --modified].split("\n")
220
+ else
221
+ []
222
+ end
223
+ end
224
+
225
+ # Fill in the latest changed revisions in a file safety map.
226
+ # (Don't write this data to the YAML file, as it is intrinsic to the SVN
227
+ # repository.)
228
+ def set_last_changed_revision(repo, file_safety, fnames)
229
+ dot_freq = (file_safety.size / 40.0).ceil # Print up to 40 progress dots
230
+ case repository_type
231
+ when 'git'
232
+ fnames = file_safety.keys if fnames.nil?
233
+
234
+ fnames.each_with_index do |f, i|
235
+ info = %x[git log -n 1 #{f}].split("\n").first[7..-1]
236
+ if info.nil? || info.empty?
237
+ file_safety[f]["last_changed_rev"] = -1
238
+ else
239
+ file_safety[f]["last_changed_rev"] = info
240
+ end
241
+ # Show progress
242
+ print '.' if (i % dot_freq) == 0
243
+ end
244
+ puts
245
+ when 'git-svn', 'svn'
246
+ fnames = file_safety.keys if fnames.nil?
247
+
248
+ fnames.each_with_index do |f, i|
249
+ last_revision = get_last_changed_revision(repo, f)
250
+ if last_revision.nil? || last_revision.empty?
251
+ file_safety[f]['last_changed_rev'] = -1
252
+ else
253
+ file_safety[f]['last_changed_rev'] = last_revision
254
+ end
255
+ # Show progress
256
+ print '.' if (i % dot_freq) == 0
257
+ end
258
+ puts
259
+ # NOTE: Do we need the following for retries?
260
+ # if retries && result.size != fnames.size && fnames.size > 1
261
+ # # At least one invalid (deleted file --> subsequent arguments ignored)
262
+ # # Try each file individually
263
+ # # (It would probably be safe to continue from the extra_info.size argument)
264
+ # puts "Retrying (got #{result.size}, expected #{fnames.size})" if debug >= 2
265
+ # result = []
266
+ # fnames.each{ |f|
267
+ # result += svn_info_entries([f], repo, false, debug)
268
+ # }
269
+ # end
270
+ end
271
+ end
272
+
273
+ # Return the last changed revision
274
+ def get_last_changed_revision(repo, fname)
275
+ case repository_type
276
+ when 'git'
277
+ %x[git log -n 1 "#{fname}"].split("\n").first[7..-1]
278
+ when 'git-svn', 'svn'
279
+ begin
280
+ svn_info = %x[svn info -r head "#{repo}/#{fname}"]
281
+ rescue
282
+ puts "we have an error in the svn info line"
283
+ end
284
+ begin
285
+ svn_info.match('Last Changed Rev: ([0-9]*)').to_a[1]
286
+ rescue
287
+ puts "We have an error in getting the revision"
288
+ end
289
+ end
290
+ end
291
+
292
+ # Get mime type. Note that Git does not have this information
293
+ def get_mime_type(repo, fname)
294
+ case repository_type
295
+ when 'git'
296
+ 'Git does not provide mime types'
297
+ when 'git-svn', 'svn'
298
+ %x[svn propget svn:mime-type "#{repo}/#{fname}"].chomp
299
+ end
300
+ end
301
+
302
+ # # Print file diffs, for code review
303
+ def print_file_diffs(file_safety, repo, fname, user_name)
304
+ entry = file_safety[fname]
305
+ repolatest = entry['last_changed_rev']
306
+ safe_revision = entry['safe_revision']
307
+
308
+ if safe_revision.nil?
309
+ first_revision = set_safe_revision
310
+ print_repo_file_diffs(repolatest, repo, fname, user_name, first_revision)
311
+ else
312
+
313
+ rev = get_last_changed_revision(repo, fname)
314
+ if rev
315
+ mime = get_mime_type(repo, fname)
316
+ end
317
+
318
+ print_repo_file_diffs(repolatest, repo, fname, user_name, safe_revision) if repolatest != safe_revision
319
+ end
320
+ end
321
+
322
+ # Returns first commit for git and 0 for svn in order to be used to display
323
+ # new files. Called from print_file_diffs
324
+ def set_safe_revision
325
+ case repository_type
326
+ when 'git'
327
+ %x[git rev-list --max-parents=0 HEAD].chomp
328
+ when 'git-svn', 'svn'
329
+ 0
330
+ end
331
+ end
332
+
333
+ def print_repo_file_diffs(repolatest, repo, fname, user_name, safe_revision)
334
+ case repository_type
335
+ when 'git'
336
+ puts %[git --no-pager diff -b #{safe_revision}..#{repolatest} #{fname}]
337
+ puts %x[git --no-pager diff -b #{safe_revision}..#{repolatest} #{fname}]
338
+ when 'git-svn', 'svn'
339
+ puts %[svn diff -r #{safe_revision.to_i}:#{repolatest.to_i} -x -b #{repo}/#{fname}]
340
+ puts %x[svn diff -r #{safe_revision.to_i}:#{repolatest.to_i} -x -b #{repo}/#{fname}]
341
+ else
342
+ puts 'Unknown repo'
343
+ end
344
+
345
+ puts %(To flag the changes to this file as safe, run:)
346
+ puts %( rake audit:safe release=#{repolatest} file=#{fname} reviewed_by=#{user_name} comments="")
347
+ puts
348
+ end
349
+
350
+ def release_valid?
351
+ case repository_type
352
+ when 'svn', 'git-svn'
353
+ ENV['release'] =~ /\A[0-9][0-9]*\Z/
354
+ when 'git'
355
+ ENV['release'] =~ /\A[0-9a-f]{40}\Z/
356
+ else
357
+ false
358
+ end
359
+ end
360
+
361
+ def get_release
362
+ release = ENV['release']
363
+ release = nil if release == '0'
364
+ case repository_type
365
+ when 'svn', 'git-svn'
366
+ release.to_i
367
+ when 'git'
368
+ release
369
+ else
370
+ ''
371
+ end
372
+ return release
373
+ end
374
+
375
+ namespace :audit do
376
+
377
+ desc "Audit safety of source code.
378
+ Usage: audit:code [max_print=n] [ignore_new=false|true] [show_diffs=false|true] [reviewed_by=usr]
379
+
380
+ File #{SAFETY_FILE} lists the safety and revision information
381
+ of the era source code. This task updates the list, and [TODO] warns about
382
+ files which have changed since they were last verified as safe."
383
+ task(:code) do
384
+ puts "Usage: audit:code [max_print=n] [ignore_new=false|true] [show_diffs=false|true] [show_in_priority=false|true] [reviewed_by=usr]"
385
+ puts "This is a #{repository_type} repository"
386
+
387
+ ignore_new = (ENV['ignore_new'].to_s =~ /\Atrue\Z/i)
388
+ show_diffs = (ENV['show_diffs'].to_s =~ /\Atrue\Z/i)
389
+ show_in_priority = (ENV['show_in_priority'].to_s =~ /\Atrue\Z/i)
390
+ max_print = ENV['max_print'] =~ /\A-?[0-9][0-9]*\Z/ ? ENV['max_print'].to_i : 20
391
+
392
+ audit_code_safety(max_print, ignore_new, show_diffs, show_in_priority, ENV['reviewed_by'])
393
+
394
+ unless show_diffs
395
+ puts "To show file diffs, run: rake audit:code max_print=-1 show_diffs=true"
396
+ end
397
+ end
398
+
399
+ desc "Flag a source file as safe.
400
+
401
+ Usage:
402
+ Flag as safe: rake audit:safe release=revision reviewed_by=usr [comments=...] file=f
403
+ Needs review: rake audit:safe release=0 [comments=...] file=f"
404
+ task (:safe) do
405
+ required_fields = ["release", "file"]
406
+ required_fields << "reviewed_by" unless ENV['release'] == '0'
407
+ missing = required_fields.collect{|f| (f if ENV[f].to_s.empty? || (f=='reviewed_by' && ENV[f] == 'usr'))}.compact # Avoid accidental missing username
408
+ if !missing.empty?
409
+ puts "Usage: rake audit:safe release=revision reviewed_by=usr [comments=...] file=f"
410
+ puts "or, to flag a file for review: rake audit:safe release=0 [comments=...] file=f"
411
+ abort("Error: Missing required argument(s): #{missing.join(', ')}")
412
+ end
413
+
414
+ unless release_valid?
415
+ puts "Usage: rake audit:safe release=revision reviewed_by=usr [comments=...] file=f"
416
+ puts "or, to flag a file for review: rake audit:safe release=0 [comments=...] file=f"
417
+ abort("Error: Invalid release: #{ENV['release']}")
418
+ end
419
+
420
+ release = get_release
421
+ flag_file_as_safe(release, ENV['reviewed_by'], ENV['comments'], ENV['file'])
422
+ end
423
+ end