tools/build/_decorators.py

Summary

Maintainability
F
3 days
Test Coverage
#!/usr/bin/env python

import os.path
import sys
import subprocess


def replace(msg, to_replace):
    for k in to_replace:
        msg = msg.replace(k[0], k[1])
    return msg


class Base:

    def __init__(self, name, data_type, return_type):
        self.names = [("NAME", name.replace(" ", "_")),
                      ("DATA", data_type)]
        self.names.extend([("GET_BOTH", "get_value"),
                           ("SET_BOTH", "set_value"),
                           ("GET_STATIC", "get_static_value"),
                           ("SET_STATIC", "set_static_value"),
                           ("GET_FRAME", "get_frame_value"),
                           ("SET_FRAME", "set_frame_value")])
        if return_type.endswith("s"):
            self.names.append(("TYPES", return_type + "List"))
        elif return_type.endswith("x"):
            self.names.append(("TYPES", return_type + "es"))
        else:
            self.names.append(("TYPES", return_type + "s"))
        self.names.append(("TYPE", return_type))
        self.get_methods = ""
        self.set_methods = ""
        self.bulk_get_methods = ""
        self.bulk_set_methods = ""
        self.helpers = ""
        self.check = ""

    def get_data_members(self):
        return replace("DATA NAME_;", self.names)

    def get_get_set_methods(self, const):
        if not const:
            ret = replace(self.set_methods, self.names)
        else:
            ret = replace(self.get_methods, self.names)
        return ret

    def get_helpers(self):
        return replace(self.helpers, self.names)

    def get_data_arguments(self):
        return replace("DATA NAME", self.names)

    def get_data_pass(self, member):
        if member:
            return replace("NAME_", self.names)
        else:
            return replace("NAME", self.names)

    def get_data_saves(self):
        return replace("NAME_(NAME)", self.names)

    def get_data_initialize(self):
        return (
            replace("NAME_(" + self.data_initialize + ")", self.names)
        )

    def get_check(self):
        return replace(self.check, self.names)


class Attribute(Base):

    def _is_path(self, name):
        """Return True iff `name` describes a filesystem path attribute.
           Unfortunately internally RMF stores both paths and non-path
           strings in String(s) attributes (and we can't easily add a
           separate Path(s) type without breaking backwards compatibility),
           and uses the attribute name to distinguish them. Check here that
           we haven't used a path-like name for a string or vice versa."""
        return (name.endswith("filename") or name.endswith("filenames")
                or name in ("cluster density", "image files", "path"))

    def _check_string_name(self, name):
        if self._is_path(name):
            raise ValueError("Cannot use a 'path' name (%s) for a "
                             "string Attribute" % name)

    def __init__(self, name, attribute_type, function_name=None,
                 default=None):
        if attribute_type in ('String', 'Strings'):
            self._check_string_name(name)
        if not function_name:
            self.function_name = name.replace(" ", "_")
        else:
            self.function_name = function_name
        Base.__init__(self, name, attribute_type +
                      "Key", attribute_type)
        self.get_methods = """
  TYPE get_%s() const {
    try {
      return get_node().GET_BOTH(NAME_);
    } RMF_DECORATOR_CATCH( );
  }
  TYPE get_frame_%s() const {
    try {
      return get_node().GET_FRAME(NAME_);
    } RMF_DECORATOR_CATCH( );
  }
  TYPE get_static_%s() const {
    try {
      return get_node().GET_STATIC(NAME_);
    } RMF_DECORATOR_CATCH( );
  }
""" % (self.function_name, self.function_name, self.function_name)
        if default is not None:
            self.get_methods = self.get_methods.replace(
                'return',
                'if (!get_node().get_has_value(NAME_)) return %s;\n'
                'return' % repr(default).replace("'", '"'))
        self.set_methods = """
  void set_%s(TYPE v) {
    try {
      get_node().SET_BOTH(NAME_, v);
    } RMF_DECORATOR_CATCH( );
  }
  void set_frame_%s(TYPE v) {
    try {
      get_node().SET_FRAME(NAME_, v);
    } RMF_DECORATOR_CATCH( );
  }
  void set_static_%s(TYPE v) {
    try {
      get_node().SET_STATIC(NAME_, v);
    } RMF_DECORATOR_CATCH( );
  }
""" % (self.function_name, self.function_name, self.function_name)
        # If the attribute is allowed to be null, skip check
        if default is not None:
            self.check = ""
        else:
            self.check = "!nh.GET(NAME_).get_is_null()"
        self.data_initialize = "fh.get_key<TYPETag>(cat_, \"%s\")" % name


class NodeAttribute(Attribute):

    def __init__(self, name):
        Attribute.__init__(self, name, "Int", True)
        self.get_methods = """
  NodeConstHandle get_NAME() const {
    try {
      int id = get_node().GET_BOTH(NAME_);
      return get_node().get_file().get_node(NodeID(id));
    } RMF_DECORATOR_CATCH( );
  }
"""
        self.set_methods = """
  void set_NAME(NodeConstHandle v) {
    try {
      get_node().SET_BOTH(NAME_, v.get_id().get_index());
    } RMF_DECORATOR_CATCH( );
  }
"""


class PathAttribute(Attribute):
    """Similar to a string Attribute, but designed for storing paths.
       Paths are stored internally relative to the directory containing
       the RMF file (in-memory RMFs are considered to be in the current
       working directory) but the API always returns absolute paths."""

    def _check_string_name(self, name):
        if not self._is_path(name):
            raise ValueError("Cannot use a non-path name (%s) "
                             "for a PathAttribute" % name)

    def __init__(self, name, function_name=None):
        Attribute.__init__(self, name, "String", function_name)
        self.get_methods = """
  String get_%s() const {
    try {
      String relpath = get_node().GET_BOTH(NAME_);
      String filename = get_node().get_file().get_path();
      return internal::get_absolute_path(filename, relpath);
    } RMF_DECORATOR_CATCH( );
  }
""" % self.function_name
        self.set_methods = """
  void set_%s(String path) {
   try {
     String filename = get_node().get_file().get_path();
     String relpath = internal::get_relative_path(filename, path);
     get_node().SET_BOTH(NAME_, relpath);
   } RMF_DECORATOR_CATCH( );
  }
""" % self.function_name


class OptionalPathAttribute(Attribute):
    """Like a PathAttribute, but it can be empty."""

    def _check_string_name(self, name):
        if not self._is_path(name):
            raise ValueError("Cannot use a non-path name (%s) "
                             "for an OptionalPathAttribute" % name)

    def __init__(self, name, function_name=None):
        Attribute.__init__(self, name, "String", function_name)
        self.get_methods = """
  String get_%s() const {
    try {
      if (!get_node().get_has_value(NAME_)) {
        return "";
      } else {
        String relpath = get_node().GET_BOTH(NAME_);
        String filename = get_node().get_file().get_path();
        return internal::get_absolute_path(filename, relpath);
      }
    } RMF_DECORATOR_CATCH( );
  }
""" % self.function_name
        self.set_methods = """
  void set_%s(String path) {
   try {
     if (path.empty()) {
       get_node().SET_BOTH(NAME_, path);
     } else {
       String filename = get_node().get_file().get_path();
       String relpath = internal::get_relative_path(filename, path);
       get_node().SET_BOTH(NAME_, relpath);
     }
   } RMF_DECORATOR_CATCH( );
  }
""" % self.function_name


class AttributePair(Base):

    def __init__(self, name, data_type, return_type, begin, end):
        Base.__init__(self, name, "std::array<%sKey, 2>" %
                      data_type, return_type)
        self.helpers = """  template <class H> DATA get_NAME_keys(H fh) const {
     DATA ret;
     ret[0] = fh.template get_key<%sTag>(cat_, "%s");
     ret[1] = fh.template get_key<%sTag>(cat_, "%s");
     return ret;
    }
""" % (data_type, begin, data_type, end)
        self.check = "!nh.GET(NAME_[0]).get_is_null() && !nh.GET(NAME_[1]).get_is_null()"
        self.data_initialize = "get_NAME_keys(fh)"


class SingletonRangeAttribute(AttributePair):

    def __init__(self, name, data_type, begin, end):
        AttributePair.__init__(
            self, name, data_type, data_type, begin, end)
        self.get_methods = """
  TYPE get_NAME() const {
    try {
      return get_node().GET_BOTH(NAME_[0]);
    } RMF_DECORATOR_CATCH( );
  }
  TYPE get_frame_NAME() const {
    try {
      return get_node().GET_FRAME(NAME_[0]);
    } RMF_DECORATOR_CATCH( );
  }
  TYPE get_static_NAME() const {
    try {
      return get_node().GET_STATIC(NAME_[0]);
    } RMF_DECORATOR_CATCH( );
  }
"""
        self.set_methods = """
  void set_NAME(TYPE v) {
    try {
      get_node().SET_BOTH(NAME_[0], v);
      get_node().SET_BOTH(NAME_[1], v);
    } RMF_DECORATOR_CATCH( );
  }
  void set_frame_NAME(TYPE v) {
    try {
      get_node().SET_FRAME(NAME_[0], v);
      get_node().SET_FRAME(NAME_[1], v);
    } RMF_DECORATOR_CATCH( );
  }
  void set_static_NAME(TYPE v) {
    try {
      get_node().SET_STATIC(NAME_[0], v);
      get_node().SET_STATIC(NAME_[1], v);
    } RMF_DECORATOR_CATCH( );
  }
"""
        self.check = "!nh.GET(NAME_[0]).get_is_null() && !nh.GET(NAME_[1]).get_is_null() && nh.GET_BOTH(NAME_[0]) == nh.GET_BOTH(NAME_[1])"


class RangeAttribute(AttributePair):

    def __init__(self, name, data_type, begin, end):
        AttributePair.__init__(
            self, name, data_type, data_type + "Range", begin, end)
        self.get_methods = """
  TYPE get_NAME() const {
    try {
      TYPE ret;
      ret[0] = get_node().GET_BOTH(NAME_[0]);
      ret[1] = get_node().GET_BOTH(NAME_[1]);
      return ret;
    } RMF_DECORATOR_CATCH( );
  }
  TYPE get_static_NAME() const {
    try {
      TYPE ret;
      ret[0] = get_node().GET_STATIC(NAME_[0]);
      ret[1] = get_node().GET_STATIC(NAME_[1]);
      return ret;
    } RMF_DECORATOR_CATCH( );
  }
  TYPE get_frame_NAME() const {
    try {
    TYPE ret;
      ret[0] = get_node().GET_FRAME(NAME_[0]);
      ret[1] = get_node().GET_FRAME(NAME_[1]);
      return ret;
    } RMF_DECORATOR_CATCH( );
  }
"""
        self.set_methods = """
  void set_NAME(%s v0, %s v1) {
    try {
      get_node().SET_BOTH(NAME_[0], v0);
      get_node().SET_BOTH(NAME_[1], v1);
    } RMF_DECORATOR_CATCH( );
  }
  void set_frame_NAME(%s v0, %s v1) {
    try {
      get_node().SET_FRAME(NAME_[0], v0);
      get_node().SET_FRAME(NAME_[1], v1);
    } RMF_DECORATOR_CATCH( );
  }
  void set_static_NAME(%s v0, %s v1) {
    try {
      get_node().SET_STATIC(NAME_[0], v0);
      get_node().SET_STATIC(NAME_[1], v1);
    } RMF_DECORATOR_CATCH( );
  }
""" % (data_type, data_type, data_type, data_type, data_type, data_type)
        self.check = "!nh.GET(NAME_[0]).get_is_null() && !nh.GET(NAME_[1]).get_is_null() && nh.GET_BOTH(NAME_[0]) < nh.GET_BOTH(NAME_[1])"

        self.check = "!nh.GET(NAME_[0]).get_is_null() && !nh.GET(NAME_[1]).get_is_null() && nh.GET_BOTH(NAME_[0]) < nh.GET_BOTH(NAME_[1])"


