diff --git a/clover-merge b/clover-merge index b7ec767..a5773ea 100755 --- a/clover-merge +++ b/clover-merge @@ -2,8 +2,6 @@ execute(); diff --git a/spec/Accumulator.spec.php b/spec/Accumulator.spec.php index 2fc44a2..40d67bb 100644 --- a/spec/Accumulator.spec.php +++ b/spec/Accumulator.spec.php @@ -5,7 +5,6 @@ use d0x2f\CloverMerge\Accumulator; use d0x2f\CloverMerge\File; use d0x2f\CloverMerge\Utilities; -use d0x2f\CloverMerge\Metrics; /** * @phan-closure-scope \Kahlan\Scope @@ -31,9 +30,6 @@ 'test.php' ]); - $metrics = $this->accumulator->getMetrics(); - expect($metrics)->toBeAnInstanceOf(Metrics::class); - $file = $result->get('test.php'); expect($file)->toBeAnInstanceOf(File::class); diff --git a/spec/ClassT.spec.php b/spec/ClassT.spec.php index a3997a2..6f844c3 100644 --- a/spec/ClassT.spec.php +++ b/spec/ClassT.spec.php @@ -3,7 +3,6 @@ namespace d0x2f\CloverMerge\Spec; use d0x2f\CloverMerge\ClassT; -use d0x2f\CloverMerge\Metrics; /** * @phan-closure-scope \Kahlan\Scope @@ -12,11 +11,11 @@ describe('__construct', function () { context('Receives a namespace and metrics.', function () { beforeEach(function () { - $this->metrics = new Metrics(new \Ds\Map([ + $properties = new \Ds\Map([ 'foo' => 'bar', 'baz' => 'fred' - ])); - $this->instance = new ClassT('Example\Namespace', $this->metrics); + ]); + $this->instance = new ClassT($properties); }); it('produces a valid instance.', function () { @@ -29,9 +28,7 @@ beforeEach(function () { $xml_element = simplexml_load_string( ' - - - ' + ' ); assert($xml_element !== false); $this->instance = ClassT::fromXML($xml_element); @@ -41,44 +38,28 @@ expect($this->instance)->toBeAnInstanceOf(ClassT::class); }); - it('has the correct namespace set.', function () { - expect($this->instance->getNamespace())->toBe('Example\Namespace'); - }); - - it('has the correct metrics set.', function () { - expect($this->instance->getMetrics()->getProperties()->toArray())->toBe([ + it('has the correct properties set.', function () { + expect($this->instance->getProperties()->toArray())->toBe([ + 'name' => 'Example\Namespace\Class', + 'namespace' => 'Example\Namespace', 'foo' => 'bar', 'baz' => 'fred' ]); }); }); }); - describe('getNamespace', function () { - beforeEach(function () { - $this->metrics = new Metrics(new \Ds\Map([ - 'foo' => 'bar', - 'baz' => 'fred' - ])); - $this->instance = new ClassT('Example\Namespace', $this->metrics); - $this->result = $this->instance->getNamespace(); - }); - - it('returns the properties map.', function () { - expect($this->result)->toBe('Example\Namespace'); - }); - }); - describe('getMetrics', function () { + describe('getProperties', function () { beforeEach(function () { - $this->metrics = new Metrics(new \Ds\Map([ + $properties = new \Ds\Map([ 'foo' => 'bar', 'baz' => 'fred' - ])); - $this->instance = new ClassT('Example\Namespace', $this->metrics); - $this->result = $this->instance->getMetrics(); + ]); + $instance = new ClassT($properties); + $this->result = $instance->getProperties(); }); it('returns the properties map.', function () { - expect($this->result->getProperties()->toArray())->toBe([ + expect($this->result->toArray())->toBe([ 'foo' => 'bar', 'baz' => 'fred' ]); diff --git a/spec/File.spec.php b/spec/File.spec.php index 5aecb09..6c67fa7 100644 --- a/spec/File.spec.php +++ b/spec/File.spec.php @@ -4,6 +4,7 @@ use d0x2f\CloverMerge\File; use d0x2f\CloverMerge\Utilities; +use d0x2f\CloverMerge\Metrics; /** * @phan-closure-scope \Kahlan\Scope @@ -30,8 +31,10 @@ - - + + + + '); @@ -54,19 +57,24 @@ $class = $classes->first(); expect($class->key)->toBe('Example\Namespace\Class'); - expect($class->value->getNamespace())->toBe('Example\Namespace'); + expect($class->value->getProperties()->toArray())->toBe([ + 'name' => 'Example\Namespace\Class', + 'namespace' => 'Example\Namespace' + ]); }); it('has the correct lines set.', function () { $lines = $this->instance->getLines(); - expect($lines)->toHaveLength(3); + expect($lines)->toHaveLength(5); $keys = $lines->keys(); - expect($keys->toArray())->toBe([22, 28, 29]); + expect($keys->toArray())->toBe([22, 28, 29, 30, 31]); expect($lines->get(22)->getCount())->toBe(1); expect($lines->get(28)->getCount())->toBe(1); expect($lines->get(29)->getCount())->toBe(0); + expect($lines->get(30)->getCount())->toBe(0); + expect($lines->get(31)->getCount())->toBe(1); }); }); describe('Receives a XML element with errors.', function () { @@ -99,6 +107,35 @@ }); }); }); + describe('toXml', function () { + beforeEach(function () { + $xml_element = simplexml_load_string(' + + + + + + + + + + + + + '); + assert($xml_element !== false); + $instance = File::fromXML($xml_element, 'package_name'); + $this->result = $instance->toXml(new \DOMDocument(), '/src/Example/Namespace/Class.php'); + }); + + it('produces a DOM element.', function () { + expect($this->result[0])->toBeAnInstanceOf(\DOMElement::class); + }); + + it('produces a metrics object.', function () { + expect($this->result[1])->toBeAnInstanceOf(Metrics::class); + }); + }); describe('merge', function () { context('Receives a second File instance to merge into this one.', function () { beforeEach(function () { @@ -143,8 +180,14 @@ 'Example\Namespace\Class' ]); - expect($classes->get('Example\Namespace\Class')->getNamespace())->toBe('Example\Namespace'); - expect($classes->get('Example\Namespace\OtherClass')->getNamespace())->toBe('Example\OtherNamespace'); + expect($classes->get('Example\Namespace\Class')->getProperties()->toArray())->toBe([ + 'name' => 'Example\Namespace\Class', + 'namespace' => 'Example\Namespace' + ]); + expect($classes->get('Example\Namespace\OtherClass')->getProperties()->toArray())->toBe([ + 'name' => 'Example\Namespace\OtherClass', + 'namespace' => 'Example\OtherNamespace' + ]); }); it('has the correct lines set.', function () { diff --git a/spec/Invocation.spec.php b/spec/Invocation.spec.php index 14f34c1..9fdef2a 100644 --- a/spec/Invocation.spec.php +++ b/spec/Invocation.spec.php @@ -5,6 +5,7 @@ use d0x2f\CloverMerge\Invocation; use d0x2f\CloverMerge\Accumulator; use d0x2f\CloverMerge\Utilities; +use d0x2f\CloverMerge\Metrics; /** * @phan-closure-scope \Kahlan\Scope @@ -133,7 +134,7 @@ new \SimpleXMLElement('') ); allow(Accumulator::class)->toReceive('parseAll')->andReturn(); - allow(Accumulator::class)->toReceive('toXml')->andReturn(new \Ds\Map()); + allow(Accumulator::class)->toReceive('toXml')->andReturn([new \Ds\Map(), new Metrics()]); $this->invocation = new Invocation(['prog', '-o', 'test', 'path', 'path2']); $this->closure = function () { $this->invocation->execute(); diff --git a/spec/Metrics.spec.php b/spec/Metrics.spec.php index d2b2b39..a3e9ece 100644 --- a/spec/Metrics.spec.php +++ b/spec/Metrics.spec.php @@ -9,12 +9,9 @@ */ describe('Metrics', function () { describe('__construct', function () { - context('Receives a map of properties.', function () { + context('Receives a set of statistics.', function () { beforeEach(function () { - $this->instance = new Metrics(new \Ds\Map([ - 'foo' => 'bar', - 'baz' => 'fred' - ])); + $this->instance = new Metrics(10, 9, 8, 7, 6, 5, 4, 3, 2); }); it('produces a valid instance.', function () { @@ -22,43 +19,157 @@ }); }); }); - describe('fromXML', function () { - context('Receives an XML element.', function () { - beforeEach(function () { - $xml_element = simplexml_load_string( - ' - ' - ); - assert($xml_element !== false); - $this->instance = Metrics::fromXML($xml_element); - }); + describe('toFileXml', function () { + beforeEach(function () { + $instance = new Metrics(10, 9, 8, 7, 6, 5, 4, 3, 2); + $xml_document = new \DOMDocument(); + $this->result = $instance->toFileXml($xml_document); + }); - it('produces a valid instance.', function () { - expect($this->instance)->toBeAnInstanceOf(Metrics::class); - }); + it("produces a XML element containing an 'elements' attribute.", function () { + expect($this->result->getAttribute('elements'))->toBe('24'); + }); - it('has the correct properties set.', function () { - expect($this->instance->getProperties()->toArray())->toBe([ - 'foo' => 'bar', - 'baz' => 'fred' - ]); - }); + it("produces a XML element containing a 'coveredelements' attribute.", function () { + expect($this->result->getAttribute('coveredelements'))->toBe('21'); + }); + + it("produces a XML element containing a 'conditionals' attribute.", function () { + expect($this->result->getAttribute('conditionals'))->toBe('8'); + }); + + it("produces a XML element containing a 'coveredconditionals' attribute.", function () { + expect($this->result->getAttribute('coveredconditionals'))->toBe('7'); + }); + + it("produces a XML element containing a 'statements' attribute.", function () { + expect($this->result->getAttribute('statements'))->toBe('10'); + }); + + it("produces a XML element containing a 'coveredstatements' attribute.", function () { + expect($this->result->getAttribute('coveredstatements'))->toBe('9'); + }); + + it("produces a XML element containing a 'methods' attribute.", function () { + expect($this->result->getAttribute('methods'))->toBe('6'); + }); + + it("produces a XML element containing a 'coveredmethods' attribute.", function () { + expect($this->result->getAttribute('coveredmethods'))->toBe('5'); + }); + + it("produces a XML element containing a 'classes' attribute.", function () { + expect($this->result->getAttribute('classes'))->toBe('4'); + }); + + it("produces a XML element without a 'files' attribute.", function () { + expect($this->result->hasAttribute('files'))->toBe(false); + }); + + it("produces a XML element without a 'packages' attribute.", function () { + expect($this->result->hasAttribute('packages'))->toBe(false); + }); + }); + describe('toPackageXml', function () { + beforeEach(function () { + $instance = new Metrics(10, 9, 8, 7, 6, 5, 4, 3, 2); + $xml_document = new \DOMDocument(); + $this->result = $instance->toPackageXml($xml_document); + }); + + it("produces a XML element containing an 'elements' attribute.", function () { + expect($this->result->getAttribute('elements'))->toBe('24'); + }); + + it("produces a XML element containing a 'coveredelements' attribute.", function () { + expect($this->result->getAttribute('coveredelements'))->toBe('21'); + }); + + it("produces a XML element containing a 'conditionals' attribute.", function () { + expect($this->result->getAttribute('conditionals'))->toBe('8'); + }); + + it("produces a XML element containing a 'coveredconditionals' attribute.", function () { + expect($this->result->getAttribute('coveredconditionals'))->toBe('7'); + }); + + it("produces a XML element containing a 'statements' attribute.", function () { + expect($this->result->getAttribute('statements'))->toBe('10'); + }); + + it("produces a XML element containing a 'coveredstatements' attribute.", function () { + expect($this->result->getAttribute('coveredstatements'))->toBe('9'); + }); + + it("produces a XML element containing a 'methods' attribute.", function () { + expect($this->result->getAttribute('methods'))->toBe('6'); + }); + + it("produces a XML element containing a 'coveredmethods' attribute.", function () { + expect($this->result->getAttribute('coveredmethods'))->toBe('5'); + }); + + it("produces a XML element containing a 'classes' attribute.", function () { + expect($this->result->getAttribute('classes'))->toBe('4'); + }); + + it("produces a XML element containing a 'files' attribute.", function () { + expect($this->result->getAttribute('files'))->toBe('3'); + }); + + it("produces a XML element without a 'packages' attribute.", function () { + expect($this->result->hasAttribute('packages'))->toBe(false); }); }); - describe('getProperties', function () { + describe('toProjectXml', function () { beforeEach(function () { - $this->instance = new Metrics(new \Ds\Map([ - 'foo' => 'bar', - 'baz' => 'fred' - ])); - $this->result = $this->instance->getProperties(); - }); - - it('returns the properties map.', function () { - expect($this->result->toArray())->toBe([ - 'foo' => 'bar', - 'baz' => 'fred' - ]); + $instance = new Metrics(10, 9, 8, 7, 6, 5, 4, 3, 2); + $xml_document = new \DOMDocument(); + $this->result = $instance->toProjectXml($xml_document); + }); + + it("produces a XML element containing an 'elements' attribute.", function () { + expect($this->result->getAttribute('elements'))->toBe('24'); + }); + + it("produces a XML element containing a 'coveredelements' attribute.", function () { + expect($this->result->getAttribute('coveredelements'))->toBe('21'); + }); + + it("produces a XML element containing a 'conditionals' attribute.", function () { + expect($this->result->getAttribute('conditionals'))->toBe('8'); + }); + + it("produces a XML element containing a 'coveredconditionals' attribute.", function () { + expect($this->result->getAttribute('coveredconditionals'))->toBe('7'); + }); + + it("produces a XML element containing a 'statements' attribute.", function () { + expect($this->result->getAttribute('statements'))->toBe('10'); + }); + + it("produces a XML element containing a 'coveredstatements' attribute.", function () { + expect($this->result->getAttribute('coveredstatements'))->toBe('9'); + }); + + it("produces a XML element containing a 'methods' attribute.", function () { + expect($this->result->getAttribute('methods'))->toBe('6'); + }); + + it("produces a XML element containing a 'coveredmethods' attribute.", function () { + expect($this->result->getAttribute('coveredmethods'))->toBe('5'); + }); + + it("produces a XML element containing a 'classes' attribute.", function () { + expect($this->result->getAttribute('classes'))->toBe('4'); + }); + + it("produces a XML element containing a 'files' attribute.", function () { + expect($this->result->getAttribute('files'))->toBe('3'); + }); + + it("produces a XML element containing a 'packages' attribute.", function () { + expect($this->result->getAttribute('packages'))->toBe('2'); }); }); }); diff --git a/src/Accumulator.php b/src/Accumulator.php index 1a699cf..81e1610 100644 --- a/src/Accumulator.php +++ b/src/Accumulator.php @@ -28,13 +28,6 @@ class Accumulator */ private $files; - /** - * Document metrics. - * - * @var ?Metrics - */ - private $metrics = null; - /** * If this accumulator is empty. * @@ -64,44 +57,6 @@ public function getFiles() : \Ds\Map return $this->files; } - /** - * Get the top level metrics object. - * - * @return Metrics|null - */ - public function getMetrics() : ?Metrics - { - return $this->metrics; - } - - /** - * Get the number of files discovered. - * - * @return integer - */ - public function getFileCount() : int - { - return $this->files->count(); - } - - /** - * Sum coverage information. - * - * @return array{0:int,1:int} - */ - public function getCoverage() : array - { - return $this->files->reduce( - function (array $carry, string $_, File $file) { - [$covered, $total] = $file->getCoverage(); - $carry[0] += $covered; - $carry[1] += $total; - return $carry; - }, - [0, 0] - ); - } - /** * Parse each document in the given collection. * @@ -199,10 +154,7 @@ private function parseItem( } elseif ($name === 'file') { $this->parseFile($element, $package_name); } elseif ($name === 'metrics') { - $metrics = Metrics::fromXml($element); - if (is_null($this->metrics)) { - $this->metrics = $metrics; - } + // Ignore input metrics, we'll compute our own. } else { Utilities::logWarning("Ignoring unexpected element: {$name}."); } @@ -235,10 +187,11 @@ private function parseFile( /** * Build an XML representation. + * Returns a tuple of the xml string and a metrics object. * - * @return string + * @return array{0:string,1:Metrics} */ - public function toXml() : string + public function toXml() : array { // Sort files by name $this->files->ksort(); @@ -254,96 +207,39 @@ public function toXml() : string $xml_project->setAttribute('timestamp', $_SERVER['REQUEST_TIME']); $xml_coverage->appendChild($xml_project); + $project_metrics = new Metrics(); + $packages = new \Ds\Map(); - foreach ($this->files as $name => $item) { - $xml_file = Accumulator::buildFile($xml_document, $name, $item); - $package_name = $item->getPackageName(); + foreach ($this->files as $name => $file) { + [$xml_file, $file_metrics] = $file->toXml($xml_document, $name); + $project_metrics->merge($file_metrics); + $package_name = $file->getPackageName(); if (is_null($package_name)) { $xml_project->appendChild($xml_file); } elseif ($packages->hasKey($package_name)) { - $packages->get($package_name)->appendChild($xml_file); + $packages->get($package_name)[0]->appendChild($xml_file); + $packages->get($package_name)[1]->merge($file_metrics); } else { $xml_package = $xml_document->createElement('package'); $xml_package->setAttribute('name', $package_name); $xml_project->appendChild($xml_package); $xml_package->appendChild($xml_file); - $packages->put($package_name, $xml_package); - } - } - - if (!is_null($this->metrics)) { - $xml_project->appendChild(Accumulator::buildMetrics($xml_document, $this->metrics)); - } - - return $xml_document->saveXML(); - } - - /** - * Build an xml representation of a file. - * - * @param \DomDocument $xml_document - * @param string $name - * @param File $file - * @return \DOMElement - */ - private static function buildFile( - \DomDocument $xml_document, - string $name, - File $file - ) : \DOMElement { - $xml_file = $xml_document->createElement('file'); - $xml_file->setAttribute('name', $name); - - foreach ($file->getClasses() as $name => $class) { - $xml_class = $xml_document->createElement('class'); - $xml_class->setAttribute('name', $name); - $namespace = $class->getNamespace(); - if (!is_null($namespace)) { - $xml_class->setAttribute('namespace', $namespace); - } - $metrics = $class->getMetrics(); - if (!is_null($metrics)) { - $xml_metrics = Accumulator::buildMetrics($xml_document, $metrics); - $xml_class->appendChild($xml_metrics); + $package_metrics = new Metrics(); + $package_metrics->package_count = 1; + $package_metrics->merge($file_metrics); + $packages->put($package_name, [$xml_package, $package_metrics]); } - $xml_file->appendChild($xml_class); } - foreach ($file->getLines() as $number => $line) { - $xml_line = $xml_document->createElement('line'); - $xml_line->setAttribute('num', $number); - foreach ($line->getProperties() as $name => $value) { - $xml_line->setAttribute($name, $value); - } - $xml_line->setAttribute('count', $line->getCount()); - $xml_file->appendChild($xml_line); + foreach ($packages as $package) { + $package_xml = $package[0]; + $package_metrics = $package[1]; + $package_xml->appendChild($package_metrics->toPackageXml($xml_document)); } - $metrics = $file->getMetrics(); - if (!is_null($metrics)) { - $xml_metrics = Accumulator::buildMetrics($xml_document, $metrics); - $xml_file->appendChild($xml_metrics); - } + $xml_project->appendChild($project_metrics->toProjectXml($xml_document)); - return $xml_file; - } - - /** - * Build an xml representation of a set of metrics. - * - * @param \DomDocument $xml_document - * @param Metrics $metrics - * @return \DOMElement - */ - private static function buildMetrics( - \DomDocument $xml_document, - Metrics $metrics - ) : \DOMElement { - $xml_metrics = $xml_document->createElement('metrics'); - foreach ($metrics->getProperties() as $name => $value) { - $xml_metrics->setAttribute($name, $value); - } - return $xml_metrics; + return [$xml_document->saveXML(), $project_metrics]; } } diff --git a/src/ClassT.php b/src/ClassT.php index 8d942b8..6658548 100644 --- a/src/ClassT.php +++ b/src/ClassT.php @@ -7,30 +7,23 @@ */ class ClassT { - /** - * Class namespace. - * - * @var string|null - */ - private $namespace; /** - * Class metrics + * Other properties on the line. + * E.g. name, visibility, complexity, crap. * - * @var Metrics|null + * @var \Ds\Map $properties */ - private $metrics; + private $properties; /** * Constructor. * - * @param string|null $namespace - * @param Metrics|null $metrics + * @param \Ds\Map $properties Any properties on the XML node. */ - public function __construct(?string $namespace = null, ?Metrics $metrics = null) + public function __construct(\Ds\Map $properties) { - $this->namespace = $namespace; - $this->metrics = $metrics; + $this->properties = $properties; } /** @@ -41,48 +34,36 @@ public function __construct(?string $namespace = null, ?Metrics $metrics = null) */ public static function fromXML(\SimpleXMLElement $xml) : ClassT { - $attributes = $xml->attributes(); - $class = new ClassT($attributes['namespace'] ?? null); - $children = $xml->children(); - foreach ($children as $child) { - $name = $child->getName(); - if ($name === 'metrics') { - $class->mergeMetrics(Metrics::fromXml($child)); - } else { - Utilities::logWarning("Ignoring unknown element: {$name}."); - } - } - return $class; - } - - /** - * Set metrics. - * - * @param Metrics $metrics - * @return void - */ - public function mergeMetrics(Metrics $metrics) : void - { - $this->metrics = $this->metrics ?? $metrics; + $properties = new \Ds\Map($xml->attributes()); + $properties->apply(function ($_, $value) { + return (string) $value; + }); + return new ClassT($properties); } /** - * Get the namespace. + * Produce an XML representation. * - * @return string|null + * @param \DomDocument $document The parent document. + * @return \DOMElement */ - public function getNamespace() : ?string - { - return $this->namespace; + public function toXml( + \DomDocument $document + ) : \DOMElement { + $xml_class = $document->createElement('class'); + foreach ($this->properties as $key => $value) { + $xml_class->setAttribute($key, $value); + } + return $xml_class; } /** - * Get the metrics. + * Get the properties. * - * @return Metrics|null + * @return \Ds\Map */ - public function getMetrics() : ?Metrics + public function getProperties() : \Ds\Map { - return $this->metrics; + return $this->properties; } } diff --git a/src/File.php b/src/File.php index 8a2daa1..15ed1ec 100644 --- a/src/File.php +++ b/src/File.php @@ -28,13 +28,6 @@ class File */ private $package_name; - /** - * Metrics element. - * - * @var Metrics|null - */ - private $metrics; - /** * Construct with the given package name. * @@ -45,7 +38,6 @@ public function __construct(?string $package_name = null) $this->classes = new \Ds\Map(); $this->lines = new \Ds\Map(); $this->package_name = $package_name; - $this->metrics = null; } /** @@ -83,7 +75,7 @@ private static function parseChildXML(File &$file, \SimpleXMLElement $child) } $file->mergeClass($attributes['name'] ?? '', ClassT::fromXml($child)); } elseif ($name === 'metrics') { - $file->mergeMetrics(Metrics::fromXml($child)); + // Ignore input metrics, we'll compute our own. } elseif ($name === 'line') { if (!Utilities::xmlHasAttributes($child, ['num', 'count'])) { Utilities::logWarning('Ignoring line with no num or count.'); @@ -96,16 +88,76 @@ private static function parseChildXML(File &$file, \SimpleXMLElement $child) } /** - * Get the covered and total lines count. + * Produce an XML element representing this file. * - * @return array{0:int,1:int} + * @param \DomDocument $xml_document The parent document. + * @param string $name The name of the file. + * @return array{0:\DOMElement,1:Metrics} */ - public function getCoverage() : array - { - $covered = $this->lines->filter(function (int $_, Line $line) { - return $line->getCount() > 0; - })->count(); - return [$covered, $this->lines->count()]; + public function toXml( + \DomDocument $xml_document, + string $name + ) : array { + $xml_file = $xml_document->createElement('file'); + $xml_file->setAttribute('name', $name); + + // Metric counts + $statement_count = 0; + $covered_statement_count = 0; + $conditional_count = 0; + $covered_conditional_count = 0; + $method_count = 0; + $covered_method_count = 0; + $class_count = $this->classes->count(); + + // Classes + foreach ($this->classes as $class) { + $xml_file->appendChild($class->toXml($xml_document)); + } + + // Lines + foreach ($this->lines as $line) { + $xml_file->appendChild($line->toXml($xml_document)); + $properties = $line->getProperties(); + + $covered = $line->getCount() > 0; + $type = $properties['type'] ?? 'stmt'; + + if ($type === 'method') { + $method_count ++; + if ($covered) { + $covered_method_count ++; + } + } elseif ($type === 'stmt') { + $statement_count ++; + if ($covered) { + $covered_statement_count ++; + } + } elseif ($type === 'cond') { + $conditional_count ++; + if ($covered) { + $covered_conditional_count ++; + } + } else { + Utilities::logWarning("Ignoring unexpected line type: {$type}."); + } + } + + // Metrics + $metrics = new Metrics( + $statement_count, + $covered_statement_count, + $conditional_count, + $covered_conditional_count, + $method_count, + $covered_method_count, + $class_count, + 1 + ); + $xml_file->appendChild($metrics->toFileXml($xml_document)); + + // Return a tuple of the XML node and the metrics to carry over. + return [$xml_file, $metrics]; } /** @@ -120,7 +172,6 @@ public function merge(File $other, string $merge_mode = 'inclusive', bool $lock_ { $this->classes = $other->getClasses()->merge($this->classes); $this->package_name = $this->package_name ?? $other->package_name; - $this->metrics = $this->metrics ?? $other->metrics; $other_lines = $other->getLines(); @@ -165,17 +216,6 @@ public function mergeLine(int $number, Line $line, bool $lock_lines = false) : v } } - /** - * Set metrics if not already set. - * - * @param Metrics $metrics - * @return void - */ - public function mergeMetrics(Metrics $metrics) - { - $this->metrics = $this->metrics ?? $metrics; - } - /** * Get the classes. * @@ -205,14 +245,4 @@ public function getPackageName() : ?string { return $this->package_name; } - - /** - * Get the metrics. - * - * @return Metrics|null - */ - public function getMetrics() : ?Metrics - { - return $this->metrics; - } } diff --git a/src/Invocation.php b/src/Invocation.php index 659a652..db81257 100644 --- a/src/Invocation.php +++ b/src/Invocation.php @@ -96,21 +96,22 @@ public function execute() : void $accumulator->parseAll($this->documents); // Output - $output = $accumulator->toXml(); - $write_result = file_put_contents($this->output_path, $output); + [$xml, $metrics] = $accumulator->toXml(); + $write_result = file_put_contents($this->output_path, $xml); if ($write_result === false) { throw new FileException("Unable to write to given output file."); } // Stats - $files_discovered = $accumulator->getFileCount(); - [$covered, $total] = $accumulator->getCoverage(); - if ($total === 0) { + $files_discovered = $metrics->file_count; + $element_count = $metrics->getElementCount(); + $covered_element_count = $metrics->getCoveredElementCount(); + if ($element_count === 0) { $coverage_percentage = 0; } else { - $coverage_percentage = 100 * $covered/$total; + $coverage_percentage = 100 * $covered_element_count/$element_count; } printf("Files Discovered: %d\n", $files_discovered); - printf("Final Coverage: %d/%d (%.2f%%)\n", $covered, $total, $coverage_percentage); + printf("Final Coverage: %d/%d (%.2f%%)\n", $covered_element_count, $element_count, $coverage_percentage); } } diff --git a/src/Line.php b/src/Line.php index 37417c2..29e3863 100644 --- a/src/Line.php +++ b/src/Line.php @@ -25,6 +25,7 @@ class Line /** * Initialise with a hit count. * + * @param \Ds\Map $properties Any other properties on the XML node. * @param integer $count */ public function __construct(\Ds\Map $properties, int $count = 0) @@ -52,6 +53,23 @@ public static function fromXML(\SimpleXMLElement $xml) : Line return new Line($properties, (int)$properties->remove('count')); } + /** + * Produce an XML representation. + * + * @param \DomDocument $document The parent document. + * @return \DOMElement + */ + public function toXml( + \DomDocument $document + ) : \DOMElement { + $xml_line = $document->createElement('line'); + foreach ($this->properties as $key => $value) { + $xml_line->setAttribute($key, $value); + } + $xml_line->setAttribute('count', (string)$this->count); + return $xml_line; + } + /** * Merge another line with this one. * diff --git a/src/Metrics.php b/src/Metrics.php index d5ad187..3144e37 100644 --- a/src/Metrics.php +++ b/src/Metrics.php @@ -7,45 +7,166 @@ */ class Metrics { + /** @var integer */ + public $statement_count = 0; + + /** @var integer */ + public $covered_statement_count = 0; + + /** @var integer */ + public $conditional_count = 0; + + /** @var integer */ + public $covered_conditional_count = 0; + + /** @var integer */ + public $method_count = 0; + + /** @var integer */ + public $covered_method_count = 0; + + /** @var integer */ + public $class_count = 0; + + /** @var integer */ + public $file_count = 0; + + /** @var integer */ + public $package_count = 0; + + /** + * Constructor + * + * @param integer $statement_count + * @param integer $covered_statement_count + * @param integer $conditional_count + * @param integer $covered_conditional_count + * @param integer $method_count + * @param integer $covered_method_count + * @param integer $class_count + * @param integer $file_count + * @param integer $package_count + */ + public function __construct( + int $statement_count = 0, + int $covered_statement_count = 0, + int $conditional_count = 0, + int $covered_conditional_count = 0, + int $method_count = 0, + int $covered_method_count = 0, + int $class_count = 0, + int $file_count = 0, + int $package_count = 0 + ) { + $this->statement_count = $statement_count; + $this->covered_statement_count = $covered_statement_count; + $this->conditional_count = $conditional_count; + $this->covered_conditional_count = $covered_conditional_count; + $this->method_count = $method_count; + $this->covered_method_count = $covered_method_count; + $this->class_count = $class_count; + $this->file_count = $file_count; + $this->package_count = $package_count; + } + /** - * Properties. + * Create an XML element to represent these metrics under a file. * - * @var \Ds\Map + * @param \DOMDocument $xml_document The parent document. + * @return \DOMElement */ - private $properties; + public function toFileXml( + \DOMDocument $xml_document + ) : \DOMElement { + $xml_metrics = $xml_document->createElement('metrics'); + + // We can't know the complexity, just set 0 + // (attribute required by the clover xml schema) + $xml_metrics->setAttribute('complexity', '0'); + + $xml_metrics->setAttribute('elements', (string)$this->getElementCount()); + $xml_metrics->setAttribute('coveredelements', (string)$this->getCoveredElementCount()); + $xml_metrics->setAttribute('conditionals', (string)$this->conditional_count); + $xml_metrics->setAttribute('coveredconditionals', (string)$this->covered_conditional_count); + $xml_metrics->setAttribute('statements', (string)$this->statement_count); + $xml_metrics->setAttribute('coveredstatements', (string)$this->covered_statement_count); + $xml_metrics->setAttribute('methods', (string)$this->method_count); + $xml_metrics->setAttribute('coveredmethods', (string)$this->covered_method_count); + $xml_metrics->setAttribute('classes', (string)$this->class_count); + + return $xml_metrics; + } + + /** + * Create an XML element to represent these metrics under a package. + * Contains all the attributes of the file context plus the number of files. + * + * @param \DOMDocument $xml_document The parent document. + * @return \DOMElement + */ + public function toPackageXml( + \DOMDocument $xml_document + ) : \DOMElement { + $xml_metrics = $this->toFileXml($xml_document); + $xml_metrics->setAttribute('files', (string)$this->file_count); + return $xml_metrics; + } + + /** + * Create an XML element to represent these metrics under a project. + * Contains all the attributes of the package context plus the number of packages. + * + * @param \DOMDocument $xml_document The parent document. + * @return \DOMElement + */ + public function toProjectXml( + \DOMDocument $xml_document + ) : \DOMElement { + $xml_metrics = $this->toPackageXml($xml_document); + $xml_metrics->setAttribute('packages', (string)$this->package_count); + return $xml_metrics; + } /** - * Constructor. + * Merge another set of metrics into this one. * - * @param \Ds\Map $properties + * @param Metrics $metrics + * @return void */ - public function __construct(\Ds\Map $properties) + public function merge(Metrics $metrics) : void { - $this->properties = $properties; + $this->statement_count += $metrics->statement_count; + $this->covered_statement_count += $metrics->covered_statement_count; + $this->conditional_count += $metrics->conditional_count; + $this->covered_conditional_count += $metrics->covered_conditional_count; + $this->method_count += $metrics->method_count; + $this->covered_method_count += $metrics->covered_method_count; + $this->class_count += $metrics->class_count; + $this->file_count += $metrics->file_count; + $this->package_count += $metrics->package_count; } /** - * Construct from XML. + * Return the number of elements. * - * @param \SimpleXMLElement $xml - * @return Metrics + * @return integer */ - public static function fromXML(\SimpleXMLElement $xml) : Metrics + public function getElementCount() : int { - $properties = new \Ds\Map($xml->attributes()); - $properties->apply(function ($_, $value) { - return (string) $value; - }); - return new Metrics($properties); + return $this->statement_count + + $this->conditional_count + + $this->method_count; } /** - * Get properties. + * Return the number of covered elemetns. * - * @return \Ds\Map + * @return integer */ - public function getProperties() : \Ds\Map + public function getCoveredElementCount() : int { - return $this->properties; + return $this->covered_statement_count + + $this->covered_conditional_count + + $this->covered_method_count; } } diff --git a/test.xml b/test.xml new file mode 100644 index 0000000..2a48cc3 --- /dev/null +++ b/test.xml @@ -0,0 +1,9471 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +