Explorar o código

Maintainability metric plugin implemented.

avkonst %!s(int64=11) %!d(string=hai) anos
pai
achega
7129d1c03a

+ 2 - 2
mainline/ext/std/code/complexity.py

@@ -75,8 +75,8 @@ class Plugin(mpp.api.Plugin, mpp.api.MetricPluginMixin, mpp.api.Child, mpp.api.I
 
     class MaxIndentCounter(mpp.api.MetricPluginMixin.IterAssignCounter):
         
-        def __init__(self, plugin, alias, data, region):
-            super(Plugin.MaxIndentCounter, self).__init__(plugin, alias, data, region)
+        def __init__(self, *args, **kwargs):
+            super(Plugin.MaxIndentCounter, self).__init__(*args, **kwargs)
             self.current_level = 0
             
         def assign(self, match):

+ 27 - 0
mainline/ext/std/code/mi.ini

@@ -0,0 +1,27 @@
+;
+;    Metrix++, Copyright 2009-2013, Metrix++ Project
+;    Link: http://metrixplusplus.sourceforge.net
+;    
+;    This file is a part of Metrix++ Tool.
+;    
+;    Metrix++ is free software: you can redistribute it and/or modify
+;    it under the terms of the GNU General Public License as published by
+;    the Free Software Foundation, version 3 of the License.
+;    
+;    Metrix++ is distributed in the hope that it will be useful,
+;    but WITHOUT ANY WARRANTY; without even the implied warranty of
+;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;    GNU General Public License for more details.
+;    
+;    You should have received a copy of the GNU General Public License
+;    along with Metrix++.  If not, see <http://www.gnu.org/licenses/>.
+;
+
+[Plugin]
+version: 1.0
+package: std.code
+module:  mi
+class:   Plugin
+depends: std.code.complexity, std.code.lines
+actions: collect
+enabled: True

+ 68 - 0
mainline/ext/std/code/mi.py

@@ -0,0 +1,68 @@
+#
+#    Metrix++, Copyright 2009-2013, Metrix++ Project
+#    Link: http://metrixplusplus.sourceforge.net
+#    
+#    This file is a part of Metrix++ Tool.
+#    
+#    Metrix++ is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, version 3 of the License.
+#    
+#    Metrix++ is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+#    GNU General Public License for more details.
+#    
+#    You should have received a copy of the GNU General Public License
+#    along with Metrix++.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import mpp.api
+
+class Plugin(mpp.api.Plugin,
+             mpp.api.IConfigurable,
+             mpp.api.Child,
+             mpp.api.MetricPluginMixin):
+    
+    def declare_configuration(self, parser):
+        self.parser = parser
+        parser.add_option("--std.code.mi.simple", "--scms",
+            action="store_true", default=False,
+            help="Enables collection of simple maintainability index metric."
+             " It uses std.code.line:code, std.code.complexity:cyclomatic"
+             " metrics to rank level of maintainability."
+             " Lower value of this metric indicates better maintainability."
+             " [default: %default]")
+
+    def configure(self, options):
+        self.is_active_simple = options.__dict__['std.code.mi.simple']
+        if self.is_active_simple == True:
+            required_opts = ['std.code.complexity.cyclomatic', 'std.code.lines.code']
+            for each in required_opts:
+                if options.__dict__[each] == False:
+                    self.parser.error('option --std.code.mi.simple: requires --{0} option'.
+                                      format(each))
+    
+    def initialize(self):
+        self.declare_metric(self.is_active_simple,
+                            self.Field('simple', int),
+                            {
+                             'std.code.complexity':(None, self.RankedComplexityCounter),
+                             'std.code.lines':(None, self.RankedLinesCounter),
+                            },
+                            # set none, because this plugin is not interested in parsing the code
+                            marker_type_mask=mpp.api.Marker.T.NONE)
+        
+        super(Plugin, self).initialize(fields=self.get_fields())
+
+        if self.is_active() == True:
+            self.subscribe_by_parents_name('std.code.complexity')
+            self.subscribe_by_parents_name('std.code.lines')
+
+    class RankedComplexityCounter(mpp.api.MetricPluginMixin.RankedCounter):
+        rank_source = ('std.code.complexity', 'cyclomatic')
+        rank_ranges = [(None, 7), (8, 11), (12, 19), (20, 49), (50, None)]
+    
+    class RankedLinesCounter(mpp.api.MetricPluginMixin.RankedCounter):
+        rank_source = ('std.code.lines', 'code')
+        rank_ranges = [(None, 124), (125, 249), (250, 499), (500, 999), (1000, None)]

+ 122 - 98
mainline/mpp/api.py

@@ -23,6 +23,84 @@ import sys
 import mpp.internal.dbwrap
 import mpp.internal.api_impl
 
+class InterfaceNotImplemented(Exception):
+    def __init__(self, obj):
+        Exception.__init__(self, "Method '"
+                            + sys._getframe(1).f_code.co_name
+                            + "' has not been implemented for "
+                            + str(obj.__class__))
+
+class IConfigurable(object):
+    def configure(self, options):
+        raise InterfaceNotImplemented(self)
+    def declare_configuration(self, optparser):
+        raise InterfaceNotImplemented(self)
+
+class IRunable(object):
+    def run(self, args):
+        raise InterfaceNotImplemented(self)
+    
+class IParser(object):
+    def process(self, parent, data, is_updated):
+        raise InterfaceNotImplemented(self)
+
+class ICode(object):
+    pass
+
+class CallbackNotImplemented(Exception):
+    
+    def __init__(self, obj, callback_name):
+        Exception.__init__(self, "Callback '"
+                           + callback_name
+                           + "' has not been implemented for "
+                           + str(obj.__class__))
+
+class Child(object):
+    
+    def notify(self, parent, callback_name, *args):
+        if hasattr(self, callback_name) == False:
+            raise CallbackNotImplemented(self, callback_name)
+        self.__getattribute__(callback_name)(parent, *args)
+
+    def subscribe_by_parents_name(self, parent_name, callback_name='callback'):
+        self.get_plugin(parent_name).subscribe(self, callback_name)
+    
+    def subscribe_by_parents_names(self, parent_names, callback_name='callback'):
+        for parent_name in parent_names:
+            self.get_plugin(parent_name).subscribe(self, callback_name)
+
+    def subscribe_by_parents_interface(self, interface, callback_name='callback'):
+        for plugin in self._get_plugin_loader().iterate_plugins():
+            if isinstance(plugin, interface):
+                plugin.subscribe(self, callback_name)
+
+
+class Parent(object):
+    
+    def init_Parent(self):
+        if hasattr(self, 'children') == False:
+            self.children = []
+            
+    def subscribe(self, obj, callback_name):
+        self.init_Parent()
+        if (isinstance(obj, Child) == False):
+            raise TypeError()
+        self.children.append((obj,callback_name))
+
+    def unsubscribe(self, obj, callback_name):
+        self.init_Parent()
+        self.children.remove((obj, callback_name))
+
+    def notify_children(self, *args):
+        self.init_Parent()
+        for child in self.children:
+            child[0].notify(self, child[1], *args)
+
+    def iterate_children(self):
+        self.init_Parent()
+        for child in self.children:
+            yield child
+
 ##############################################################################
 #
 # 
@@ -927,6 +1005,9 @@ class BasePlugin(object):
             return None
         return self.name
 
+    def get_namespace(self):
+        return self.get_name()
+
     def set_version(self, version):
         self.version = version
 
@@ -995,7 +1076,7 @@ class Plugin(BasePlugin):
                 # mark the plug-in as updated in order to trigger full rescan
                 self.is_updated = self.is_updated or is_created
 
-class MetricPluginMixin(object):
+class MetricPluginMixin(Parent):
 
     class AliasError(Exception):
         def __init__(self, alias):
@@ -1003,7 +1084,9 @@ class MetricPluginMixin(object):
             
     class PlainCounter(object):
         
-        def __init__(self, plugin, alias, data, region):
+        def __init__(self, namespace, field, plugin, alias, data, region):
+            self.namespace = namespace
+            self.field = field
             self.plugin = plugin
             self.alias = alias
             self.data = data
@@ -1044,6 +1127,24 @@ class MetricPluginMixin(object):
         def assign(self, match):
             return self.result
 
+    class RankedCounter(PlainCounter):
+        
+        def __init__(self, *args, **kwargs):
+            super(MetricPluginMixin.RankedCounter, self).__init__(*args, **kwargs)
+            self.result = self.region.get_data(self.namespace, self.field)
+            if self.result == None:
+                self.result = 1
+        
+        def get_result(self):
+            sourced_metric = self.region.get_data(self.rank_source[0], self.rank_source[1])
+            for (ind, range_pair) in enumerate(self.rank_ranges):
+                if ((range_pair[0] == None or sourced_metric >= range_pair[0])
+                    and
+                    (range_pair[1] == None or sourced_metric <= range_pair[1])):
+                        self.result = self.result * (ind + 1)
+                        break
+            return self.result
+
     def declare_metric(self, is_active, field,
                        pattern_to_search_or_map_of_patterns,
                        marker_type_mask=Marker.T.ANY,
@@ -1090,19 +1191,18 @@ class MetricPluginMixin(object):
         is_updated = is_updated or self.is_updated
         if is_updated == True:
             for field in self.get_fields():
-                self.count_if_active(field.name, data, alias=parent.get_name())
-        # if parent, notify children
-        if isinstance(self, Parent):
-            self.notify_children(data, is_updated)
-
-    def count_if_active(self, metric_name, data, namespace=None, alias='*'):
-        if self.is_active(metric_name) == False:
+                self.count_if_active(self.get_namespace(),
+                                     field.name,
+                                     data,
+                                     alias=parent.get_name())
+        # this mixin implements parent interface
+        self.notify_children(data, is_updated)
+
+    def count_if_active(self, namespace, field, data, alias='*'):
+        if self.is_active(field) == False:
             return
         
-        if namespace == None:
-            namespace = self.get_name()
-            
-        field_data = self._fields[metric_name]
+        field_data = self._fields[field]
         if alias not in field_data[4].keys():
             if '*' not in field_data[4].keys():
                 raise self.AliasError(alias)
@@ -1111,93 +1211,17 @@ class MetricPluginMixin(object):
         (pattern_to_search, counter_class) = field_data[4][alias]
         
         for region in data.iterate_regions(filter_group=field_data[5]):
-            counter = counter_class(self, alias, data, region)
-            for marker in data.iterate_markers(
-                            filter_group = field_data[1],
-                            region_id = region.get_id(),
-                            exclude_children = field_data[2],
-                            merge=field_data[3]):
-                counter.count(marker, pattern_to_search)
+            counter = counter_class(namespace, field, self, alias, data, region)
+            if field_data[1] != Marker.T.NONE:
+                for marker in data.iterate_markers(
+                                filter_group = field_data[1],
+                                region_id = region.get_id(),
+                                exclude_children = field_data[2],
+                                merge=field_data[3]):
+                    counter.count(marker, pattern_to_search)
             count = counter.get_result()
             if count != 0 or field_data[0].non_zero == False:
-                region.set_data(namespace, metric_name, count)
-
-class InterfaceNotImplemented(Exception):
-    def __init__(self, obj):
-        Exception.__init__(self, "Method '"
-                            + sys._getframe(1).f_code.co_name
-                            + "' has not been implemented for "
-                            + str(obj.__class__))
-
-class IConfigurable(object):
-    def configure(self, options):
-        raise InterfaceNotImplemented(self)
-    def declare_configuration(self, optparser):
-        raise InterfaceNotImplemented(self)
-
-class IRunable(object):
-    def run(self, args):
-        raise InterfaceNotImplemented(self)
-    
-class IParser(object):
-    def process(self, parent, data, is_updated):
-        raise InterfaceNotImplemented(self)
-
-class ICode(object):
-    pass
-
-class CallbackNotImplemented(Exception):
-    
-    def __init__(self, obj, callback_name):
-        Exception.__init__(self, "Callback '"
-                           + callback_name
-                           + "' has not been implemented for "
-                           + str(obj.__class__))
-
-class Child(object):
-    
-    def notify(self, parent, callback_name, *args):
-        if hasattr(self, callback_name) == False:
-            raise CallbackNotImplemented(self, callback_name)
-        self.__getattribute__(callback_name)(parent, *args)
+                region.set_data(namespace, field, count)
 
-    def subscribe_by_parents_name(self, parent_name, callback_name='callback'):
-        self.get_plugin(parent_name).subscribe(self, callback_name)
-    
-    def subscribe_by_parents_names(self, parent_names, callback_name='callback'):
-        for parent_name in parent_names:
-            self.get_plugin(parent_name).subscribe(self, callback_name)
-
-    def subscribe_by_parents_interface(self, interface, callback_name='callback'):
-        for plugin in self._get_plugin_loader().iterate_plugins():
-            if isinstance(plugin, interface):
-                plugin.subscribe(self, callback_name)
-
-
-class Parent(object):
-    
-    def init_Parent(self):
-        if hasattr(self, 'children') == False:
-            self.children = []
-            
-    def subscribe(self, obj, callback_name):
-        self.init_Parent()
-        if (isinstance(obj, Child) == False):
-            raise TypeError()
-        self.children.append((obj,callback_name))
-
-    def unsubscribe(self, obj, callback_name):
-        self.init_Parent()
-        self.children.remove((obj, callback_name))
-
-    def notify_children(self, *args):
-        self.init_Parent()
-        for child in self.children:
-            child[0].notify(self, child[1], *args)
-
-    def iterate_children(self):
-        self.init_Parent()
-        for child in self.children:
-            yield child
 
 

+ 6 - 0
mainline/tests/general/test_basic/test_help_collect_default_stdout.gold.txt

@@ -56,6 +56,12 @@ Options:
   --std.code.magic.numbers.simplier, --scmns
                         Is set, 0, -1 and 1 numbers are not counted in
                         'std.code.magic.numbers' metric [default: False]
+  --std.code.mi.simple, --scms
+                        Enables collection of simple maintainability index
+                        metric. It uses std.code.line:code,
+                        std.code.complexity:cyclomatic metrics to rank level
+                        of maintainability. Lower value of this metric
+                        indicates better maintainability. [default: False]
   --std.code.todo.comments, --sctc
                         Enables collection of number of todo/fixme/etc markers
                         in comments [default: False]