123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893 |
- <!DOCTYPE html>
- <!--
- Metrix++, Copyright 2009-2013, Metrix++ Project
- Link: http://metrixplusplus.sourceforge.net
-
- This file is 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/>.
- -->
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <title>Metrix++ Project</title>
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="">
- <meta name="author" content="">
- <!-- Le styles -->
- <!--
- <link href="../../style.css" rel="stylesheet">
- -->
- <link href="assets/css/bootstrap.css" rel="stylesheet">
- <link href="assets/css/bootstrap-responsive.css" rel="stylesheet">
- <link href="assets/css/docs.css" rel="stylesheet">
- <link href="assets/js/google-code-prettify/prettify.css" rel="stylesheet">
-
- <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
- <!--[if lt IE 9]>
- <script src="assets/js/html5shiv.js"></script>
- <![endif]-->
- <!-- Le fav and touch icons -->
- <link rel="apple-touch-icon-precomposed" sizes="144x144" href="assets/ico/apple-touch-icon-144-precomposed.png">
- <link rel="apple-touch-icon-precomposed" sizes="114x114" href="assets/ico/apple-touch-icon-114-precomposed.png">
- <link rel="apple-touch-icon-precomposed" sizes="72x72" href="assets/ico/apple-touch-icon-72-precomposed.png">
- <link rel="apple-touch-icon-precomposed" href="assets/ico/apple-touch-icon-57-precomposed.png">
- <link rel="shortcut icon" href="assets/ico/favicon.png">
- <!--
- <script type="text/javascript">
- var _gaq = _gaq || [];
- _gaq.push(['_setAccount', 'UA-146052-10']);
- _gaq.push(['_trackPageview']);
- (function() {
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
- })();
- </script>
- -->
- </head>
- <body data-spy="scroll" data-target=".bs-docs-sidebar">
- <!-- Navbar
- ================================================== -->
- <div class="navbar navbar-link navbar-fixed-top">
- <div class="navbar-inner">
- <div class="container">
- <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- <a class="brand" href="./index.html">Metrix++</a>
- <div class="nav-collapse collapse">
- <ul class="nav">
- <li class="">
- <a href="./index.html">Home</a>
- </li>
- <li class="">
- <a href="./workflow.html">Workflow</a>
- </li>
- <li class="">
- <a href="./extend.html">Create plugin</a>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </div>
- <!-- Subhead
- ================================================== -->
- <header class="jumbotron" id="overview">
- <div class="container">
- <div class="row">
- <div class="span3"></div>
- <div class="span9">
- <h5 class="text-right">Management of source code quality is possible.</h5>
- <p class="text-right">
- <a href="https://sourceforge.net/projects/metrixplusplus/files/latest/download"
- ><button type="button"class="btn btn-danger">Download</button></a>
- <!--
- <button type="button"class="btn btn-warning">Donate</button>
- -->
- </p>
- </div>
- </div>
- </div>
- </header>
- <div class="container"><div class="row">
-
- <!-- Docs nav
- ================================================== -->
- <div class="span3 bs-docs-sidebar">
- <ul class="nav nav-list bs-docs-sidenav">
- <li><a href="#metric_plugin"><i class="icon-chevron-right"></i> Metric plugin</a></li>
- <li><a href="#tool_plugin"><i class="icon-chevron-right"></i> Post-analysis tool</a></li>
- <li><a href="#language_plugin"><i class="icon-chevron-right"></i> Language parser</a></li>
- </ul>
- </div>
-
- <!-- Sections
- ================================================== -->
- <div class="span9">
- <div class="page-header">
- <h1>Create plugin</h1>
- </div>
- <p>There are 3 types of plugins considered in this chapter:</p>
- <ul>
- <li>Metric plugin</li>
- <li>Language parser</li>
- <li>Post-processing / Analysis tool</li>
- </ul>
- <p>Tutorial for metric plugin is generic at the beginning and large portion of this is applied to
- all other plugins. You need to know python and python regular expressions library to write Metrix++ extensions.</p>
- <section id="metric_plugin">
- <h2>Metric plugin</h2>
- <p>The tutorial will explain how to create a plugin to count magic numbers in source code.
- It will be relatively simple at first and will be extended with additional configuration
- options and smarter counting logic.</p>
- <h4>Create placeholder for new plugin</h4>
- <ol>
- <li>All plugins are loaded by Metrix++ from standard places within the tool installation directory and
- from custom places specified in the METRIXPLUSPLUS_PATH environment variable.
- METRIXPLUSPLUS_PATH has got the same format as system PATH environment variable.
- So, the first step in plugin development is to set the METRIXPLUSPLUS_PATH to point out to
- the directory (or directories) where plugin is located.</li>
- <li>Create new python package 'myext', python lib 'magic.py' and 'magic.ini' file.</li>
- <pre>
- + working_directory (set in METRIXPLUSPLUS_PATH variable)
- \--+ myext
- \--- __init__.py
- \--- magic.py
- \--- magic.ini
- </pre>
- <li>__init__.py is empty file to make myext considered by python as a package.</li>
- <li>Edit magic.py to have the following content:
- <pre class="prettyprint linenums">
- import mpp.api
- class Plugin(mpp.api.Plugin):
-
- def initialize(self):
- print "Hello world"
- </pre>
- mpp.api package include Metrix++ API classes. mpp.api.Plugin is the base class, which can be loaded
- by Metrix++ engine and does nothing by default. In the code sample above it is extended to print
- "Hello world" on initialization.</li>
- <li>Edit magic.ini to have the following content:
- <pre class="prettyprint linenums">
- [Plugin]
- version: 1.0
- package: myext
- module: magic
- class: Plugin
- depends: None
- actions: collect
- enabled: True
- </pre>
- This file is a manifest for Metrix++ plugin loader. The fields in Plugin section are:
- <dl class="dl-horizontal">
- <dt>version</dt>
- <dd>a string representing the version, step up it every time when behaviour of a plugin
- or backward compatibility in api or data scope is changed</dd>
- <dt>package</dt>
- <dd>python package name where to load from</dd>
- <dt>module</dt>
- <dd>python module name (filename of *.py file) to load</dd>
- <dt>class</dt>
- <dd>name of a plugin class to instanciate</dd>
- <dt>depends</dt>
- <dd>list of plugin names to load, if it this plugin is loaded</dd>
- <dt>actions</dt>
- <dd>list of Metrix++ actions affected by this plugin</dd>
- <dt>enabled</dt>
- <dd>True or False, working status of a plugin</dd>
- </dl>
- </li>
- <li>Now run Metrix++ to see how this new plugin works:</li>
- <pre>> python "/path/to/metrix++.py" collect</pre>
- <pre>Hello world</pre>
- </ol>
- <h4>Toogle option for the plugin</h4>
- <ol>
- <li>It is recommended to follow the convention for all plugins: 'run only if enabled'.
- So, let's extend the magic.py file to make it configurable
- <pre class="prettyprint linenums">
- import mpp.api
- class Plugin(mpp.api.Plugin,
- # make this instance configurable...
- mpp.api.IConfigurable):
- # ... and implement 2 interfaces
-
- def declare_configuration(self, parser):
- parser.add_option("--myext.magic.numbers", "--mmn",
- action="store_true", default=False,
- help="Enables collection of magic numbers metric [default: %default]")
-
- def configure(self, options):
- self.is_active_numbers = options.__dict__['myext.magic.numbers']
-
- def initialize(self):
- # use configuration option here
- if self.is_active_numbers == True:
- print "Hello world"
- </pre>
- parser argument is an instance of optparse.OptionParser class. It has got an extension to
- accept multiple options of the same argument. Check std.tools.limit to see how to declare multiopt options, if you need.</li>
- <li>Now run Metrix++ to see how this works:</li>
- <pre>> python "/path/to/metrix++.py" collect --myext.magic.numbers</pre>
- <pre>Hello world</pre>
- </ol>
- <h4>Subscribe to notifications from parent plugins (or code parsers)</h4>
- <ol>
- <li>Every plugin works in a callback functions called by parent plugins.
- Callback receives a reference to parent plugin, data object where to store metrics data,
- and a flag indicating if there are changes in file or parent's settings since the previous collection.</li>
- <pre class="prettyprint linenums">
- import mpp.api
- class Plugin(mpp.api.Plugin,
- mpp.api.IConfigurable,
- # declare that it can subscribe on notifications
- mpp.api.Child):
-
- def declare_configuration(self, parser):
- parser.add_option("--myext.magic.numbers", "--mmn",
- action="store_true", default=False,
- help="Enables collection of magic numbers metric [default: %default]")
-
- def configure(self, options):
- self.is_active_numbers = options.__dict__['myext.magic.numbers']
-
- def initialize(self):
- if self.is_active_numbers == True:
- # subscribe to notifications from all code parsers
- self.subscribe_by_parents_interface(mpp.api.ICode, 'callback')
- # parents (code parsers) will call the callback declared
- def callback(self, parent, data, is_updated):
- print parent.get_name(), data.get_path(), is_updated
- </pre>
- <li>Now run Metrix++ to see how this works. Try to do iterative scans (--db-file-prev option) to see how the
- state of arguments is changed</li>
- <pre>> python "/path/to/metrix++.py" collect --myext.magic.numbers</pre>
- <pre>std.code.cpp ./test.cpp True</pre>
- </ol>
- <h4>Implement simple metric based on regular expression pattern</h4>
- <ol>
- <li>Callback may execute counting, searcing and additional parsing and store results, using data argument.
- 'data' argument is an instance of mpp.api.FileData class.
- However, most metrics can be implemented
- simplier, if mpp.api.MetricPluginMixin routines are used. MetricPluginMixin implements
- declarative style for metrics based on searches by regular expression. It
- cares about initialisation of database fields and properties.
- It implements default callback which counts number of matches by regular expression for all
- active declared metrics. So, let's utilise that:
- </li>
- <pre class="prettyprint linenums">
- import mpp.api
- import re
- class Plugin(mpp.api.Plugin,
- mpp.api.IConfigurable,
- mpp.api.Child,
- # reuse by inheriting standard metric facilities
- mpp.api.MetricPluginMixin):
-
- def declare_configuration(self, parser):
- parser.add_option("--myext.magic.numbers", "--mmn",
- action="store_true", default=False,
- help="Enables collection of magic numbers metric [default: %default]")
-
- def configure(self, options):
- self.is_active_numbers = options.__dict__['myext.magic.numbers']
-
- def initialize(self):
- # declare metric rules
- self.declare_metric(
- self.is_active_numbers, # to count if active in callback
- self.Field('numbers', int), # field name and type in the database
- re.compile(r'''\b[0-9]+\b'''), # pattern to search
- marker_type_mask=mpp.api.Marker.T.CODE, # search in code
- region_type_mask=mpp.api.Region.T.ANY) # search in all types of regions
-
- # use superclass facilities to initialize everything from declared fields
- super(Plugin, self).initialize(fields=self.get_fields())
-
- # subscribe to all code parsers if at least one metric is active
- if self.is_active() == True:
- self.subscribe_by_parents_interface(mpp.api.ICode)
- </pre>
- <li>Now run Metrix++ to count numbers in code files.</li>
- <pre>> python "/path/to/metrix++.py" collect --myext.magic.numbers</pre>
- <li>Now view the results. At this stage it is fully working simple metric.</li>
- <pre>> python "/path/to/metrix++.py" view</pre>
- <pre>
- :: info: Overall metrics for 'myext.magic:numbers' metric
- Average : 2.75
- Minimum : 0
- Maximum : 7
- Total : 11.0
- Distribution : 4 regions in total (including 0 suppressed)
- Metric value : Ratio : R-sum : Number of regions
- 0 : 0.250 : 0.250 : 1 |||||||||||||||||||||||||
- 1 : 0.250 : 0.500 : 1 |||||||||||||||||||||||||
- 3 : 0.250 : 0.750 : 1 |||||||||||||||||||||||||
- 7 : 0.250 : 1.000 : 1 |||||||||||||||||||||||||
- :: info: Directory content:
- Directory : .
- </pre>
- </ol>
- <h4>Extend regular expression incremental counting by smarter logic</h4>
- <ol>
- <li>At this stage the metric counts every number in source code.
- However, we indent to spot only 'magic' numbers. Declared constant
- is not a magic number, so it is better to exclude constants from counting.
- It is easy to change default counter behaviour by implementing
- a function with name '_<metric_name>_count'. </li>
- <pre class="prettyprint linenums">
- import mpp.api
- import re
- class Plugin(mpp.api.Plugin,
- mpp.api.IConfigurable,
- mpp.api.Child,
- mpp.api.MetricPluginMixin):
-
- def declare_configuration(self, parser):
- parser.add_option("--myext.magic.numbers", "--mmn",
- action="store_true", default=False,
- help="Enables collection of magic numbers metric [default: %default]")
-
- def configure(self, options):
- self.is_active_numbers = options.__dict__['myext.magic.numbers']
-
- def initialize(self):
- # improve pattern to find declarations of constants
- pattern_to_search = re.compile(
- r'''((const(\s+[_a-zA-Z][_a-zA-Z0-9]*)+\s*[=]\s*)[-+]?[0-9]+\b)|(\b[0-9]+\b)''')
- self.declare_metric(self.is_active_numbers,
- self.Field('numbers', int),
- # give a pair of pattern + custom counter logic class
- (pattern_to_search, self.NumbersCounter),
- marker_type_mask=mpp.api.Marker.T.CODE,
- region_type_mask=mpp.api.Region.T.ANY)
-
- super(Plugin, self).initialize(fields=self.get_fields())
-
- if self.is_active() == True:
- self.subscribe_by_parents_interface(mpp.api.ICode)
-
- # implement custom counter behavior:
- # increments counter by 1 only if single number spotted,
- # but not declaration of a constant
- class NumbersCounter(mpp.api.MetricPluginMixin.IterIncrementCounter):
- def increment(self, match):
- if match.group(0).startswith('const'):
- return 0
- return 1
- </pre>
- <li>Initialy counter is initialized by zero, but it is possible to
- change it as well by implementing a function with name '_<metric_name>_count_initialize'.
- Plugin we are implementing does not require this.</li>
- <li>Now run Metrix++ to collect and view the results.</li>
- <pre>> python "/path/to/metrix++.py" collect --myext.magic.numbers</pre>
- <pre>> python "/path/to/metrix++.py" view</pre>
- </ol>
- <h4>Language specific regular expressions</h4>
- <ol>
- <li>In the previous step we added matching of constants assuming that identifiers
- may have symbols '_', 'a-z', 'A-Z' and '0-9'. It is true for C++ but it is not complete for Java.
- Java identifier may have '$' symbol in the identifier. So, let's add language specific pattern
- in the declaration of the metric:</li>
- <pre class="prettyprint linenums">
- import mpp.api
- import re
- class Plugin(mpp.api.Plugin,
- mpp.api.IConfigurable,
- mpp.api.Child,
- mpp.api.MetricPluginMixin):
-
- def declare_configuration(self, parser):
- parser.add_option("--myext.magic.numbers", "--mmn",
- action="store_true", default=False,
- help="Enables collection of magic numbers metric [default: %default]")
-
- def configure(self, options):
- self.is_active_numbers = options.__dict__['myext.magic.numbers']
-
- def initialize(self):
- # specialized pattern for java
- pattern_to_search_java = re.compile(
- r'''((const(\s+[_$a-zA-Z][_$a-zA-Z0-9]*)+\s*[=]\s*)[-+]?[0-9]+\b)|(\b[0-9]+\b)''')
- # pattern for C++ and C# languages
- pattern_to_search_cpp_cs = re.compile(
- r'''((const(\s+[_a-zA-Z][_a-zA-Z0-9]*)+\s*[=]\s*)[-+]?[0-9]+\b)|(\b[0-9]+\b)''')
- # pattern for all other languages
- pattern_to_search = re.compile(
- r'''\b[0-9]+\b''')
- self.declare_metric(self.is_active_numbers,
- self.Field('numbers', int),
- # dictionary of pairs instead of a single pair
- {
- 'std.code.java': (pattern_to_search_java, self.NumbersCounter),
- 'std.code.cpp': (pattern_to_search_cpp_cs, self.NumbersCounter),
- 'std.code.cs': (pattern_to_search_cpp_cs, self.NumbersCounter),
- '*': pattern_to_search
- },
- marker_type_mask=mpp.api.Marker.T.CODE,
- region_type_mask=mpp.api.Region.T.ANY)
-
- super(Plugin, self).initialize(fields=self.get_fields())
-
- if self.is_active() == True:
- self.subscribe_by_parents_interface(mpp.api.ICode)
- class NumbersCounter(mpp.api.MetricPluginMixin.IterIncrementCounter):
- def increment(self, match):
- if match.group(0).startswith('const'):
- return 0
- return 1
- </pre>
- <li>Keys in the dictionary of patterns are names of parent plugins (references to code parsers).
- The key '*' refers to any parser.</li>
- <li>Now run Metrix++ to collect and view the results.</li>
- <pre>> python "/path/to/metrix++.py" collect --myext.magic.numbers</pre>
- <pre>> python "/path/to/metrix++.py" view</pre>
- </ol>
- <h4>Store only non-zero metric values</h4>
- <ol>
- <li>Most functions have the metric, which we are implemneting, equal to zero.
- However, we are interested in finding code blocks having this metric greater than zero.
- Zeros consumes the space in the data file. So, we can optimise the size of a data file,
- if we exclude zero metric values. Let's declare this behavior for the metric.</li>
- <pre class="prettyprint linenums">
- import mpp.api
- import re
- class Plugin(mpp.api.Plugin,
- mpp.api.IConfigurable,
- mpp.api.Child,
- mpp.api.MetricPluginMixin):
-
- def declare_configuration(self, parser):
- parser.add_option("--myext.magic.numbers", "--mmn",
- action="store_true", default=False,
- help="Enables collection of magic numbers metric [default: %default]")
-
- def configure(self, options):
- self.is_active_numbers = options.__dict__['myext.magic.numbers']
-
- def initialize(self):
- pattern_to_search_java = re.compile(
- r'''((const(\s+[_$a-zA-Z][_$a-zA-Z0-9]*)+\s*[=]\s*)[-+]?[0-9]+\b)|(\b[0-9]+\b)''')
- pattern_to_search_cpp_cs = re.compile(
- r'''((const(\s+[_a-zA-Z][_a-zA-Z0-9]*)+\s*[=]\s*)[-+]?[0-9]+\b)|(\b[0-9]+\b)''')
- pattern_to_search = re.compile(
- r'''\b[0-9]+\b''')
- self.declare_metric(self.is_active_numbers,
- self.Field('numbers', int,
- # optimize the size of datafile:
- # store only non-zero results
- non_zero=True),
- {
- 'std.code.java': (pattern_to_search_java, self.NumbersCounter),
- 'std.code.cpp': (pattern_to_search_cpp_cs, self.NumbersCounter),
- 'std.code.cs': (pattern_to_search_cpp_cs, self.NumbersCounter),
- '*': pattern_to_search
- },
- marker_type_mask=mpp.api.Marker.T.CODE,
- region_type_mask=mpp.api.Region.T.ANY)
-
- super(Plugin, self).initialize(fields=self.get_fields())
-
- if self.is_active() == True:
- self.subscribe_by_parents_interface(mpp.api.ICode)
- class NumbersCounter(mpp.api.MetricPluginMixin.IterIncrementCounter):
- def increment(self, match):
- if match.group(0).startswith('const'):
- return 0
- return 1
- </pre>
- <li>Now run Metrix++ to collect and view the results.</li>
- <pre>> python "/path/to/metrix++.py" collect --myext.magic.numbers</pre>
- <pre>> python "/path/to/metrix++.py" view</pre>
- </ol>
- <h4>Additional per metric configuration options</h4>
- <ol>
- <li>It is typical that most numbers counted by the metric are equal to 0, -1 or 1.
- They are not necessary magic numbers. 0 or 1 are typical variable initializers.
- -1 is a typical negative return code. So, let's implement simplified version of the metric,
- which does not count 0, -1 and 1, if the specific new option is set.</li>
- <pre class="prettyprint linenums">
- import mpp.api
- import re
- class Plugin(mpp.api.Plugin,
- mpp.api.IConfigurable,
- mpp.api.Child,
- mpp.api.MetricPluginMixin):
-
- def declare_configuration(self, parser):
- parser.add_option("--myext.magic.numbers", "--mmn",
- action="store_true", default=False,
- help="Enables collection of magic numbers metric [default: %default]")
- # Add new option
- parser.add_option("--myext.magic.numbers.simplier", "--mmns",
- action="store_true", default=False,
- help="Is set, 0, -1 and 1 numbers are not counted [default: %default]")
-
- def configure(self, options):
- self.is_active_numbers = options.__dict__['myext.magic.numbers']
- # remember the option here
- self.is_active_numbers_simplier = options.__dict__['myext.magic.numbers.simplier']
-
- def initialize(self):
- pattern_to_search_java = re.compile(
- r'''((const(\s+[_$a-zA-Z][_$a-zA-Z0-9]*)+\s*[=]\s*)[-+]?[0-9]+\b)|(\b[0-9]+\b)''')
- pattern_to_search_cpp_cs = re.compile(
- r'''((const(\s+[_a-zA-Z][_a-zA-Z0-9]*)+\s*[=]\s*)[-+]?[0-9]+\b)|(\b[0-9]+\b)''')
- pattern_to_search = re.compile(
- r'''\b[0-9]+\b''')
- self.declare_metric(self.is_active_numbers,
- self.Field('numbers', int,
- non_zero=True),
- {
- 'std.code.java': (pattern_to_search_java, self.NumbersCounter),
- 'std.code.cpp': (pattern_to_search_cpp_cs, self.NumbersCounter),
- 'std.code.cs': (pattern_to_search_cpp_cs, self.NumbersCounter),
- '*': pattern_to_search
- },
- marker_type_mask=mpp.api.Marker.T.CODE,
- region_type_mask=mpp.api.Region.T.ANY)
-
- super(Plugin, self).initialize(fields=self.get_fields(),
- # remember option settings in data file properties
- # in order to detect changes in settings on iterative re-run
- properties=[self.Property('number.simplier', self.is_active_numbers_simplier)])
-
- if self.is_active() == True:
- self.subscribe_by_parents_interface(mpp.api.ICode)
- class NumbersCounter(mpp.api.MetricPluginMixin.IterIncrementCounter):
- def increment(self, match):
- if (match.group(0).startswith('const') or
- (self.plugin.is_active_numbers_simplier == True and
- match.group(0) in ['0', '1', '-1', '+1'])):
- return 0
- return 1
- </pre>
- <li>Now run Metrix++ to collect and view the results.</li>
- <pre>> python "/path/to/metrix++.py" collect --myext.magic.numbers</pre>
- <pre>> python "/path/to/metrix++.py" view</pre>
- <pre>
- :: info: Overall metrics for 'myext.magic:numbers' metric
- Average : 2.5 (excluding zero metric values)
- Minimum : 2
- Maximum : 3
- Total : 5.0
- Distribution : 2 regions in total (including 0 suppressed)
- Metric value : Ratio : R-sum : Number of regions
- 2 : 0.500 : 0.500 : 1 ||||||||||||||||||||||||||||||||||||||||||||||||||
- 3 : 0.500 : 1.000 : 1 ||||||||||||||||||||||||||||||||||||||||||||||||||
- :: info: Directory content:
- Directory : .
- </pre>
- </ol>
- <h4>Summary</h4>
- <p>We have finished with the tutorial. The tutorial explained how to implement simple and advanced metric plugins.
- We used built-in Metrix++ base classes. If you need to more advanced plugin capabilities,
- override in your plugin class functions inherited from mpp.api base classes. Check code of standard plugins
- to learn more techniques.</p>
- <p></p>
- </section>
- <section id="tool_plugin">
- <h2>Analysis tool plugin</h2>
- <p>This tutorial will explain how to build custom Metrix++ command, which is bound to custom post-analysis tool.
- We will implement the tool, which identifies all new and changed regions and counts number of added lines.
- We skip calculating number of deleted lines, but it is easy to extend from what we get finally in the tutorial.</p>
- <h4>New Metrix++ command / action</h4>
- <ol>
- <li>As in the tutorial for metric plugin, set the environment and
- create new python package 'myext', python lib 'compare.py' and 'compare.ini' file.</li>
- <pre>
- + working_directory (set in METRIXPLUSPLUS_PATH variable)
- \--+ myext
- \--- __init__.py
- \--- compare.py
- \--- compare.ini
- </pre>
- <li>__init__.py is empty file to make myext considered by python as a package.</li>
- <li>Edit compare.py to have the following content:
- <pre class="prettyprint linenums">
- import mpp.api
- class Plugin(mpp.api.Plugin, mpp.api.IRunable):
-
- def run(self, args):
- print args
- return 0
- </pre>
- Inheritance from mpp.api.IRunable declares that the plugin is runable and requires implementation of 'run' interface.</li>
- <li>Edit compare.ini to have the following content:
- <pre class="prettyprint linenums">
- [Plugin]
- version: 1.0
- package: myext
- module: compare
- class: Plugin
- depends: None
- actions: compare
- enabled: True
- </pre>
- This file is a manifest for Metrix++ plugin loader. Actions field has got new value 'compare'.
- Metrix++ engine will automatically pick this action and will add it to the list of available commands.
- This plugin will be loaded on 'compare' action.
- </li>
- <li>Now run Metrix++ to see how this new plugin works:</li>
- <pre>> python "/path/to/metrix++.py" compare -- path1 path2 path3</pre>
- <pre>["path1", "path2", "path3"]</pre>
- </ol>
- <h4>Access data file loader and its' interfaces</h4>
- <ol>
- <li>By default, all post-analysis tools have got --db-file and --db-file-prev options. It is
- because 'mpp.dbf' plugin is loaded for any action, including our new one 'compare'. In order to continue
- the tutorial, we need to have 2 data files with 'std.code.lines:total' metric collected.
- So, write to files by running:</li>
- <pre>cd my_project_version_1
- > python "/path/to/metrix++.py" collect --std.code.lines.total</pre>
- <pre>cd my_project_version_2
- > python "/path/to/metrix++.py" collect --std.code.lines.total</pre>
- <li>Edit compare.py file to get the loader and iterate collected file paths:</li>
- <pre class="prettyprint linenums">
- import mpp.api
- # load common utils for post processing tools
- import mpp.utils
- class Plugin(mpp.api.Plugin, mpp.api.IRunable):
-
- def run(self, args):
- # get data file reader using standard metrix++ plugin
- loader = self.get_plugin('mpp.dbf').get_loader()
-
- # iterate and print file length for every path in args
- exit_code = 0
- for path in (args if len(args) > 0 else [""]):
- file_iterator = loader.iterate_file_data(path)
- if file_iterator == None:
- mpp.utils.report_bad_path(path)
- exit_code += 1
- continue
- for file_data in file_iterator:
- print file_data.get_path()
- return exit_code
- </pre>
- <li>Now run Metrix++ to see how it works:</li>
- <pre>> python "/path/to/metrix++.py" compare --db-file=my_project_version_2/metrixpp.db --db-file-prev=my_project_version_1/metrixpp.db</pre>
- </ol>
- <h4>Identify added, modified files/regions and read metric data</h4>
- <ol>
- <li>Let's extend the logic of the tool to compare files and regions, read 'std.code.lines:total' metric
- and calcuate the summary of number of added lines. mpp.utils.FileRegionsMatcher is helper class
- which does matching and comparison of regions for 2 given mpp.api.FileData objects.</li>
- <pre class="prettyprint linenums">
- import mpp.api
- import mpp.utils
- import mpp.cout
- class Plugin(mpp.api.Plugin, mpp.api.IRunable):
-
- def run(self, args):
- loader = self.get_plugin('mpp.dbf').get_loader()
- # get previous db file loader
- loader_prev = self.get_plugin('mpp.dbf').get_loader_prev()
-
- exit_code = 0
- for path in (args if len(args) > 0 else [""]):
- added_lines = 0
- file_iterator = loader.iterate_file_data(path)
- if file_iterator == None:
- mpp.utils.report_bad_path(path)
- exit_code += 1
- continue
- for file_data in file_iterator:
- added_lines += self._compare_file(file_data, loader, loader_prev)
- mpp.cout.notify(path, '', mpp.cout.SEVERITY_INFO,
- "Change trend report",
- [('Added lines', added_lines)])
- return exit_code
- def _compare_file(self, file_data, loader, loader_prev):
- # compare file with previous and return number of new lines
- file_data_prev = loader_prev.load_file_data(file_data.get_path())
- if file_data_prev == None:
- return self._sum_file_regions_lines(file_data)
- elif file_data.get_checksum() != file_data_prev.get_checksum():
- return self._compare_file_regions(file_data, file_data_prev)
- def _sum_file_regions_lines(self, file_data):
- # just sum up the metric for all regions
- result = 0
- for region in file_data.iterate_regions():
- result += region.get_data('std.code.lines', 'total')
-
- def _compare_file_regions(self, file_data, file_data_prev):
- # compare every region with previous and return number of new lines
- matcher = mpp.utils.FileRegionsMatcher(file_data, file_data_prev)
- result = 0
- for region in file_data.iterate_regions():
- if matcher.is_matched(region.get_id()) == False:
- # if added region, just add the lines
- result += region.get_data('std.code.lines', 'total')
- elif matcher.is_modified(region.get_id()):
- # if modified, add the difference in lines
- region_prev = file_data_prev.get_region(
- matcher.get_prev_id(region.get_id()))
- result += (region.get_data('std.code.lines', 'total') -
- region_prev.get_data('std.code.lines', 'total'))
- return result
- </pre>
- <li>Now run Metrix++ to see how it works:</li>
- <pre>> python "/path/to/metrix++.py" compare --db-file=my_project_version_2/metrixpp.db --db-file-prev=my_project_version_1/metrixpp.db</pre>
- <pre>
- :: info: Change trend report
- Added lines : 7
- </pre>
- </ol>
- <h4>Summary</h4>
- <p>We have finished with the tutorial. The tutorial explained how to read Metrix++ data files and
- implement custom post-processing tools. Even if some existing Metrix++ code requires clean-up and refactoring,
- check code of standard tool plugins to learn more techniques.</p>
- </section>
- <section id="language_plugin">
- <h2>Language parser plugin</h2>
- <p>Unfortunately, there is no good documentation at this stage for this part.
- Briefly, if metric plugin counts and stores data into FileData object,
- tool plugin reads this data, language plugin construct the original structure of
- FileData object. The orginal structure includes regions (like functions, classes, etc.)
- and markers (like comments, strings, preprocessor, etc.).
- Check code of existing parsers.</p>
- <ul>
- <li>a language parser plugin is registered in the same way as a metric plugin</li>
- <li>it registers parser's callback in 'std.tools.collect' plugin</li>
- <li>parses a file in a callback, called by 'std.tools.collect'</li>
- <li>parser needs to identify markers and regions
- and tell about this to file data object passed as an
- argument for the callback.</li>
- </ul>
- <p>There are useful options and tools avaialble for
- trobuleshooting purposes during development:</p>
- <ul>
- <li>metrix++.py debug generates html code showing parsed code structures and their boundaries</li>
- <li>--nest-regions for view tool forces the viewer to indent subregions.</li>
- <li>--log-level option is available for any command and is helpful to trace execution.</li>
- </ul>
- <p>Finally, if there are any questions or enquires, please,
- feel free to <a href="https://sourceforge.net/p/metrixplusplus/tickets/new/">submit new question</a>.</p>
- </section>
- </div> <!-- end for sections -->
- </div></div> <!-- end for row and container -->
- <!-- Footer
- ================================================== -->
- <footer class="footer">
- <div class="container">
- <div class="row">
- <div class="span3">
- <p><a href="http://sourceforge.net/projects/metrixplusplus/"><img src="http://sflogo.sourceforge.net/sflogo.php?group_id=275605&type=3"
- alt="Get Metrix++ at SourceForge.net. Fast, secure and Free Open Source software downloads" border="0"></a></p>
- <p>·</p>
- <p>· ·<script type="text/javascript" src="http://www.ohloh.net/p/485947/widgets/project_users_logo.js"></script></p>
- <p><a href="http://freecode.com/projects/metrix"><img src="assets/img/fm_logo.png" width="130"></a></p>
- <p>·</p>
- <p><a href="http://www.softpedia.com/progClean/Metrix-Clean-241097.html"><img src="assets/img/softpedia_free_award_f.gif" width="147" /></a></p>
- </div>
- <div class="span9">
- <p>Copyright <strong>©</strong> 2009 - 2013, <a href="mailto:avkonst@users.sourceforge.net"><span class="normalImportance">Metrix++</span> Project</a></p>
- <p>Code licensed under <a href="http://www.gnu.org/licenses/gpl.txt" target="_blank">GPL 3.0</a>, documentation under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.</p>
- <ul class="footer-links">
- <li><a href="https://sourceforge.net/p/metrixplusplus/tickets/new/">Ask question</a></li>
- <li class="muted">·</li>
- <li><a href="https://sourceforge.net/p/metrixplusplus/tickets/new/">Report defect</a></li>
- <li class="muted">·</li>
- <li><a href="https://sourceforge.net/p/metrixplusplus/tickets/new/">Feature request</a></li>
- <li class="muted">·</li>
- <li><a href="https://sourceforge.net/p/metrixplusplus/tickets/search/?q=%21status%3Awont-fix+%26%26+%21status%3Aclosed">Open issues</a></li>
- <li class="muted">·</li>
- <li><a href="https://sourceforge.net/p/metrixplusplus/wiki/ChangeLog/">Changelog</a></li>
- </ul>
- </div>
- </div>
- </div>
- </footer>
- <!-- Le javascript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
- <script src="assets/js/jquery.js"></script>
- <script src="assets/js/bootstrap-transition.js"></script>
- <script src="assets/js/bootstrap-alert.js"></script>
- <script src="assets/js/bootstrap-modal.js"></script>
- <script src="assets/js/bootstrap-dropdown.js"></script>
- <script src="assets/js/bootstrap-scrollspy.js"></script>
- <script src="assets/js/bootstrap-tab.js"></script>
- <script src="assets/js/bootstrap-tooltip.js"></script>
- <script src="assets/js/bootstrap-popover.js"></script>
- <script src="assets/js/bootstrap-button.js"></script>
- <script src="assets/js/bootstrap-collapse.js"></script>
- <script src="assets/js/bootstrap-carousel.js"></script>
- <script src="assets/js/bootstrap-typeahead.js"></script>
- <script src="assets/js/bootstrap-affix.js"></script>
- <script>
- !function ($) {
- $(function(){
- // carousel demo
- $('#myCarousel').carousel()
- })
- }(window.jQuery)
- </script>
- <script src="assets/js/holder/holder.js"></script>
- <script src="assets/js/google-code-prettify/prettify.js"></script>
- <script src="assets/js/application.js"></script>
- <script>
-
- </script>
- <!-- Analytics
- ================================================== -->
- <!--
- <script>
- var _gauges = _gauges || [];
- (function() {
- var t = document.createElement('script');
- t.type = 'text/javascript';
- t.async = true;
- t.id = 'gauges-tracker';
- t.setAttribute('data-site-id', '4f0dc9fef5a1f55508000013');
- t.src = '//secure.gaug.es/track.js';
- var s = document.getElementsByTagName('script')[0];
- s.parentNode.insertBefore(t, s);
- })();
- </script>
- -->
- </body>
- </html>
|