decorator = """

  /** See also NAME and NAMEFactory.
    */
    class NAMEConst: public Decorator {
    friend class NAMEFactory;
  protected:
    DATA_MEMBERS
    NAMEConst(NodeConstHandle nh,
              DATA_ARGUMENTS):
       DATA_SAVES {
    }
  public:
    CONSTMETHODS
    static std::string get_decorator_type_name() {
         return "NAMEConst";
    }
    RMF_SHOWABLE(NAMEConst, "NAME: " << get_node());
  };
   /** See also NAMEFactory.
    */
    class NAME: public NAMEConst {
    friend class NAMEFactory;
    NAME(NodeHandle nh,
              DATA_ARGUMENTS):
       NAMEConst(nh, DATA_PASS_ARGUMENTS) {
    }
  public:
    NONCONSTMETHODS
    static std::string get_decorator_type_name() {
         return "NAME";
    }
  };

"""


factory = """
  /** Create decorators of type NAME.
    */
  class NAMEFactory: public Factory {
    Category cat_;
DATA_MEMBERS
HELPERS
  public:
    NAMEFactory(FileConstHandle fh):
    cat_(fh.get_category("CATEGORY")),
    DATA_INITIALIZE {
    }
     NAMEFactory(FileHandle fh):
    cat_(fh.get_category("CATEGORY")),
    DATA_INITIALIZE {
    }
    /** Get a NAMEConst for nh.*/
    NAMEConst get(NodeConstHandle nh) const {
      CREATE_CHECKS
      return NAMEConst(nh, DATA_PASS);
    }
    /** Get a NAME for nh.*/
    NAME get(NodeHandle nh) const {
      CREATE_CHECKS
      return NAME(nh, DATA_PASS);
    }
    /** Check whether nh has all the attributes required to be a
        NAMEConst.*/
    bool get_is(NodeConstHandle nh) const {
      return FRAME_CHECKS;
    }
    bool get_is_static(NodeConstHandle nh) const {
      return STATIC_CHECKS;
    }
    RMF_SHOWABLE(NAMEFactory, "NAMEFactory");
  };
  #ifndef RMF_DOXYGEN
struct NAMEConstFactory: public NAMEFactory {
    NAMEConstFactory(FileConstHandle fh):
    NAMEFactory(fh) {
    }
    NAMEConstFactory(FileHandle fh):
    NAMEFactory(fh) {
    }

};
  #endif

"""


