Skip to content

Commit

Permalink
Adding \Fuko\Source\Code
Browse files Browse the repository at this point in the history
  • Loading branch information
kktsvetkov committed Jun 8, 2021
1 parent 89f5f5c commit b984913
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 2 deletions.
41 changes: 39 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,43 @@
**Fuko\\Source** is a small PHP library that helps you to extracts chunks of
source code identified by code references, e.g. filename + line.

## Basic Use
It is really simple to use. Imagine you want to extract a piece of code, and
you know what source code line exactly you want to reference: for example
`/var/www/html/index.php` at line 17. You must first create a new `\Fuko\Source\Code`
object, and then call the `getLinesAt()` method:

...
```php
include __DIR__ . '/vendor/autoload.php';

$source = new \Fuko\Source\Code('/var/www/html/index.php');
print_r($source->getLinesAt(17));
/*
Array
(
[14] => Illuminate\Support\ClassLoader::register();
[15] => Illuminate\Support\ClassLoader::addDirectories(array(CLASS_DIR,CONTROLLERS,CONTROLLERS.'Middleware/', MODELS, CONTROLLERS.'Admin/'));
[16] =>
[17] => $FileLoader = new FileLoader(new Filesystem, RESOURCES.'lang');
[18] => $translator = new Translator($FileLoader, 'en');
[19] => $Container = new Container();
[20] => $validation = new Factory($translator, $Container);
)
*/

```

By default there are 7 lines of code (LOCs) returned, but you can specify a
different number in the range of 1 to 20 (as defined in `\Fuko\Source\Code::LOC_MAX`):
```php
include __DIR__ . '/vendor/autoload.php';

$source = new \Fuko\Source\Code('/var/www/html/index.php');
print_r($source->getLinesAt(17, 1));
/*
Array
(
[17] => $FileLoader = new FileLoader(new Filesystem, RESOURCES.'lang');
)
*/

```
131 changes: 131 additions & 0 deletions src/Code.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php /**
* @category Fuko
* @package Fuko\Source
*
* @author Kaloyan Tsvetkov (KT) <kaloyan@kaloyan.info>
* @link https://github.com/fuko-php/source/
* @license https://opensource.org/licenses/MIT
*/

namespace Fuko\Source;

use InvalidArgumentException;
use RuntimeException;

use function ceil;
use function file_exists;
use function fclose;
use function fgets;
use function fopen;

/**
* Source Code Reader
*
* Use this class to extract source code lines using a code reference (filename + line)
*
* @package Fuko\Source
*/
class Code
{
/**
* @var integer default LOCs (lines of code) returned
* @see \Fuko\Source\Code::getLinesAt()
*/
const LOC_DEFAULT = 7;

/**
* @var integer max numer of LOCs (lines of code) returned
* @see \Fuko\Source\Code::getLinesAt()
*/
const LOC_MAX = 20;

/**
* @var string source filename
*/
protected $sourceFilename = '';

/**
* Source Code Constructor
*
* @param string $filename
*/
function __construct(string $filename)
{
$this->sourceFilename = $filename;

}

/**
* Get Source Code Lines
*
* @param integer $line target line of the reference
* @param integer $locs how many LOCs (lines of code) to return
* @return array
* @throws InvalidArgumentException
* @throws RuntimeException
*/
function getLinesAt(int $line, int $locs = null) : array
{
if (!file_exists($this->sourceFilename))
{
throw new RuntimeException(
"Source code file not found: {$this->sourceFilename}",
20004
);
}

if ($line < 1)
{
throw new InvalidArgumentException(
"The \$line argument must be a positive integer, instead {$line} given",
20005
);
}

if (null !== $locs)
{
if ($locs < 1)
{
throw new InvalidArgumentException(
"The \$locs argument must be a positive integer, instead {$locs} given",
20006
);
}
}

// by default show 7 lines, but do not go beyond 20
//
$locs = $locs ?? static::LOC_DEFAULT;
if (static::LOC_MAX < $locs)
{
$locs = static::LOC_MAX;
}

$before = ceil(($locs-1)/2);
$after = $locs -1 - $before;

$from = ($from = $line - $before) > 1 ? $from : 1;
$to = $line + $after;

$lines = array();
$fp = fopen($this->sourceFilename, 'r');

$atLine = 0;
while (false !== ($lineCode = fgets($fp)))
{
if (++$atLine < $from)
{
continue;
}
if ($atLine > $to)
{
break;
}

$lines[ $atLine ] = $lineCode;
}
fclose($fp);

return $lines;
}
}
93 changes: 93 additions & 0 deletions tests/CodeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace Fuko\Source\Tests;

use Fuko\Source\Code;
use PHPUnit\Framework\TestCase;

use function file;

class CodeTest extends TestCase
{
/**
* @covers Fuko\Source\Code::getLinesAt()
* @expectedException RuntimeException
*/
function testMissingFile()
{
$source = new Code('/i/am/not/here');
$source->getLinesAt(123);
}

/**
* @covers Fuko\Source\Code::getLinesAt()
* @expectedException InvalidArgumentException
*/
function testNegativeSourceLine()
{
$source = new Code(__FILE__);
$source->getLinesAt(-2);
}

/**
* @covers Fuko\Source\Code::getLinesAt()
* @expectedException InvalidArgumentException
*/
function testZeroSourceLine()
{
$source = new Code(__FILE__);
$source->getLinesAt(0);
}

/**
* @covers Fuko\Source\Code::getLinesAt()
* @expectedException InvalidArgumentException
*/
function testNegativeLOCs()
{
$source = new Code(__FILE__);
$source->getLinesAt(2, -2);
}

/**
* @covers Fuko\Source\Code::getLinesAt()
* @expectedException InvalidArgumentException
*/
function testZeroLOCs()
{
$source = new Code(__FILE__);
$source->getLinesAt(1, 0);
}

/**
* @covers Fuko\Source\Code::getLinesAt()
*/
function testGetLinesAt()
{
$source = new Code($composer = __DIR__ . '/../composer.json');
$lines = file($composer);

$this->assertEquals(
$source->getLinesAt(1, 1),
[ 1 => $lines[0] ]
);

$this->assertEquals(
$source->getLinesAt(4), [
1 => $lines[0],
2 => $lines[1],
3 => $lines[2],
4 => $lines[3],
5 => $lines[4],
6 => $lines[5],
7 => $lines[6],
]);

$this->assertEquals(
$source->getLinesAt(4, 3), [
3 => $lines[2],
4 => $lines[3],
5 => $lines[4],
]);
}
}

0 comments on commit b984913

Please sign in to comment.