File: encoded_string.rb

package info (click to toggle)
ruby-ttfunk 1.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 18,472 kB
  • sloc: ruby: 7,954; makefile: 7
file content (151 lines) | stat: -rw-r--r-- 3,459 bytes parent folder | download
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
140
141
142
143
144
145
146
147
148
149
150
151
# frozen_string_literal: true

require 'stringio'
require_relative 'placeholder'

module TTFunk
  # Risen when the final encoded string was requested but there were some
  # unresolved placeholders in it.
  class UnresolvedPlaceholderError < StandardError
  end

  # Risen when a placeholder is added to an Encoded String but it already
  # contains a placeholder with the same name.
  class DuplicatePlaceholderError < StandardError
  end

  # Encoded string takes care of placeholders in binary strings. Placeholders
  # are used when bytes need to be placed in the stream before their value is
  # known.
  #
  # @api private
  class EncodedString
    # @yieldparam [self]
    def initialize
      yield(self) if block_given?
    end

    # Append to string.
    #
    # @param obj [String, Placeholder, EncodedString]
    # @return [self]
    def <<(obj)
      case obj
      when String
        io << obj
      when Placeholder
        add_placeholder(obj)
        io << ("\0" * obj.length)
      when self.class
        # adjust placeholders to be relative to the entire encoded string
        obj.placeholders.each_pair do |_, placeholder|
          add_placeholder(placeholder.dup, placeholder.position + io.length)
        end

        io << obj.unresolved_string
      end

      self
    end

    # Append multiple objects.
    #
    # @param objs [Array<String, Placeholder, EncodedString>]
    # @return [self]
    def concat(*objs)
      objs.each do |obj|
        self << obj
      end
      self
    end

    # Append padding to align string to the specified word width.
    #
    # @param width [Integer]
    # @return [self]
    def align!(width = 4)
      if (length % width).positive?
        self << ("\0" * (width - (length % width)))
      end

      self
    end

    # Length of this string.
    #
    # @return [Integer]
    def length
      io.length
    end

    # Raw string.
    #
    # @return [String]
    # @raise [UnresolvedPlaceholderError] if there are any unresolved
    #   placeholders left.
    def string
      unless placeholders.empty?
        raise UnresolvedPlaceholderError,
          "string contains #{placeholders.size} unresolved placeholder(s)"
      end

      io.string
    end

    # Raw bytes.
    #
    # @return [Array<Integer>]
    # @raise [UnresolvedPlaceholderError] if there are any unresolved
    #   placeholders left.
    def bytes
      string.bytes
    end

    # Unresolved raw string.
    #
    # @return [String]
    def unresolved_string
      io.string
    end

    # Resolve placeholder.
    #
    # @param name [Symbol]
    # @param value [String]
    # @return [void]
    def resolve_placeholder(name, value)
      last_pos = io.pos

      if (placeholder = placeholders[name])
        io.seek(placeholder.position)
        io.write(value[0..placeholder.length])
        placeholders.delete(name)
      end
    ensure
      io.seek(last_pos)
    end

    # Plaholders
    #
    # @return [Hash{Symbol => Plaholder}]
    def placeholders
      @placeholders ||= {}
    end

    private

    def add_placeholder(new_placeholder, pos = io.pos)
      if placeholders.include?(new_placeholder.name)
        raise DuplicatePlaceholderError,
          "placeholder #{new_placeholder.name} already exists"
      end

      new_placeholder.position = pos
      placeholders[new_placeholder.name] = new_placeholder
    end

    def io
      @io ||= StringIO.new(''.b).binmode
    end
  end
end