class Decorator:

    def __init__(self, allowed_types, category, name,
                 attributes,
                 init_function="", check_all_attributes=False):
        self.name = name
        self.category = category
        self.allowed_types = allowed_types
        self.init_function = init_function
        self.attributes = attributes
        self.check_all_attributes = check_all_attributes

    def _get_data_members(self):
        ret = []
        for a in self.attributes:
            ret.append(a.get_data_members())
        return "\n".join(ret)

    def _get_methods(self, const):
        ret = []
        for a in self.attributes:
            ret.append(a.get_get_set_methods(const))
        return "\n".join(ret)

    def _get_bulk_methods(self, const):
        ret = []
        for a in self.attributes:
            ret.append(a.get_bulk_methods(const))
        return "\n".join(ret)

    def _get_helpers(self):
        ret = []
        for a in self.attributes:
            ret.append(a.get_helpers())
        return "\n".join(ret)

    def _get_data_arguments(self):
        ret = []
        for a in self.attributes:
            ret.append(a.get_data_arguments())
        return ",\n".join(ret)

    def _get_data_pass(self, member):
        ret = []
        for a in self.attributes:
            ret.append(a.get_data_pass(member))
        return ",\n".join(ret)

    def _get_data_saves(self):
        ret = []
        for a in self.attributes:
            ret.append(a.get_data_saves())
        return ",\n".join(["Decorator(nh)"] + ret)

    def _get_type_check(self):
        cret = []
        for t in self.allowed_types:
            cret.append("nh.get_type() == RMF::%s" % t)
        return "(" + "||".join(cret) + ")"

    def _get_checks(self, use_all=False):
        ret = [self._get_type_check()]
        if self.check_all_attributes or use_all:
            for a in self.attributes:
                ret.append(a.get_check())
        else:
            # for a in self.attributes:
            ret.append(self.attributes[0].get_check())
        return "\n    && ".join(x for x in ret if x != "")

    def _get_construct(self):
        ret = []
        # make handle missing later
        ret.append("Category cat = fh.get_category(\""
                   + self.category + "\");")
        ret.append("RMF_UNUSED(cat);")
        for a in self.attributes:
            ret.append(a.get_construct())
        return "\n".join(ret)

    def _get_data_initialize(self):
        ret = []
        for a in self.attributes:
            ret.append(a.get_data_initialize())
        return ", ".join(ret)

    def _get_list(self):
        ret = [("HELPERS", self._get_helpers()),
               ("DATA_MEMBERS", self._get_data_members()),
               ("NONCONSTMETHODS", self._get_methods(False)),
               ("CONSTMETHODS", self._get_methods(True)),
               ("DATA_ARGUMENTS", self._get_data_arguments()),
               ("DATA_SAVES", self._get_data_saves()),
               ("DATA_PASS_ARGUMENTS", self._get_data_pass(False)),
               ("DATA_PASS", self._get_data_pass(True)),
               ("DATA_INITIALIZE", self._get_data_initialize())]
        ret.append(("CREATE_CHECKS", """RMF_USAGE_CHECK(%s, std::string("Bad node type. Got \\\"")
                                      + boost::lexical_cast<std::string>(nh.get_type())
                                      + "\\\" in decorator type  %s");"""
                    % (self._get_type_check(), self.name)))
        ret.append(
            ("FRAME_CHECKS", self._get_checks()
             .replace("GET", "get_value")))
        ret.append(
            ("STATIC_CHECKS", self._get_checks(use_all=True)
             .replace("GET", "get_static_value")))
        return ret

    def get(self):
        ret = ""
        common = [("NAME", self.name),
                  ("CATEGORY", self.category)]
        ret += replace(decorator, common + self._get_list())
        ret += replace(factory, common + self._get_list())
        return ret


def make_header(name, infos, deps):
    path = os.path.join("include", "RMF", "decorator")
    if not os.path.exists(path):
        os.makedirs(path)

    path = os.path.join("include", "RMF", "decorator", name + ".h")
    fl = open(path, "w")
    fl.write("""/**
 *  \\file RMF/decorator/%(name)s.h
 *  \\brief Helper functions for manipulating RMF files.
 *
 *  Copyright 2007-2022 IMP Inventors. All rights reserved.
 *
 */

#ifndef RMF_%(NAME)s_DECORATORS_H
#define RMF_%(NAME)s_DECORATORS_H

#include <RMF/config.h>
#include <RMF/infrastructure_macros.h>
#include <RMF/NodeHandle.h>
#include <RMF/FileHandle.h>
#include <RMF/Decorator.h>
#include <RMF/constants.h>
#include <RMF/Vector.h>
#include <RMF/internal/paths.h>
#include <array>
#include <boost/lexical_cast.hpp>
""" % {"name": name, "NAME": name.upper()})
    for d in deps:
        fl.write('#include "%s.h"\n' % d)
    fl.write("""
RMF_ENABLE_WARNINGS
namespace RMF {
namespace decorator {
""")
    for i in infos:
        fl.write(i.get() + '\n')
    fl.write("""
} /* namespace decorator */
} /* namespace RMF */
RMF_DISABLE_WARNINGS

#endif /* RMF_%(NAME)s_DECORATORS_H */
""" % {"NAME": name.upper()})

    del fl
    root = os.path.split(os.path.split(sys.argv[0])[0])[0]
    subprocess.check_call([sys.executable,
                           os.path.join(root, "dev_tools", "cleanup_code.py"),
                           path])