Skip to content

Commit fbd5124

Browse files
author
Glyn Normington
committed
Add FilteringPathname
This class behaves like Pathname but filters the filesystem and is optionally immutable. The implementation leans heavily on method_missing in order to centralise the filtering, conversion, and mutability logic. [#61738342]
1 parent 2d8cae3 commit fbd5124

3 files changed

Lines changed: 544 additions & 1 deletion

File tree

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Encoding: utf-8
2+
# Cloud Foundry Java Buildpack
3+
# Copyright (c) 2013 the original author or authors.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
require 'java_buildpack/util'
18+
require 'pathname'
19+
require 'set'
20+
21+
module JavaBuildpack::Util
22+
23+
# This class conforms to the interface of +Pathname+, but filters the set of files that can be accessed and does not
24+
# support +Pathname+'s class methods.
25+
#
26+
# If a +Pathname+ method which mutates the file system is called, it will throw an exception unless the instance is
27+
# mutable.
28+
#
29+
# If the underlying filesystem is modified once an instance of this path has been created, the view provided
30+
# by the instance will not change unless a file or directory allowed by the instance's filter is created, modified, or
31+
# deleted.
32+
class FilteringPathname
33+
34+
# Create a +FilteringPathname+ which behaves like the given pathname, but which applies the given filter to all files.
35+
#
36+
# The filesystem underpinning the given pathname must not contain a file or directory whose name is the name of the
37+
# given pathname with '.nil' appended to it. This must be true for the lifetime of the +FilteringPathname+.
38+
#
39+
# The filter is applied to files which are accessed via the given pathname.
40+
# If the filter returns +true+ for a particular pathname, the pathname behaves normally for this instance.
41+
# If the filter returns +false+ for a particular pathname, the pathname behaves as if it does not exist.
42+
#
43+
# The +FilteringPathname+ may be immutable in which case calling a mutator method causes an exception to be thrown.
44+
# Alternatively, the +FilteringPathname+ may be mutable in which case calling a mutator method may mutate the
45+
# file system. The results of mutating the file system will be subject to filtering by the given filter.
46+
#
47+
# @param [Pathname] pathname the +Pathname+ which is to be filtered
48+
# @param [Proc] filter a lambda which takes a +Pathname+ and returns either +true+ (to 'keep' the pathname) or
49+
# +false+ (to filter out the pathname)
50+
# @param [Boolean] mutable +true+ if and only if the +FilteringPathname+ may be used to mutate the file system
51+
def initialize(pathname, filter, mutable = false)
52+
@pathname = pathname
53+
@filter = filter
54+
@non_existent = Pathname.new "#{pathname}.nil"
55+
FilteringPathname.check_file_does_not_exist @non_existent
56+
@delegated_pathname = @filter.call(@pathname) ? @pathname : @non_existent
57+
@mutable = mutable
58+
end
59+
60+
# Dispatch superclass methods via method_missing.
61+
undef_method :<=>
62+
undef_method :==
63+
undef_method :===
64+
undef_method :taint
65+
undef_method :untaint
66+
67+
# @see Pathname.
68+
def each_entry(&block)
69+
delegate_and_yield_visible(:each_entry, &block)
70+
end
71+
72+
# @see Pathname.
73+
def entries
74+
visible delegate.entries
75+
end
76+
77+
# @see Pathname.
78+
def open(mode = nil, perm = nil, opt = nil, &block)
79+
check_mutable if mode =~ /[wa]/
80+
delegate.open(mode, perm, opt, &block)
81+
end
82+
83+
# @see Pathname.
84+
def to_s
85+
@filter.call(@pathname) ? delegate.to_s : ''
86+
end
87+
88+
# @see Pathname.
89+
def children(with_directory = true)
90+
if with_directory
91+
super # delegate to method_missing
92+
else
93+
visible delegate.children(false)
94+
end
95+
end
96+
97+
# @see Pathname.
98+
def each_child(with_directory = true, &block)
99+
if with_directory
100+
super # delegate to method_missing
101+
else
102+
delegate_and_yield_visible(:each_child, false, &block)
103+
end
104+
end
105+
106+
private
107+
108+
MUTATORS = [:chmod, :chown, :delete, :lchmod, :lchown, :make_link, :make_symlink, :mkdir, :mkpath, :rename, :rmdir, :rmtree, :taint, :unlink, :untaint].to_set.freeze
109+
110+
def self.check_file_does_not_exist(file)
111+
fail "#{file} should not exist" if file.exist?
112+
end
113+
114+
def check_mutable
115+
fail 'FilteringPathname is immutable' unless @mutable
116+
end
117+
118+
def convert_if_necessary(r)
119+
if r.instance_of?(Pathname)
120+
@filter.call(r) ? filtered_pathname(r) : nil
121+
else
122+
r
123+
end
124+
end
125+
126+
def convert_result_if_necessary(result)
127+
if result.instance_of? Array
128+
result.map { |r| convert_if_necessary(r) }.compact
129+
else
130+
result ? convert_if_necessary(result) || @non_existent : nil
131+
end
132+
end
133+
134+
def delegate
135+
FilteringPathname.check_file_does_not_exist @non_existent
136+
@delegated_pathname
137+
end
138+
139+
def delegate_and_yield_visible(method, *args)
140+
delegate.send(method, *args) do |y|
141+
yield y if visible y
142+
end
143+
end
144+
145+
def filtered_pathname(pathname)
146+
FilteringPathname.new(pathname, @filter)
147+
end
148+
149+
def method_missing(method, *args)
150+
check_mutable if MUTATORS.member? method
151+
if block_given?
152+
result = delegate.send(method, *args) do |*values|
153+
converted_values = values.map { |value| convert_if_necessary(value) }.compact
154+
yield *converted_values unless converted_values.empty? # rubocop:disable Syntax
155+
end
156+
else
157+
result = delegate.send(method, *args)
158+
end
159+
convert_result_if_necessary(result)
160+
end
161+
162+
def respond_to_missing?(symbol, include_private = false)
163+
delegate.respond_to?(symbol, include_private)
164+
end
165+
166+
def visible(entry)
167+
if entry.instance_of? Array
168+
entry.select { |child| visible(child) }
169+
else
170+
@filter.call(@pathname + entry)
171+
end
172+
end
173+
174+
end
175+
end

0 commit comments

Comments
 (0)