File: registry.rb

package info (click to toggle)
ruby-bindata 2.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 652 kB
  • sloc: ruby: 8,896; makefile: 4
file content (129 lines) | stat: -rw-r--r-- 3,493 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
module BinData
  # Raised when #lookup fails.
  class UnRegisteredTypeError < StandardError; end

  # This registry contains a register of name -> class mappings.
  #
  # Numerics (integers and floating point numbers) have an endian property as
  # part of their name (e.g. int32be, float_le).
  #
  # Classes can be looked up based on their full name or an abbreviated +name+
  # with +hints+.
  #
  # There are two hints supported, :endian and :search_prefix.
  #
  #   #lookup("int32", { endian: :big }) will return Int32Be.
  #
  #   #lookup("my_type", { search_prefix: :ns }) will return NsMyType.
  #
  # Names are stored in under_score_style, not camelCase.
  class Registry
    def initialize
      @registry = {}
    end

    def register(name, class_to_register)
      return if name.nil? || class_to_register.nil?

      formatted_name = underscore_name(name)
      warn_if_name_is_already_registered(formatted_name, class_to_register)

      @registry[formatted_name] = class_to_register
    end

    def unregister(name)
      @registry.delete(underscore_name(name))
    end

    def lookup(name, hints = {})
      search_names(name, hints).each do |search|
        register_dynamic_class(search)
        if @registry.has_key?(search)
          return @registry[search]
        end
      end

      # give the user a hint if the endian keyword is missing
      search_names(name, hints.merge(endian: :big)).each do |search|
        register_dynamic_class(search)
        if @registry.has_key?(search)
          raise(UnRegisteredTypeError, "#{name}, do you need to specify endian?")
        end
      end

      raise(UnRegisteredTypeError, name)
    end

    # Convert CamelCase +name+ to underscore style.
    def underscore_name(name)
      name
        .to_s
        .sub(/.*::/, "")
        .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
        .gsub(/([a-z\d])([A-Z])/, '\1_\2')
        .tr('-', '_')
        .downcase
    end

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

    def search_names(name, hints)
      base = underscore_name(name)
      searches = []

      search_prefix = [""] + Array(hints[:search_prefix])
      search_prefix.each do |prefix|
        nwp = name_with_prefix(base, prefix)
        nwe = name_with_endian(nwp, hints[:endian])

        searches << nwp
        searches << nwe if nwe
      end

      searches
    end

    def name_with_prefix(name, prefix)
      prefix = prefix.to_s.chomp('_')
      if prefix == ""
        name
      else
        "#{prefix}_#{name}"
      end
    end

    def name_with_endian(name, endian)
      return nil if endian.nil?

      suffix = (endian == :little) ? 'le' : 'be'
      if /^u?int\d+$/.match?(name)
        name + suffix
      else
        name + '_' + suffix
      end
    end

    def register_dynamic_class(name)
      if /^u?int\d+(le|be)$/.match?(name) || /^s?bit\d+(le)?$/.match?(name)
        class_name = name.gsub(/(?:^|_)(.)/) { $1.upcase }
        begin
          # call const_get for side effect of creating class
          BinData.const_get(class_name)
        rescue NameError
        end
      end
    end

    def warn_if_name_is_already_registered(name, class_to_register)
      prev_class = @registry[name]
      if prev_class && prev_class != class_to_register
        Kernel.warn "warning: replacing registered class #{prev_class} " \
                    "with #{class_to_register}"
      end
    end
  end

  # A singleton registry of all registered classes.
  RegisteredClasses = Registry.new
end