pixar-ruby-extensions 1.11.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 (34) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +177 -0
  3. data/README.md +29 -0
  4. data/lib/pixar-ruby-extensions.rb +10 -0
  5. data/lib/pixar_ruby_extensions/array/predicates.rb +30 -0
  6. data/lib/pixar_ruby_extensions/array/utils.rb +88 -0
  7. data/lib/pixar_ruby_extensions/array.rb +17 -0
  8. data/lib/pixar_ruby_extensions/filetest/predicates.rb +38 -0
  9. data/lib/pixar_ruby_extensions/filetest.rb +14 -0
  10. data/lib/pixar_ruby_extensions/float/utils.rb +36 -0
  11. data/lib/pixar_ruby_extensions/float.rb +14 -0
  12. data/lib/pixar_ruby_extensions/hash/utils.rb +94 -0
  13. data/lib/pixar_ruby_extensions/hash.rb +15 -0
  14. data/lib/pixar_ruby_extensions/integer/utils.rb +187 -0
  15. data/lib/pixar_ruby_extensions/integer.rb +14 -0
  16. data/lib/pixar_ruby_extensions/ipaddr/predicates.rb +50 -0
  17. data/lib/pixar_ruby_extensions/ipaddr/utils.rb +77 -0
  18. data/lib/pixar_ruby_extensions/ipaddr.rb +20 -0
  19. data/lib/pixar_ruby_extensions/json/jsonl.rb +50 -0
  20. data/lib/pixar_ruby_extensions/json/utils.rb +78 -0
  21. data/lib/pixar_ruby_extensions/json.rb +17 -0
  22. data/lib/pixar_ruby_extensions/object/predicates.rb +49 -0
  23. data/lib/pixar_ruby_extensions/object.rb +15 -0
  24. data/lib/pixar_ruby_extensions/pathname/predicates.rb +38 -0
  25. data/lib/pixar_ruby_extensions/pathname/utils.rb +139 -0
  26. data/lib/pixar_ruby_extensions/pathname.rb +18 -0
  27. data/lib/pixar_ruby_extensions/string/conversions.rb +113 -0
  28. data/lib/pixar_ruby_extensions/string/predicates.rb +52 -0
  29. data/lib/pixar_ruby_extensions/string.rb +17 -0
  30. data/lib/pixar_ruby_extensions/time/utils.rb +46 -0
  31. data/lib/pixar_ruby_extensions/time.rb +16 -0
  32. data/lib/pixar_ruby_extensions/version.rb +15 -0
  33. data/lib/pixar_ruby_extensions.rb +44 -0
  34. metadata +78 -0
