File: string.rb

package info (click to toggle)
ruby-bindata 2.4.14-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 600 kB
  • sloc: ruby: 8,566; makefile: 4
file content (153 lines) | stat: -rw-r--r-- 4,465 bytes parent folder | download | duplicates (2)
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
152
153
require "bindata/base_primitive"

module BinData
  # A String is a sequence of bytes.  This is the same as strings in Ruby 1.8.
  # The issue of character encoding is ignored by this class.
  #
  #   require 'bindata'
  #
  #   data = "abcdefghij"
  #
  #   obj = BinData::String.new(read_length: 5)
  #   obj.read(data)
  #   obj #=> "abcde"
  #
  #   obj = BinData::String.new(length: 6)
  #   obj.read(data)
  #   obj #=> "abcdef"
  #   obj.assign("abcdefghij")
  #   obj #=> "abcdef"
  #   obj.assign("abcd")
  #   obj #=> "abcd\000\000"
  #
  #   obj = BinData::String.new(length: 6, trim_padding: true)
  #   obj.assign("abcd")
  #   obj #=> "abcd"
  #   obj.to_binary_s #=> "abcd\000\000"
  #
  #   obj = BinData::String.new(length: 6, pad_byte: 'A')
  #   obj.assign("abcd")
  #   obj #=> "abcdAA"
  #   obj.to_binary_s #=> "abcdAA"
  #
  # == Parameters
  #
  # String objects accept all the params that BinData::BasePrimitive
  # does, as well as the following:
  #
  # <tt>:read_length</tt>::    The length in bytes to use when reading a value.
  # <tt>:length</tt>::         The fixed length of the string.  If a shorter
  #                            string is set, it will be padded to this length.
  # <tt>:pad_byte</tt>::       The byte to use when padding a string to a
  #                            set length.  Valid values are Integers and
  #                            Strings of length 1.  "\0" is the default.
  # <tt>:pad_front</tt>::      Signifies that the padding occurs at the front
  #                            of the string rather than the end.  Default
  #                            is false.
  # <tt>:trim_padding</tt>::   Boolean, default false.  If set, #value will
  #                            return the value with all pad_bytes trimmed
  #                            from the end of the string.  The value will
  #                            not be trimmed when writing.
  class String < BinData::BasePrimitive
    arg_processor :string

    optional_parameters :read_length, :length, :trim_padding, :pad_front, :pad_left
    default_parameters  pad_byte: "\0"
    mutually_exclusive_parameters :read_length, :length
    mutually_exclusive_parameters :length, :value

    def initialize_shared_instance
      if (has_parameter?(:value) || has_parameter?(:asserted_value)) &&
          !has_parameter?(:read_length)
        extend WarnNoReadLengthPlugin
      end
      super
    end

    def assign(val)
      super(binary_string(val))
    end

    def snapshot
      # override to trim padding
      snap = super
      snap = clamp_to_length(snap)

      if get_parameter(:trim_padding)
        trim_padding(snap)
      else
        snap
      end
    end

    #---------------
    private

    def clamp_to_length(str)
      str = binary_string(str)

      len = eval_parameter(:length) || str.length
      if str.length == len
        str
      elsif str.length > len
        str.slice(0, len)
      else
        padding = (eval_parameter(:pad_byte) * (len - str.length))
        if get_parameter(:pad_front)
          padding + str
        else
          str + padding
        end
      end
    end

    def trim_padding(str)
      if get_parameter(:pad_front)
        str.sub(/\A#{eval_parameter(:pad_byte)}*/, "")
      else
        str.sub(/#{eval_parameter(:pad_byte)}*\z/, "")
      end
    end

    def value_to_binary_string(val)
      clamp_to_length(val)
    end

    def read_and_return_value(io)
      len = eval_parameter(:read_length) || eval_parameter(:length) || 0
      io.readbytes(len)
    end

    def sensible_default
      ""
    end
  end

  class StringArgProcessor < BaseArgProcessor
    def sanitize_parameters!(obj_class, params)
      params.warn_replacement_parameter(:initial_length, :read_length)
      params.must_be_integer(:read_length, :length)
      params.rename_parameter(:pad_left, :pad_front)
      params.sanitize(:pad_byte) { |byte| sanitized_pad_byte(byte) }
    end

    #-------------
    private

    def sanitized_pad_byte(byte)
      pad_byte = byte.is_a?(Integer) ? byte.chr : byte.to_s
      if pad_byte.bytesize > 1
        raise ArgumentError, ":pad_byte must not contain more than 1 byte"
      end
      pad_byte
    end
  end

  # Warns when reading if :value && no :read_length
  module WarnNoReadLengthPlugin
    def read_and_return_value(io)
      warn "#{debug_name} does not have a :read_length parameter - returning empty string"
      ""
    end
  end
end