summaryrefslogtreecommitdiff
path: root/lib/gitlab/ci/build/artifacts/metadata/entry.rb
blob: 25b71fc3275e00cfd12c60d4bceae22d84e2303b (plain)
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
module Gitlab
  module Ci::Build::Artifacts
    class Metadata
      ##
      # Class that represents an entry (path and metadata) to a file or
      # directory in GitLab CI Build Artifacts binary file / archive
      #
      # This is IO-operations safe class, that does similar job to
      # Ruby's Pathname but without the risk of accessing filesystem.
      #
      # This class is working only with UTF-8 encoded paths.
      #
      class Entry
        attr_reader :path, :entries
        attr_accessor :name

        def initialize(path, entries)
          @path = path.dup.force_encoding('UTF-8')
          @entries = entries

          if path.include?("\0")
            raise ArgumentError, 'Path contains zero byte character!'
          end

          unless path.valid_encoding?
            raise ArgumentError, 'Path contains non-UTF-8 byte sequence!'
          end
        end

        def directory?
          blank_node? || @path.end_with?('/')
        end

        def file?
          !directory?
        end

        def has_parent?
          nodes > 0
        end

        def parent
          return nil unless has_parent?
          self.class.new(@path.chomp(basename), @entries)
        end

        def basename
          (directory? && !blank_node?) ? name + '/' : name
        end

        def name
          @name || @path.split('/').last.to_s
        end

        def children
          return [] unless directory?
          return @children if @children

          child_pattern = %r{^#{Regexp.escape(@path)}[^/]+/?$}
          @children = select_entries { |path| path =~ child_pattern }
        end

        def directories(opts = {})
          return [] unless directory?
          dirs = children.select(&:directory?)
          return dirs unless has_parent? && opts[:parent]

          dotted_parent = parent
          dotted_parent.name = '..'
          dirs.prepend(dotted_parent)
        end

        def files
          return [] unless directory?
          children.select(&:file?)
        end

        def metadata
          @entries[@path] || {}
        end

        def nodes
          @path.count('/') + (file? ? 1 : 0)
        end

        def blank_node?
          @path.empty? # "" is considered to be './'
        end

        def exists?
          blank_node? || @entries.include?(@path)
        end

        def empty?
          children.empty?
        end

        def to_s
          @path
        end

        def ==(other)
          @path == other.path && @entries == other.entries
        end

        def inspect
          "#{self.class.name}: #{@path}"
        end

        private

        def select_entries
          selected = @entries.select { |path, _metadata| yield path }
          selected.map { |path, _metadata| self.class.new(path, @entries) }
        end
      end
    end
  end
end