@@ -0,0 +1,187 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ module PixarRubyExtensions
9
+
10
+ module IntegerExtensions
11
+
12
+ module Utils
13
+
14
+ # Array for processing pix_humanize_secs.
15
+ # How many weeks in a year??
16
+ # first, given the Gregorian calendar, how many days in 1000 years?
17
+ # 365000 days if all are common years
18
+ # +250 cuz every 4th year is a leap year
19
+ # - 7 cuz centuries are only leap years if divisble by 400
20
+ # so there are only 3, not 10
21
+ # = 365243
22
+ #
23
+ # and there are 7 days per week, so in 1000 years, a week averages to
24
+ # 365243 / 7
25
+ # = 5217.7571428571426 weeks per 1000 years
26
+ # = 52.177571428571426 weeks per year
27
+
28
+ HUMANIZE_SECS_ARRAY = [
29
+ [60, :seconds, :second],
30
+ [60, :minutes, :minute],
31
+ [24, :hours, :hour],
32
+ [7, :days, :day],
33
+ [52.1776, :weeks, :week],
34
+ [100, :years, :year],
35
+ [10, :centuries, :century],
36
+ [Float::INFINITY, :millenia, :millenium]
37
+ ].freeze
38
+
39
+ # Very handy!
40
+ # Converts an integer number of seconds into a English string, like so:
41
+ #
42
+ # 123456789.humanize_secs
43
+ # # => "3 years 47 weeks 0 days 21 hours 33 minutes 9 seconds"
44
+ #
45
+ # Idea credits to:
46
+ # Mladen Jablanović at
47
+ # http://stackoverflow.com/questions/4136248/how-to-generate-a-human-readable-time-range-using-ruby-on-rails
48
+ #
49
+ # And:
50
+ # Jonathan Simmons at
51
+ # https://gist.github.com/jonathansimmons/24fa43ac6ee53819fb93
52
+ #
53
+ # @param omit_zeros [Boolean] If true, units with a value of zero, like '0 weeks' or
54
+ # '0 hours' will be omitted, so the output of calling this on the 123456789 example above
55
+ # would be "3 years 47 weeks 21 hours 33 minutes 9 seconds". Default is false
56
+ #
57
+ # @param down_to [Symbol] One of :seconds, :minutes, :hours, :days, :weeks, :years,
58
+ # :centuries, :millenia
59
+ # Only include time units down-to and including this one. Default is :seconds
60
+ # NOTE: output is truncated not rounded. In the example here, you lose all info
61
+ # about the 21 hrs, 33 min, and 9 secs, and just see '0 days'
62
+ # @example:
63
+ # 123456789.pix_humanize_secs
64
+ # # => "3 years 47 weeks 0 days 21 hours 33 minutes 9 seconds"
65
+ #
66
+ # 123456789.pix_humanize_secs down_to: :days
67
+ # # => "3 years 47 weeks 0 days"
68
+ #
69
+ # @param up_to [Symbol] One of :seconds, :minutes, :hours, :days, :weeks, :years,
70
+ # :centuries, :millenia
71
+ # Only include time units up-to and including this one. Default is :years.
72
+ # @example:
73
+ # 44567789123.pix_humanize_secs
74
+ # # => "1412 years 13 weeks 0 days 21 hours 25 minutes 23 seconds"
75
+ #
76
+ # 44567789123.pix_humanize_secs up_to: :millenia
77
+ # # => "1 millenium 4 centuries 12 years 13 weeks 0 days 21 hours 25 minutes 23 seconds"
78
+ #
79
+ # @return [String] The integer number of seconds, expressed in descending English time units
80
+ #
81
+ def pix_humanize_secs(omit_zeros: false, down_to: :seconds, up_to: :years, fraction: nil)
82
+ # initialize values for our loop
83
+ remaining_next_unit = self
84
+ down_to_this_unit = false
85
+ up_to_this_unit = false
86
+
87
+ output_array = HUMANIZE_SECS_ARRAY.map do |count, current_unit, singular|
88
+ unit_names = [current_unit, singular]
89
+ # puts "START: starting next unit: #{current_unit}, remaining_next_unit: #{remaining_next_unit}"
90
+
91
+ # stop when or integer hits zero
92
+ next unless remaining_next_unit.positive?
93
+
94
+ # stop if we've hit our up_to unit
95
+ next if up_to_this_unit
96
+
97
+ if unit_names.include?(up_to)
98
+ up_to_this_unit = true
99
+ current_unit_count = remaining_next_unit
100
+ else
101
+ remaining_next_unit, current_unit_count = remaining_next_unit.divmod(count)
102
+ end
103
+
104
+ # puts "MID: current_unit: #{current_unit}, current_unit_count: #{current_unit_count}, remaining_next_unit: #{remaining_next_unit}"
105
+
106
+ # skip if we are below our down_to unit
107
+ down_to_this_unit ||= unit_names.include?(down_to)
108
+ next unless down_to_this_unit
109
+
110
+ display_current_unit_count = current_unit_count.to_i
111
+ # if we are processing 'seconds' and there is a fraction, append it to the seconds
112
+ display_current_unit_count += "0.#{fraction}".to_f if current_unit == :seconds && fraction
113
+
114
+ next if omit_zeros && display_current_unit_count.zero?
115
+
116
+ display_name = display_current_unit_count == 1 ? singular : current_unit
117
+
118
+ "#{display_current_unit_count} #{display_name}"
119
+ end # map
120
+
121
+ output_array.compact.reverse.join ' '
122
+ end
123
+
124
+ A_BYTE = 1
125
+ B_BYTES = 1.0
126
+ KB_BYTES = B_BYTES * 1024
127
+ MB_BYTES = KB_BYTES * 1024
128
+ GB_BYTES = MB_BYTES * 1024
129
+ TB_BYTES = GB_BYTES * 1024
130
+ PB_BYTES = TB_BYTES * 1024
131
+
132
+ HUMANIZED_BYTE_UNITS = {
133
+ A_BYTE => 'byte',
134
+ B_BYTES => 'bytes',
135
+ KB_BYTES => 'KiB',
136
+ MB_BYTES => 'MiB',
137
+ GB_BYTES => 'GiB',
138
+ TB_BYTES => 'TiB',
139
+ PB_BYTES => 'PiB'
140
+ }.freeze
141
+
142
+ # A number of bytes converted into a string showing
143
+ # KibiBytes, Mebibytes, etc up to Pebibytes, with 2
144
+ # decimal places of precision.
145
+ #
146
+ # Returns a string with the unit in the form 'MiB'
147
+ # unless < 1KiB, in which case 'byte(s)'
148
+ # For 76253886 bytes this will return "72.72 MiB"
149
+ #
150
+ # @return [String] The human-readable file size.
151
+ #######
152
+ def pix_humanize_bytes
153
+ bytes = self
154
+ if bytes < KB_BYTES
155
+ hsize = bytes
156
+ unit = bytes == A_BYTE ? HUMANIZED_BYTE_UNITS[A_BYTE] : HUMANIZED_BYTE_UNITS[B_BYTES]
157
+
158
+ elsif bytes < MB_BYTES
159
+ hsize = (bytes / KB_BYTES).round(2)
160
+ unit = HUMANIZED_BYTE_UNITS[KB_BYTES]
161
+
162
+ elsif bytes < GB_BYTES
163
+ hsize = (bytes / MB_BYTES).round(2)
164
+ unit = HUMANIZED_BYTE_UNITS[MB_BYTES]
165
+
166
+ elsif bytes < TB_BYTES
167
+ hsize = (bytes / GB_BYTES).round(2)
168
+ unit = HUMANIZED_BYTE_UNITS[GB_BYTES]
169
+
170
+ elsif bytes < PB_BYTES
171
+ hsize = (bytes / TB_BYTES).round(2)
172
+ unit = HUMANIZED_BYTE_UNITS[TB_BYTES]
173
+
174
+ else
175
+ hsize = (bytes / PB_BYTES).round(2)
176
+ unit = HUMANIZED_BYTE_UNITS[PB_BYTES]
177
+
178
+ end
179
+
180
+ "#{hsize} #{unit}"
181
+ end
182
+
183
+ end # module
184
+
185
+ end # module
186
+
187
+ end # module
@@ -0,0 +1,14 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require 'pixar_ruby_extensions/integer/utils'
9
+
10
+ class Integer
11
+
12
+ include PixarRubyExtensions::IntegerExtensions::Utils
13
+
14
+ end
@@ -0,0 +1,50 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ module PixarRubyExtensions
9
+
10
+ module IPAddrExtensions
11
+
12
+ module Predicates
13
+
14
+ # Handy when you don't know if a value is a String or an IPAddr
15
+ #
16
+ # Does not start with pix_ so that you can call it on
17
+ # either a String or an IPAddr
18
+ #
19
+ # Because it doesn't start with pix_ we are only
20
+ # adding this if it doesn't already exist as an
21
+ # instance method
22
+ unless IPAddr.instance_methods.include? :start_with?
23
+
24
+ def start_with?(str)
25
+ to_s.start_with? str
26
+ end
27
+
28
+ end
29
+
30
+ # Handy when you don't know if a value is a String or an IPAddr
31
+ #
32
+ # Does not start with pix_ so that you can call it on
33
+ # either a String or an IPAddr
34
+ #
35
+ # Because it doesn't start with pix_ we are only
36
+ # adding this if it doesn't already exist as an
37
+ # instance method
38
+ unless IPAddr.instance_methods.include? :end_with?
39
+
40
+ def end_with?(str)
41
+ to_s.end_with? str
42
+ end
43
+
44
+ end
45
+
46
+ end # module
47
+
48
+ end # module
49
+
50
+ end # module
@@ -0,0 +1,77 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ module PixarRubyExtensions
9
+
10
+ module IPAddrExtensions
11
+
12
+ module Utils
13
+
14
+ # Convert starting and ending IPv4 IP addresses (either Strings or IPAddrs)
15
+ # into a single masked IPv4 IPAddr
16
+ #
17
+ # @param starting[Strings, IPAddr] the starting IP address
18
+ #
19
+ # @param ending[Strings, IPAddr] the ending IP address
20
+ #
21
+ # @return [IPAddr] the IP address range represented as a masked IPv4 address
22
+ #
23
+ # @example
24
+ # IPAddr.j_masked_v4addr '10.0.0.0', '10.0.0.255' # => #<IPAddr: IPv4:10.0.0.0/255.255.255.0>
25
+ #
26
+ def pix_masked_v4addr(starting, ending)
27
+ IPAddr.new "#{starting}/#{j_cidr_from_ends(starting, ending)}"
28
+ end # self.j_masked_v4addr(starting,ending)
29
+
30
+ # Given starting and ending IPv4 IP addresses (either Strings or IPAddrs)
31
+ # return the CIDR notation routing prefix mask
32
+ #
33
+ # @param starting[Strings, IPAddr] the starting IP address
34
+ #
35
+ # @param ending[Strings, IPAddr] the ending IP address
36
+ #
37
+ # @return [FixNum] the CIDR notation routing prefix mask
38
+ #
39
+ # @example
40
+ # IPAddr.j_cidr_from_ends '10.0.0.0', '10.0.0.255' # => 24
41
+ #
42
+ def pix_cidr_from_ends(starting, ending)
43
+ starting = IPAddr.new(starting) unless starting.is_a? IPAddr
44
+ ending = IPAddr.new(ending) unless ending.is_a? IPAddr
45
+
46
+ # how many possible addresses in the range?
47
+ num_addrs = ending.to_i - starting.to_i + 1
48
+
49
+ # convert the number of possible addresses to
50
+ # binary then subtract the number of bits from
51
+ # the full length of an IPv4 addr
52
+ # (32 bits) and that gives the CIDR prefix
53
+ 32 - num_addrs.to_s(2).length + 1
54
+ end # self.get_cidr(starting,ending)
55
+
56
+ # Convert a starting address (either String or IPAddr) and a
57
+ # CIDR notation routing prefix mask into the IPv4 address
58
+ # of at the end of the range of addresses.
59
+ #
60
+ # @param starting[Strings, IPAddr] the starting IP address
61
+ #
62
+ # @param cidr[FixNum] the CIDR mask
63
+ #
64
+ # @return [IPAddr] the ending IP address of the range.
65
+ #
66
+ # @example
67
+ # IPAddr.j_ending_address '10.0.0.0', 24 # => #<IPAddr: IPv4:10.0.0.255>
68
+ #
69
+ def pix_ending_address(starting, cidr)
70
+ IPAddr.new("#{starting}/#{cidr}").to_range.max
71
+ end # ending_address
72
+
73
+ end # module
74
+
75
+ end # module
76
+
77
+ end # module
@@ -0,0 +1,20 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require 'ipaddr'
9
+ require 'pixar_ruby_extensions/ipaddr/utils'
10
+ require 'pixar_ruby_extensions/ipaddr/predicates'
11
+
12
+ ############################################
13
+ # A few augmentations to IPAddr handling.
14
+ #
15
+ class IPAddr
16
+
17
+ extend PixarRubyExtensions::IPAddrExtensions::Utils
18
+ include PixarRubyExtensions::IPAddrExtensions::Predicates
19
+
20
+ end # Class IPAddr
@@ -0,0 +1,50 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ module PixarRubyExtensions
9
+
10
+ module JSONExtensions
11
+
12
+ # Add support for jsonlines (.jsonl) files
13
+ # See https://jsonlines.org/
14
+ #
15
+ # See also Array#pix_to_jsonl
16
+ #
17
+ module JSONL
18
+
19
+ # Read a jsonlines file and return an array of objects, with the addition of
20
+ # ignoring comment lines, as with JSONExtensions::Utils#pix_parse
21
+ #
22
+ # @param source [String] the string of JSON to be parsed
23
+ #
24
+ # @param comment_string [String] the string that marks the start
25
+ # of a comment line, possibly with leading whitespace. Default is '#'
26
+ #
27
+ # @return [Object] The parsed JSON
28
+ ####################
29
+ def pix_parse_jsonl(source, comment_string: '#', **opts)
30
+ src = pix_strip_comments(source, comment_string: comment_string)
31
+ src.split("\n").map { |line| JSON.parse(line, **opts) }
32
+ end
33
+
34
+ # Generate a jsonlines string from an array of objects.
35
+ #
36
+ # @param objects [Array<Object>] the objects to be turned into jsonlines
37
+ #
38
+ # @return [String] The jsonlines string
39
+ ####################
40
+ def pix_generate_jsonl(objects, **opts)
41
+ raise TypeError, "Expected an Array, got a #{objects.class}" unless objects.is_a? Array
42
+
43
+ objects.map { |obj| JSON.generate(obj, **opts) }.join("\n")
44
+ end
45
+
46
+ end # module
47
+
48
+ end # class
49
+
50
+ end # module
@@ -0,0 +1,78 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ module PixarRubyExtensions
9
+
10
+ module JSONExtensions
11
+
12
+ module Utils
13
+
14
+ # The same as JSON.parse, except whole-line comments are
15
+ # stripped from source before its parsed.
16
+ #
17
+ # Comment lines start with zero or more whitespace followed
18
+ # by the comment string, which by default is '#' but you
19
+ # can pass in others, such as '--'
20
+ #
21
+ # NOTE: Even though the JSON standard does not include
22
+ # support for comments, apprarently the Ruby JSON.parse
23
+ # method does honor '//' comments. I only discovered that
24
+ # when testing this method.
25
+ # I'm leaving it here though because we already use #
26
+ # comments, and that's the kind that YAML uses
27
+ #
28
+ # This command doesn't support or recognize in-line comments
29
+ # only whole-line comments.
30
+ #
31
+ # See JSON.parse for the standard opts and return value
32
+ #
33
+ # @param source [String] the string of JSON to be parsed
34
+ #
35
+ # @param comment_string [String] the string that marks the start
36
+ # of a comment line, possibly with leading whitespace.
37
+ #
38
+ # @return [Object] The parsed JSON
39
+ #
40
+ def pix_parse(source, comment_string: '#', **opts)
41
+ src = pix_strip_comments(source, comment_string: comment_string)
42
+ JSON.parse src, **opts
43
+ end
44
+
45
+ # See pix_parse and JSON.parse!
46
+ def pix_parse!(source, comment_string: '#', **opts)
47
+ src = pix_strip_comments(source, comment_string: comment_string)
48
+ JSON.parse! src, **opts
49
+ end
50
+
51
+ # See pix_parse and JSON.load_file
52
+ def pix_load_file(filespec, comment_string: '#', **opts)
53
+ pix_parse File.read(filespec), comment_string: comment_string, **opts
54
+ end
55
+
56
+ # See pix_parse and JSON.load_file!
57
+ def pix_load_file!(filespec, comment_string: '#', **opts)
58
+ pix_parse! File.read(filespec), comment_string: comment_string, **opts
59
+ end
60
+
61
+ # remove comment lines from a string
62
+ #
63
+ # @param source [String] the string from which to remove comment lines
64
+ #
65
+ # @param comment_string [String] the string that marks the
66
+ # start of a commend line, possibly with leading whitespce
67
+ #
68
+ # @return [String] the string with comment lines removed.
69
+ #
70
+ def pix_strip_comments(source, comment_string: '#')
71
+ source.lines.reject { |l| l =~ /^\s*#{comment_string}/ }.join
72
+ end
73
+
74
+ end # module
75
+
76
+ end # class
77
+
78
+ end # module
@@ -0,0 +1,17 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require 'json'
9
+ require 'pixar_ruby_extensions/json/utils'
10
+ require 'pixar_ruby_extensions/json/jsonl'
11
+
12
+ module JSON
13
+
14
+ extend PixarRubyExtensions::JSONExtensions::Utils
15
+ extend PixarRubyExtensions::JSONExtensions::JSONL
16
+
17
+ end
@@ -0,0 +1,49 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ module PixarRubyExtensions
9
+
10
+ module ObjectExtensions
11
+
12
+ module Predicates
13
+
14
+ BOOLEANS = [true, false].freeze
15
+
16
+ # is an object an explict true or false?
17
+ #
18
+ # @return [Boolean]
19
+ #
20
+ def pix_boolean?
21
+ BOOLEANS.include? self
22
+ end
23
+ alias pix_bool? pix_boolean?
24
+
25
+ # Is an object nil, or empty?
26
+ #
27
+ # Handier, and broader, than constantly doing
28
+ # var.to_s.empty? and doesn't require coercion
29
+ #
30
+ # @return [Boolean]
31
+ #
32
+ def pix_empty?
33
+ if nil?
34
+ true
35
+ elsif respond_to?(:empty?)
36
+ empty?
37
+ else
38
+ false
39
+ end
40
+ end
41
+
42
+ # pix_empty? was originally pix_blank?
43
+ alias pix_blank? pix_empty?
44
+
45
+ end # module
46
+
47
+ end # module
48
+
49
+ end # module
@@ -0,0 +1,15 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require 'pixar_ruby_extensions/object/predicates'
9
+
10
+ # include the modules loaded above
11
+ class Object
12
+
13
+ include PixarRubyExtensions::ObjectExtensions::Predicates
14
+
15
+ end
@@ -0,0 +1,38 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+
6
+ # frozen_string_literal: true
7
+
8
+ module PixarRubyExtensions
9
+
10
+ module PathnameExtensions
11
+
12
+ module Predicates
13
+
14
+ # Is this a real file rather than a symlink?
15
+ # @see FileTest.pix_real_file?
16
+ def pix_real_file?
17
+ FileTest.pix_real_file? self
18
+ end
19
+
20
+ # Is this a real directory rather than a symlink?
21
+ # @see FileTest.pix_real_directory?
22
+ def pix_real_directory?
23
+ FileTest.pix_real_directory? self
24
+ end
25
+
26
+ # does a path include another?
27
+ # i.e. is 'other' a descendant of self ?
28
+ def pix_include?(other)
29
+ eps = expand_path.to_s
30
+ oeps = other.expand_path.to_s
31
+ oeps != eps && oeps.start_with?(eps)
32
+ end
33
+
34
+ end # module
35
+
36
+ end # module
37
+
38
+ end # module