quality_extensions 1.1.6 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +4 -0
  3. data/License +403 -0
  4. data/Rakefile +2 -0
  5. data/Readme.rdoc +2 -3
  6. data/VERSION +1 -0
  7. data/doc_include/ReleaseNotes-0.0.10 +1 -0
  8. data/doc_include/ReleaseNotes-0.0.12 +1 -0
  9. data/doc_include/ReleaseNotes-0.0.13 +2 -0
  10. data/doc_include/ReleaseNotes-0.0.14 +1 -0
  11. data/doc_include/ReleaseNotes-0.0.15 +2 -0
  12. data/doc_include/ReleaseNotes-0.0.16 +1 -0
  13. data/doc_include/ReleaseNotes-0.0.17 +1 -0
  14. data/doc_include/ReleaseNotes-0.0.18 +1 -0
  15. data/doc_include/ReleaseNotes-0.0.19 +1 -0
  16. data/doc_include/ReleaseNotes-0.0.20 +1 -0
  17. data/doc_include/ReleaseNotes-0.0.21 +7 -0
  18. data/doc_include/ReleaseNotes-0.0.22 +2 -0
  19. data/doc_include/ReleaseNotes-0.0.23 +1 -0
  20. data/doc_include/ReleaseNotes-0.0.24 +3 -0
  21. data/doc_include/ReleaseNotes-0.0.25 +3 -0
  22. data/doc_include/ReleaseNotes-0.0.26 +2 -0
  23. data/doc_include/ReleaseNotes-0.0.27 +1 -0
  24. data/doc_include/ReleaseNotes-0.0.28 +1 -0
  25. data/doc_include/ReleaseNotes-0.0.29 +3 -0
  26. data/doc_include/ReleaseNotes-0.0.3 +1 -0
  27. data/doc_include/ReleaseNotes-0.0.30 +4 -0
  28. data/doc_include/ReleaseNotes-0.0.31 +1 -0
  29. data/doc_include/ReleaseNotes-0.0.32 +1 -0
  30. data/doc_include/ReleaseNotes-0.0.33 +1 -0
  31. data/doc_include/ReleaseNotes-0.0.34 +2 -0
  32. data/doc_include/ReleaseNotes-0.0.35 +1 -0
  33. data/doc_include/ReleaseNotes-0.0.36 +1 -0
  34. data/doc_include/ReleaseNotes-0.0.37 +1 -0
  35. data/doc_include/ReleaseNotes-0.0.38 +1 -0
  36. data/doc_include/ReleaseNotes-0.0.39 +1 -0
  37. data/doc_include/ReleaseNotes-0.0.4 +2 -0
  38. data/doc_include/ReleaseNotes-0.0.40 +1 -0
  39. data/doc_include/ReleaseNotes-0.0.41 +1 -0
  40. data/doc_include/ReleaseNotes-0.0.42 +2 -0
  41. data/doc_include/ReleaseNotes-0.0.43 +2 -0
  42. data/doc_include/ReleaseNotes-0.0.44 +3 -0
  43. data/doc_include/ReleaseNotes-0.0.45 +1 -0
  44. data/doc_include/ReleaseNotes-0.0.46 +1 -0
  45. data/doc_include/ReleaseNotes-0.0.47 +1 -0
  46. data/doc_include/ReleaseNotes-0.0.48 +2 -0
  47. data/doc_include/ReleaseNotes-0.0.49 +2 -0
  48. data/doc_include/ReleaseNotes-0.0.5 +1 -0
  49. data/doc_include/ReleaseNotes-0.0.50 +1 -0
  50. data/doc_include/ReleaseNotes-0.0.51 +1 -0
  51. data/doc_include/ReleaseNotes-0.0.52 +1 -0
  52. data/doc_include/ReleaseNotes-0.0.53 +3 -0
  53. data/doc_include/ReleaseNotes-0.0.54 +1 -0
  54. data/doc_include/ReleaseNotes-0.0.55 +2 -0
  55. data/doc_include/ReleaseNotes-0.0.56 +1 -0
  56. data/doc_include/ReleaseNotes-0.0.57 +3 -0
  57. data/doc_include/ReleaseNotes-0.0.58 +1 -0
  58. data/doc_include/ReleaseNotes-0.0.59 +1 -0
  59. data/doc_include/ReleaseNotes-0.0.6 +3 -0
  60. data/doc_include/ReleaseNotes-0.0.60 +3 -0
  61. data/doc_include/ReleaseNotes-0.0.61 +1 -0
  62. data/doc_include/ReleaseNotes-0.0.62 +1 -0
  63. data/doc_include/ReleaseNotes-0.0.63 +1 -0
  64. data/doc_include/ReleaseNotes-0.0.64 +1 -0
  65. data/doc_include/ReleaseNotes-0.0.7 +1 -0
  66. data/doc_include/ReleaseNotes-0.0.8 +2 -0
  67. data/doc_include/ReleaseNotes-0.0.9 +1 -0
  68. data/doc_include/ReleaseNotes-0.1.1 +1 -0
  69. data/doc_include/ReleaseNotes-0.1.2 +1 -0
  70. data/doc_include/ReleaseNotes-0.1.3 +1 -0
  71. data/doc_include/ReleaseNotes-0.1.4 +1 -0
  72. data/doc_include/ReleaseNotes-0.1.5 +0 -0
  73. data/doc_include/ReleaseNotes-1.0.0 +1 -0
  74. data/doc_include/ReleaseNotes-1.0.1 +1 -0
  75. data/doc_include/ReleaseNotes-1.0.2 +1 -0
  76. data/doc_include/ReleaseNotes-1.0.3 +1 -0
  77. data/doc_include/ReleaseNotes-1.1.0 +1 -0
  78. data/doc_include/ReleaseNotes-1.1.1 +1 -0
  79. data/doc_include/ReleaseNotes-1.1.6 +1 -0
  80. data/doc_include/assert_equal_with_difference_highlighting-there_he_is.png +0 -0
  81. data/doc_include/assert_equal_with_difference_highlighting-wheres_waldo.png +0 -0
  82. data/escape/Rakefile +62 -0
  83. data/escape/Readme +16 -0
  84. data/escape/doc_include/ReleaseNotes-0.0.1 +1 -0
  85. data/escape/doc_include/ReleaseNotes-0.0.3 +1 -0
  86. data/escape/doc_include/ReleaseNotes-0.0.4 +1 -0
  87. data/escape/doc_include/ReleaseNotes-0.0.5 +1 -0
  88. data/escape/doc_include/template/qualitysmith.rb +631 -0
  89. data/escape/lib/escape.rb +302 -0
  90. data/escape/pkg/escape-0.0.5.tgz +0 -0
  91. data/escape/pkg/escape-0.0.5.zip +0 -0
  92. data/escape/pkg/escape-0.0.5/Readme +16 -0
  93. data/escape/pkg/escape-0.0.5/doc_include/template/qualitysmith.rb +631 -0
  94. data/escape/pkg/escape-0.0.5/lib/escape.rb +302 -0
  95. data/lib/quality_extensions.rb +3 -1
  96. data/lib/quality_extensions/array/average.rb +45 -0
  97. data/lib/quality_extensions/array/expand_ranges.rb +2 -2
  98. data/lib/quality_extensions/array/shell_escape.rb +2 -2
  99. data/lib/quality_extensions/color/gradiate.rb +5 -5
  100. data/lib/quality_extensions/enumerable/every.rb +2 -2
  101. data/lib/quality_extensions/enumerable/grep_with_index.rb +51 -0
  102. data/lib/quality_extensions/enumerable/grepv.rb +1 -0
  103. data/lib/quality_extensions/enumerable/map_with_index.rb +2 -0
  104. data/lib/quality_extensions/enumerable/max_by_value.rb +72 -0
  105. data/lib/quality_extensions/enumerable/select_while.rb +7 -2
  106. data/lib/quality_extensions/enumerable/select_with_index.rb +2 -2
  107. data/lib/quality_extensions/float/truncate.rb +2 -1
  108. data/lib/quality_extensions/hash/except.rb +2 -2
  109. data/lib/quality_extensions/hash/to_query_string.rb +6 -6
  110. data/lib/quality_extensions/helpers/numbers.rb +259 -0
  111. data/lib/quality_extensions/kernel/die.rb +2 -2
  112. data/lib/quality_extensions/kernel/filter_output.rb +1 -0
  113. data/lib/quality_extensions/kernel/require_all.rb +7 -7
  114. data/lib/quality_extensions/module/guard_method.rb +3 -3
  115. data/lib/quality_extensions/object/non.rb +10 -4
  116. data/lib/quality_extensions/pathname.rb +51 -3
  117. data/lib/quality_extensions/regexp/named_captures.rb +7 -6
  118. data/lib/quality_extensions/regexp/to_plain_s.rb +116 -0
  119. data/lib/quality_extensions/safe_method.rb +100 -0
  120. data/lib/quality_extensions/safe_nil.rb +87 -11
  121. data/lib/quality_extensions/string/chomped_lines.rb +0 -0
  122. data/lib/quality_extensions/string/integer_eh.rb +2 -2
  123. data/lib/quality_extensions/string/prefix.rb +120 -0
  124. data/lib/quality_extensions/string/prefix_lines.rb +4 -69
  125. data/lib/quality_extensions/string/safe_numeric_conversion.rb +5 -5
  126. data/lib/quality_extensions/string/shell_escape.rb +3 -3
  127. data/lib/quality_extensions/string/to_proc.rb +119 -0
  128. data/lib/quality_extensions/template.rb +2 -2
  129. data/lib/quality_extensions/test/difference_highlighting.rb +2 -2
  130. data/lib/quality_extensions/version.rb +3 -0
  131. data/quality_extensions.gemspec +21 -0
  132. metadata +249 -145
  133. data/lib/Xfind_bug_test.rb +0 -28
