module DataMetaDom::PythonLexer

Definition for generating Python basic classes that do not relate to serialization layer.

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

Constants

INIT_PY_HEADER

Standard header for every __init__.py file

MODEL_HEADER

The top of each model file.

Public Instance Methods

genPy(model, outRoot) click to toggle source

Generates Python sources for the model, the “plain” Python part, without fancy dependencies.

  • Parameters

    • parser - instance of Model

    • outRoot - output directory

# File lib/dataMetaDom/python.rb, line 84
    def genPy(model, outRoot)
        firstRecord = model.records.values.first
        @pyPackage, baseName, packagePath = DataMetaDom::PojoLexer::assertNamespace(firstRecord.name)

        # Next: replace dots with underscores.
        # The path also adjusted accordingly.
        #
        # Rationale for this, quoting PEP 8:
        #
        #    Package and Module Names
        #
        #    Modules should have short, all-lowercase names. Underscores can be used in the module name if it improves
        #    readability. Python packages should also have short, all-lowercase names, although the use of underscores
        #    is discouraged.
        #
        # Short and all-lowercase names, and improving readability if you have complex system and need long package names,
        # is "discouraged". Can't do this here, our system is more complicated for strictly religous, "pythonic" Python.
        # A tool must be enabling, and in this case, this irrational ruling gets in the way.
        # And dots are a no-no, Python can't find packages with complicated package structures and imports.
        #
        # Hence, we opt for long package names with underscores for distinctiveness and readability:
        @pyPackage = @pyPackage.gsub('.', '_')
        packagePath = packagePath.gsub('/', '_')
        destDir = File.join(outRoot, packagePath)
        FileUtils.mkdir_p destDir
        # build the package path and create __init__.py files in each package's dir if it's not there.
        @destDir = packagePath.split(File::SEPARATOR).reduce(outRoot){ |s,v|
            r = s.empty? ? v : File.join(s, v) # next package dir
            FileUtils.mkdir_p r # create if not there
            # create the __init__.py with proper content if it's not there
            df = File.join(r, '__init__.py'); IO.write(df, INIT_PY_HEADER, mode: 'wb') unless r == outRoot || File.file?(df)
            r # pass r for the next reduce iteration
        }
        vars =  OpenStruct.new # for template's local variables. ERB does not make them visible to the binding
        modelFile = File.join(@destDir, 'model.py')
        [modelFile].each{|f| FileUtils.rm f if File.file?(f)}
        IO.write(modelFile, MODEL_HEADER % @pyPackage, mode: 'wb')
        (model.records.values + model.enums.values).each { |srcE| # it is important that the records render first
            _, baseName, _ = DataMetaDom::PojoLexer::assertNamespace(srcE.name)

            pyClassName = baseName

            case
                when srcE.kind_of?(DataMetaDom::Record)
                    fields = srcE.fields
                    rxRoster = PyRegExRoster.new
                    eqHashFields = srcE.identity ? srcE.identity.args : fields.keys.sort
                    reqFields = fields.values.select{|f| f.isRequired }.map{|f| f.name}
                    verCalls = reqFields.map{|r| %<if(self.__#{r} is None): missingFields.append("#{r}");>}.join("\n#{INDENT * 2}")
                    fieldVerifications = ''
                    fields.each_key { |k|
                        f = fields[k]
                        dt = f.dataType
                        rxRoster.register(f) if f.regex
                        if f.trgType # Maps: if either the key or the value is verifiable, do it
                            mainVf = model.records[dt.type] # main data type is verifiable
                            trgVf = model.records[f.trgType.type]  # target type is verifiable
                            if mainVf || trgVf
                                fieldVerifications << "\n#{INDENT*2}#{!f.isRequired ? "if(self.__#{f.name} is not None):\n#{INDENT*3}" : '' }for k, v in self.__#{f.name}.iteritems():#{mainVf ? 'k.verify();' : ''} #{trgVf ? 'v.verify()' :''}\n"
                            end
                        end

                        if model.records[dt.type] && !f.trgType # maps handled separately
                            fieldVerifications << "\n#{INDENT*2}#{!f.isRequired ? "if(self.__#{f.name} is not None): " : '' }#{f.aggr ? "[v___#{f.name}.verify() for v___#{f.name} in self.__#{f.name} ]" : "self.__#{f.name}.verify()"}"
                            # the Verifiable::verify method reference works just fine, tested it: Java correctly calls the method on the object
                        end
                    }
                    IO::write(File.join(@destDir, 'model.py'),
                              ERB.new(IO.read(File.join(File.dirname(__FILE__), '../../tmpl/python/entity.erb')),
                                      $SAFE, '%<>').result(binding), mode: 'ab')
                when srcE.kind_of?(DataMetaDom::Mappings)
#                        FIXME -- implement!!!
                when srcE.kind_of?(DataMetaDom::Enum)
                    IO.write(modelFile, %|#{baseName} = Enum("#{baseName}", "#{srcE.rawVals.join(' ')}")\n\n|, mode: 'ab')
#                        # handled below, bundled in one huge file
                when srcE.kind_of?(DataMetaDom::BitSet)
#                        FIXME -- implement!!!
                else
                    raise "Unsupported Entity: #{srcE.inspect}"
            end
        }
    end