1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
module Gitlab
##
# Class that represents a simplified path to a file or directory
#
# This is IO-operations safe class, that does similar job to
# Ruby's Pathname but without the risk of accessing filesystem.
#
class StringPath
attr_reader :path, :universe
attr_accessor :name
def initialize(path, universe, metadata = [])
@path = sanitize(path)
@universe = universe.map { |entry| sanitize(entry) }
@metadata = metadata
end
def to_s
@path
end
def exists?
@path == './' || @universe.include?(@path)
end
def absolute?
@path.start_with?('/')
end
def relative?
!absolute?
end
def directory?
@path.end_with?('/')
end
def file?
!directory?
end
def has_parent?
nodes > 1
end
def parent
return nil unless has_parent?
new(@path.sub(basename, ''))
end
def basename
directory? ? name + ::File::SEPARATOR : name
end
def name
@name || @path.split(::File::SEPARATOR).last
end
def has_descendants?
descendants.any?
end
def descendants
return [] unless directory?
select { |entry| entry =~ /^#{Regexp.escape(@path)}.+/ }
end
def children
return [] unless directory?
return @children if @children
@children = select do |entry|
entry =~ %r{^#{Regexp.escape(@path)}[^/\s]+/?$}
end
end
def directories
return [] unless directory?
children.select(&:directory?)
end
def directories!
return directories unless has_parent? && directory?
dotted_parent = parent
dotted_parent.name = '..'
directories.prepend(dotted_parent)
end
def files
return [] unless directory?
children.select(&:file?)
end
def metadata
index = @universe.index(@path)
@metadata[index]
end
def nodes
@path.count('/') + (file? ? 1 : 0)
end
def ==(other)
@path == other.path && @universe == other.universe
end
def inspect
"#{self.class.name}: #{@path}"
end
private
def new(path)
self.class.new(path, @universe)
end
def select
selected = @universe.select { |entry| yield entry }
selected.map { |path| new(path) }
end
def sanitize(path)
self.class.sanitize(path)
end
def self.sanitize(path)
# It looks like Pathname#new doesn't touch a file system,
# neither Pathname#cleanpath does, so it is, hopefully, filesystem safe
clean_path = Pathname.new(path).cleanpath.to_s
raise ArgumentError, 'Invalid path' if clean_path.start_with?('../')
prefix = './' unless clean_path =~ %r{^[\.|/]}
suffix = '/' if path.end_with?('/') || ['.', '..'].include?(clean_path)
prefix.to_s + clean_path + suffix.to_s
end
end
end
|