Module: DataMetaProtobuf

Defined in:
lib/dataMetaProtobuf.rb

Overview

DataMetaDOM and Protobuf IDL.

For command line details either check the new method’s source or the README, the usage section.

Constant Summary collapse

VERSION =

Current version

'1.0.2'
INDENT =

First level indent

' ' * 4
L =

Since we are piping source in and spilling the result into the STDOUT, need logger for any other output.

Logger.new('dataMetaProtobuf.log', 0, 10_000_000)
GEM_ROOT =

The root of the gem.

File.realpath(File.dirname(__FILE__) + '/../')
TMPL_ROOT =

Location of templates.

File.join(GEM_ROOT, 'tmpl')
PROTO_TYPES =

Mapping from a DataMeta DOM type to a matching renderer of Protobuf IDL.

{
        DataMetaDom::BOOL => lambda{|dt| %q<bool>},
        DataMetaDom::CHAR => lambda{|dt| %q<string>},
        DataMetaDom::INT => lambda{ |dt|
          len = dt.length
          case
            when len <= 4; %q<int32>
            when len <= 8; %q<int64>
            else; raise "Invalid integer length #{len}"
          end
        },
        DataMetaDom::FLOAT => lambda{|dt|
          len = dt.length
          case
            when len <= 4; %q<float>
            when len <= 8; %q<double>
            else; raise "Invalid float length #{len}"
          end
        },
        DataMetaDom::RAW => lambda{|dt| %q<bytes>},
        DataMetaDom::STRING => lambda{|dt| %q<string>},
=begin
Unlike DataMeta DOM, Protobuf does not support temporal types such as date, time and datetime, therefore we export
DataMeta +datetime+ as +string+.
=end
    DataMetaDom::DATETIME => lambda{|dt| %q<string>},
# No support for these in this release:
      #NUMERIC => lambda{|t| "BigDecimal"}
}

Class Method Summary collapse

Class Method Details

.assertNamespace(fullName) ⇒ Object

Splits the full name of a class into the namespace and the base, returns an array of the namespace (empty string if there is no namespace on the name) and the base name.

Examples:

  • 'BaseNameAlone' -> ['', 'BaseNameAlone']

  • 'one.package.another.pack.FinallyTheName' -> ['one.package.another.pack', 'FinallyTheName']



95
96
97
98
# File 'lib/dataMetaProtobuf.rb', line 95

def assertNamespace(fullName)
  ns, base = DataMetaDom::splitNameSpace(fullName)
  [DataMetaDom.validNs?(ns, base) ? ns : '', base]
end

.genSchema(model) ⇒ Object

Generates the Protobuf IDL, returns the source.

We decided against using the template and use simple string concatenation instead; because the way Protobuf IDL is designed, it’s easier to do the string concat. For example, rendering a message in a message in a message…



106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/dataMetaProtobuf.rb', line 106

def genSchema(model)
    result = %<
// This Protobuf schema is generated by DataMeta exporter.

syntax = "proto3";
package #{model.sources[model.sources.doneKeys[0]].namespace};

>
    model.enums.values.each { |e| renderEnum model, e, result }
    model.records.values.each { |r| renderRec model, r, result }
    result << "\n"
    result
end

.helpProtobufGen(file, errorText = nil) ⇒ Object

Shortcut to help for the Protobuf IDL generator



177
178
179
# File 'lib/dataMetaProtobuf.rb', line 177

def helpProtobufGen(file, errorText=nil)
    DataMetaDom::help(file, %<DataMeta DOM Protobuf IDL Generation ver #{VERSION}>, '<DataMeta DOM source file>', errorText)
end

.protoType(dataMetaType, model, rec) ⇒ Object

Converts DataMeta DOM type to Protobuf IDL type.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/dataMetaProtobuf.rb', line 72

def protoType(dataMetaType, model, rec)
    ty = dataMetaType.type
    if model.records[ty]
        nameSpace, base = assertNamespace(ty)
        base
    elsif model.enums[ty]
        nameSpace, base = assertNamespace(ty)
        base
    else
        renderer = PROTO_TYPES[dataMetaType.type]
        raise "Unsupported type #{dataMetaType}" unless renderer
        renderer.call(dataMetaType)
    end
end

.renderEnum(model, enm, result) ⇒ Object

Render one single enum



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/dataMetaProtobuf.rb', line 151

def renderEnum(model, enm, result)
    nameSpace, base = assertNamespace(enm.name)
    L.info("Rendering enum #{enm.name} of the type #{enm.class}")
    case enm
        when DataMetaDom::Enum
            values = enm.keys.map{|k| enm[k]} # sorted by ordinals to preserve the original order
            pbInt = 0
            result << %<
enum #{base} {
>
            values.each{|v|
                result << "#{INDENT}#{v} = #{pbInt};\n"
                pbInt += 1
            }
            result << %q<}
>
        when DataMetaDom::Mapping
            # taken care of in renderDer
        else
            raise ArgumentError, %<Unsupported enum type "#{enm.class}" for the name "#{enm.name}">

    end

end

.renderRec(model, rec, result) ⇒ Object

Render a single record



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
# File 'lib/dataMetaProtobuf.rb', line 121

def renderRec(model, rec, result)
    nameSpace, base = assertNamespace(rec.name)
    L.info("Rendering record #{rec.name}")
    result << %<
message #{base} {
>
    pbInt = 1
    rec.fields.each_key { | fldId|
        fld = rec.fields[fldId]
        ty = fld.dataType.type
        if fld.aggr && !fld.map?
            result << "#{INDENT}repeated #{protoType(fld.dataType, model, rec)} #{fldId} = #{pbInt};\n"
        elsif fld.map?
            raise ArgumentError, %<Field "#{fldId}" of the rec "#{base}": for Protobuf, map can not be optional, it must be required> unless fld.isRequired
            result << "#{INDENT}map<#{protoType(fld.dataType, model, rec)}, #{protoType(fld.trgType, model, rec)}> #{fldId} = #{pbInt};\n"
        elsif model.enums[ty] && model.enums[ty].is_a?(DataMetaDom::Mapping)
            raise ArgumentError, %<Field "#{fldId}" of the rec "#{base}": for Protobuf, map can not be optional, it must be required> unless fld.isRequired
            result << "#{INDENT}map<#{protoType(model.enums[ty].fromT, model, rec)}, #{protoType(model.enums[ty].toT, model, rec)}> #{fldId} = #{pbInt};\n"
        elsif fld.isRequired
            result << "#{INDENT}#{protoType(fld.dataType, model, rec)} #{fldId} = #{pbInt};\n"
        else
            result << "#{INDENT}repeated #{protoType(fld.dataType, model, rec)} #{fldId} = #{pbInt};\n"
        end
        pbInt += 1
    }
    result << %q<}
>
end