@@ -4,6 +4,7 @@
4
4
  # License:: Ruby License
5
5
  # Submit to Facets?:: Yes.
6
6
  # Developer notes:
7
+ # To do: write an add_prefix_to_output method based on this that prefixes all lines output with some string, to help group them together when looking through a long log file
7
8
  #++
8
9
 
9
10
  require 'stringio'
@@ -9,8 +9,8 @@ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
9
9
  require 'rubygems'
10
10
  require 'facets/filelist'
11
11
 
12
- require 'facets/kernel/require_local'
13
- require_local '../file/exact_match_regexp'
12
+ require 'facets/kernel/require_relative'
13
+ require_relative '../file/exact_match_regexp'
14
14
 
15
15
  module Kernel
16
16
 
@@ -60,7 +60,7 @@ module Kernel
60
60
  #
61
61
  # All of the +options+ available for +require_all+ are still available here.
62
62
  #
63
- def require_local_all(dir = './', options = {})
63
+ def require_relative_all(dir = './', options = {})
64
64
  raise ArgumentError.new("dir must be a String") unless dir.is_a?(String)
65
65
  local_dir = File.dirname( caller[0] )
66
66
  require_all(
@@ -86,7 +86,7 @@ require 'English'
86
86
  class TheTest < Test::Unit::TestCase
87
87
  def setup
88
88
  @base_dir = "#{Dir.tmpdir}/require_all_test"
89
- @base_local_dir = File.dirname(__FILE__) # To allow testing of require_local_all. But tests should put everything in "#{@base_local_dir}/require_all_test" to avoid clutter or name conflicts with other files!
89
+ @base_local_dir = File.dirname(__FILE__) # To allow testing of require_relative_all. But tests should put everything in "#{@base_local_dir}/require_all_test" to avoid clutter or name conflicts with other files!
90
90
  FileUtils.mkdir @base_dir
91
91
  @deep_dir = "really/really/deep/subdir"
92
92
  $loaded = []
@@ -137,11 +137,11 @@ class TheTest < Test::Unit::TestCase
137
137
  assert_equal ['require_me.rb'], $loaded
138
138
  end
139
139
 
140
- def test_require_local_all
140
+ def test_require_relative_all
141
141
  create_ruby_file 'require_all_test/lib/require_me.rb', @base_local_dir
142
142
  create_ruby_file 'require_all_test/lib/please_ignore_me.rb', @base_local_dir
143
143
 
144
- require_local_all 'require_all_test/lib', :exclude => [/ignore/]
144
+ require_relative_all 'require_all_test/lib', :exclude => [/ignore/]
145
145
  assert_equal ['require_all_test/lib/require_me.rb'], $loaded
146
146
  end
147
147
 
@@ -172,7 +172,7 @@ class TheTest < Test::Unit::TestCase
172
172
  #require_all File.dirname(@base_dir), :exclude => [/^all\.rb$/]
173
173
  # This works, but it's too much to expect users to type out!:
174
174
  #require_all File.dirname(@base_dir), :exclude => [/(\/|^)all\.rb$/]
175
-
175
+
176
176
  # So...... I added an :exclude_files option so that people wouldn't have to!
177
177
  require_all File.dirname(@base_dir), :exclude_files => ['all.rb']
178
178
 
@@ -14,8 +14,8 @@
14
14
  $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
15
15
  require 'rubygems'
16
16
  require 'quality_extensions/module/attribute_accessors'
17
- require 'facets/kernel/require_local'
18
- require_local 'bool_attr_accessor'
17
+ require 'facets/kernel/require_relative'
18
+ require_relative 'bool_attr_accessor'
19
19
  #require 'quality_extensions/module/bool_attr_accessor'
20
20
  require 'quality_extensions/symbol/match'
21
21
 
@@ -33,7 +33,7 @@ class Module
33
33
  # # Section of code during which you don't want any stupid stuff to happen
34
34
  # end # Causes @stupid_stuff_disabled to be set back to false
35
35
  # # Okay, a, you can resume doing stupid stuff again...
36
- #
36
+ #
37
37
  # If you want your guard method to *disable* the flag rather than *enable* it, simply pass false to the guard method.
38
38
  #
39
39
  # These calls can be nested however you wish:
@@ -1,12 +1,18 @@
1
+
1
2
  class Object
2
- def nonblank?
3
- !blank?
3
+ def nonnil?
4
+ !nil?
4
5
  end
5
6
  end
6
7
 
7
8
  class Object
8
- def nonnil?
9
- !nil?
9
+ def nonempty?
10
+ !empty?
10
11
  end
11
12
  end
12
13
 
14
+ class Object
15
+ def nonblank?
16
+ !blank?
17
+ end
18
+ end
@@ -4,7 +4,7 @@
4
4
  # Copyright:: Copyright (c) 2009, Tyler Rick
5
5
  # License:: Ruby License
6
6
  # Submit to Facets?::
7
- # Developer notes::
7
+ # Developer notes::
8
8
  # * Make a patch to actual pathname source.
9
9
  # * document all Pathname methods better, copying from docs for delegated class if necessary, but NOT just saying 'See FileTest.file?.' How useless that is to have to keep flipping back and forth referring to like 4 different classes!
10
10
  # History::
@@ -18,6 +18,54 @@
18
18
  #++
19
19
 
20
20
 
21
+
22
+ # TODO: How much of this stuff already exists as private methods??
23
+ #
24
+ # # chop_basename(path) -> [pre-basename, basename] or nil
25
+ # def chop_basename(path)
26
+ # base = File.basename(path)
27
+ # if /\A#{SEPARATOR_PAT}?\z/ =~ base
28
+ # return nil
29
+ # else
30
+ # return path[0, path.rindex(base)], base
31
+ # end
32
+ # end
33
+ # private :chop_basename
34
+ #
35
+ # # split_names(path) -> prefix, [name, ...]
36
+ # def split_names(path)
37
+ # names = []
38
+ # while r = chop_basename(path)
39
+ # path, basename = r
40
+ # names.unshift basename
41
+ # end
42
+ # return path, names
43
+ # end
44
+ # private :split_names
45
+ #
46
+ # def prepend_prefix(prefix, relpath)
47
+ # if relpath.empty?
48
+ # File.dirname(prefix)
49
+ # elsif /#{SEPARATOR_PAT}/ =~ prefix
50
+ # prefix = File.dirname(prefix)
51
+ # prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
52
+ # prefix + relpath
53
+ # else
54
+ # prefix + relpath
55
+ # end
56
+ # end
57
+ # private :prepend_prefix
58
+ #
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
21
69
  require 'pathname'
22
70
  require 'tempfile'
23
71
 
@@ -71,7 +119,7 @@ class Pathname
71
119
  Pathname.new(new_path)
72
120
  end
73
121
  alias_method :move, :mv
74
-
122
+
75
123
  # Copies self to +dest+ using FileUtils.cp.
76
124
  #
77
125
  # See documentation for FileUtils.cp for a list of valid +options+.
@@ -84,7 +132,7 @@ class Pathname
84
132
  end
85
133
  alias_method :copy, :cp
86
134
 
87
- # Copies self to +dest+ using FileUtils.cp_r. If self is a directory, this method copies all its contents recursively. If +dest+ is a directory, copies self to +dest/src+.
135
+ # Copies self to +dest+ using FileUtils.cp_r. If self is a directory, this method copies all its contents recursively. If +dest+ is a directory, copies self to +dest/src+.
88
136
  #
89
137
  # See documentation for FileUtils.cp_r for a list of valid +options+.
90
138
  #
@@ -1,6 +1,7 @@
1
1
  # http://tfletcher.com/lib/named_captures.rb
2
2
  require 'strscan'
3
3
 
4
+ # TODO: require the file that defines this instead
4
5
  class Module
5
6
  # c.f. ActiveSupport
6
7
  def alias_method_chain(target, feature)
@@ -13,7 +14,7 @@ end
13
14
 
14
15
  class MatchData
15
16
  attr_accessor :capture_names
16
-
17
+
17
18
  def method_missing(capture_name, *args, &block)
18
19
  if index = capture_names.index(capture_name)
19
20
  return self[index + 1]
@@ -37,10 +38,10 @@ class Regexp
37
38
  end
38
39
 
39
40
  private
40
-
41
+
41
42
  def extract_capture_names_from(pattern)
42
43
  names, scanner, last_close = [], StringScanner.new(pattern), nil
43
-
44
+
44
45
  while scanner.skip_until(/\(/)
45
46
  next if scanner.pre_match =~ /\\$/
46
47
 
@@ -51,7 +52,7 @@ class Regexp
51
52
  else
52
53
  names << :capture
53
54
  end
54
-
55
+
55
56
  while scanner.skip_until(/[()]/)
56
57
  if scanner.matched =~ /\)$/
57
58
  (names.size - 1).downto(0) do |i|
@@ -66,14 +67,14 @@ class Regexp
66
67
  end
67
68
  end
68
69
  end
69
-
70
+
70
71
  return names
71
72
  end
72
73
  end
73
74
 
74
75
  if __FILE__ == $0 then
75
76
  require 'test/unit'
76
-
77
+
77
78
  class NamedCapturesTest < Test::Unit::TestCase
78
79
  def test_escaped_brackets_are_ignored
79
80
  assert /\(\)\(\)/.capture_names.empty?
@@ -0,0 +1,116 @@
1
+ #--
2
+ # Author:: Tyler Rick
3
+ # Copyright:: Copyright (c) 2009 Tyler Rick
4
+ # License:: Ruby License
5
+ # Submit to Facets?:: Yes.
6
+ # Developer notes::
7
+ #++
8
+
9
+
10
+ class Regexp
11
+ def self.to_plain_s(input, also_unescape = nil)
12
+ input.
13
+ gsub(/((?<!\\)(\\\\)*)\(((?=\?[^):]*:)\?[^):]*:)?/, '\1'). # remove all unescaped '('s and any '?-mix:b-style prefix that their contents begin with
14
+ gsub(/((?<!\\)(\\\\)*)[)?*+\[\]]/, '\1'). # remove all unescaped ')', '?', '*', etc.es
15
+ gsub(/((?<!\\)(\\\\)*)\{.*\}/, '\1'). # remove all unescaped /{.*}/
16
+ gsub(/((?<!\\)(\\\\)*)\\([().\/#{also_unescape}])/, '\1\3'). # unescape any remaining (escaped) '\(', '\)', '\.', etc.es
17
+ gsub(/\\\\/, '\\')
18
+ end
19
+
20
+ # Sometimes you don't want to see a [serialized] version of the regular expression:
21
+ # /a/.to_s => '(?-mix:a)'
22
+ # and you simply want a simple, human-readable version:
23
+ # /a/.to_plain_s => 'a'
24
+ #
25
+ # Since the Regexp object doesn't know what it was delimited with, to_plain_s can't know which
26
+ # delimiter character, if any, it should try to unescape.
27
+ #
28
+ # %r#a\#b#.to_plain_s('#') # => "a#b"
29
+ # %r#a\#b#.to_plain_s # => "a\\#b"
30
+ #
31
+ # An escaped '/' will always be unescaped, since Ruby escapes for you even when you don't:
32
+ # /a\/b/.to_plain_s # => 'a/b'
33
+ # %r#a\/b#.to_plain_s # => 'a/b'
34
+ # %r#a/b#. to_plain_s # => "a/b"
35
+ #
36
+ def to_plain_s(also_unescape = nil)
37
+ Regexp.to_plain_s(self.to_s, also_unescape)
38
+ end
39
+ end
40
+
41
+
42
+ # _____ _
43
+ # |_ _|__ ___| |_
44
+ # | |/ _ \/ __| __|
45
+ # | | __/\__ \ |_
46
+ # |_|\___||___/\__|
47
+ #
48
+ =begin test
49
+ require 'spec/autorun'
50
+
51
+ describe 'Regexp#to_plain_s' do
52
+ it "removes '?-mix:' prefix" do
53
+ /a/.to_plain_s.should == 'a'
54
+ end
55
+
56
+ it "removes unescaped ()" do
57
+ /(a)bc/.to_plain_s.should == 'abc'
58
+ end
59
+
60
+ it "removes unescaped ?, *, +" do
61
+ /a?b*c+/.to_plain_s.should == 'abc'
62
+ end
63
+
64
+ it "removes unescaped /{3}/" do
65
+ /a{3}bc/.to_plain_s.should == 'abc'
66
+ /a{1,3}bc/.to_plain_s.should == 'abc'
67
+ end
68
+
69
+ it "unescapes escaped ()'s correctly" do
70
+ # 1 1 2 1 2
71
+ Regexp.to_plain_s( "(a) \\(b\\) \\\\(c\\\\)" ).should ==
72
+ "a (b) \\c\\"
73
+
74
+ # 1 1 12 12
75
+ /(a) \(b\) \\(c\\)/.to_plain_s.should ==
76
+ "a (b) \\c\\"
77
+
78
+ # 1 1 1 2 1 2 1 2 3 1 2 3 1 2 3 4 1 2 3 4 1 2 3 4 5 1 2 3 4 5
79
+ Regexp.to_plain_s( "(a) \\(b\\) \\\\(c\\\\) \\\\\\(d\\\\\\) \\\\\\\\(e\\\\\\\\) \\\\\\\\\\(f\\\\\\\\\\)" ).should ==
80
+ # 1 1 1 1 1 2 1 2 1 2 1 2
81
+ "a (b) \\c\\ \\(d\\) \\\\e\\\\ \\\\(f\\\\)"
82
+
83
+ # 1 1 12 12 123 123 1234 1234 12345 12345
84
+ /(a) \(b\) \\(c\\) \\\(d\\\) \\\\(e\\\\) \\\\\(f\\\\\)/.to_plain_s.should ==
85
+ # 1 1 1 1 1 2 1 2 1 2 1 2
86
+ "a (b) \\c\\ \\(d\\) \\\\e\\\\ \\\\(f\\\\)"
87
+ end
88
+
89
+ it "unescapes escaped /'s, iff they tell us that they need to be unescaped" do
90
+ /a\/b/.to_plain_s. should == 'a/b'
91
+ %r#a\#b#.to_plain_s. should == "a\\#b"
92
+ %r#a\#b#.to_plain_s('#').should == "a#b"
93
+ %r#a\/b#.to_plain_s. should == 'a/b'
94
+ %r#a/b#. to_plain_s. should == "a/b"
95
+ end
96
+
97
+ it "unescapes escaped .'s" do
98
+ /a\.b/.to_plain_s.should == 'a.b'
99
+ end
100
+
101
+ it 'matches the original regexp' do
102
+ (r=/a/).to_plain_s.should match(r)
103
+ (r=/(a)bc/).to_plain_s.should match(r)
104
+ (r=/a?b*c+/).to_plain_s.should match(r)
105
+ (r=/a{1}bc/).to_plain_s.should match(r)
106
+ (r=/(a) \(b\) \\(c\\)/).to_plain_s.should match(r)
107
+ (r=/(a) \(b\) \\(c\\) \\\(d\\\) \\\\(e\\\\) \\\\\(f\\\\\)/).to_plain_s.should match(r)
108
+
109
+ # Cannot handle the following cases:
110
+ #(r=/a{3}bc/).to_plain_s.should match(r)
111
+ #(r=/a{1,3}bc/).to_plain_s.should match(r)
112
+ end
113
+ end
114
+
115
+ =end
116
+
@@ -0,0 +1,100 @@
1
+ #--
2
+ # Credits::
3
+ # * Tom Locke (http://hobocentral.net/blog/2007/08/25/quiz/) -- original version
4
+ # * coderrr (http://coderrr.wordpress.com/2007/09/15/the-ternary-destroyer/) -- some optimizations
5
+ # * Tyler Rick -- packaged it, added some documention, and added some tests
6
+ # Copyright:: ?
7
+ # License:: ?
8
+ # Submit to Facets?:: Yes
9
+ # Developer notes::
10
+ #++
11
+
12
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
13
+ require 'singleton'
14
+ require 'rubygems'
15
+ require 'builder/blankslate'
16
+ #require 'facets/basicobject'
17
+ #puts Object.methods.include?(:blank_slate_method_added) # not there?
18
+
19
+
20
+ class Object
21
+ # not sure if receiver responds_to?
22
+ def _r?(*args)
23
+ if respond_to(args[0])
24
+ send *args
25
+ else
26
+ nil
27
+ end
28
+ end
29
+ end
30
+
31
+
32
+
33
+ # _____ _
34
+ # |_ _|__ ___| |_
35
+ # | |/ _ \/ __| __|
36
+ # | | __/\__ \ |_
37
+ # |_|\___||___/\__|
38
+ #
39
+ =begin test
40
+ require 'test/unit'
41
+
42
+ class TheTest < Test::Unit::TestCase
43
+ def test_simple__nil
44
+ hash = {}
45
+ assert_equal nil, hash[:a]._?.length
46
+ end
47
+ def test_simple__normal_objects
48
+ hash = {:a => 'abc'}
49
+ assert_equal 3, hash[:a]._?.length
50
+ end
51
+
52
+ def test_chaining
53
+ hash = {}
54
+ assert_equal nil, hash[:a]._?.foo
55
+ assert_equal nil, hash[:a]._?[:b]._?[:c]
56
+ assert_equal nil, hash[:a]._?[:b]._?[:c]._?.some_method
57
+ end
58
+
59
+ def test_inspect
60
+ assert_equal 'SafeNil', nil._?.inspect
61
+ end
62
+
63
+ def test_nil?
64
+ assert SafeNil.instance.nil?
65
+ end
66
+
67
+ def test_does_not_permanently_modify_nil_class
68
+ assert_raise(NoMethodError) { nil.foo }
69
+ nil._?
70
+ assert_raise(NoMethodError) { nil.foo }
71
+ end
72
+ end
73
+ =end
74
+
75
+
76
+
77
+ # Alternative version #1:
78
+ # This version is so nice and simple -- I wish we could use it...
79
+ # The problem with this implementation, however, is that it causes NilClass to be permanently modified with this new method_missing behavior.
80
+ # In other words, once you call _? once on nil, all instances of nil will forever behave the same as nil._? ! (So you may as well just modify NilClass
81
+ # directly.)
82
+ # This might have to do with the fact that nil itself is a “singleton object” (according to http://corelib.rubyonrails.org/classes/NilClass.html)
83
+ # Proof:
84
+ # puts nil.foo rescue p $! # => <NoMethodError: undefined method `foo' for nil:NilClass>
85
+ # nil._?
86
+ # puts nil.foo rescue p $! # => nil
87
+ # Too bad... it was so simple.
88
+ #
89
+ =begin
90
+ class NilClass
91
+ def _?
92
+ n = nil
93
+ def n.method_missing(*args)
94
+ nil
95
+ end
96
+ n
97
+ end
98
+ end
99
+ =end
100
+