new mechanisms to reference Grok instead of phprxref

This implements a mechanism that transforms our current references into
search terms understood by Grok. Using the API we can even check if
there's any hits (ideally it should be exactly one)
This commit is contained in:
Andreas Gohr
2022-01-27 23:12:27 +01:00
parent 7746fe8fdd
commit d4a11906f9
6 changed files with 470 additions and 0 deletions

52
.github/workflows/phpTestLinux.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: PHP Tests on Linux
on: [push, pull_request]
jobs:
testLinux:
name: PHP ${{ matrix.php-versions }} DokuWiki ${{ matrix.dokuwiki-branch }}
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
strategy:
matrix:
php-versions: ['7.2', '7.3', '7.4', '8.0']
dokuwiki-branch: [ 'master', 'stable']
exclude:
- dokuwiki-branch: 'stable'
php-versions: '8.0'
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, intl, PDO, pdo_sqlite, bz2
- name: Setup problem matchers
run: |
echo ::add-matcher::${{ runner.tool_cache }}/php.json
echo ::add-matcher::${{ runner.tool_cache }}/phpunit.json
- name: Download DokuWiki Test-setup
run: wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh
- name: Run DokuWiki Test-setup
env:
CI_SERVER: 1
DOKUWIKI: ${{ matrix.dokuwiki-branch }}
run: sh travis.sh
- name: Setup PHPUnit
run: |
php _test/fetchphpunit.php
cd _test
- name: Run PHPUnit
run: |
cd _test
php phpunit.phar --verbose --stderr --group plugin_xref

72
Grok.php Normal file
View File

@ -0,0 +1,72 @@
<?php
namespace dokuwiki\plugin\xref;
use dokuwiki\HTTP\DokuHTTPClient;
class Grok
{
protected $baseUrl;
protected $def;
protected $path;
public function __construct($reference, $baseUrl = 'https://codesearch.dokuwiki.org')
{
$heuristic = new Heuristics($reference);
$this->def = $heuristic->getDef();
$this->path = $heuristic->getPath();
$this->baseUrl = rtrim($baseUrl, '/');
}
/**
* Return the URL that leads to the search interface
*
* @return string
*/
public function getSearchUrl()
{
$url = $this->baseUrl . '/search?';
$param = [
'project' => 'dokuwiki',
'defs' => $this->def,
'path' => $this->path,
];
$url .= buildURLparams($param, '&');
return $url;
}
/**
* Return the URL that allows to query the API
*
* @return string
*/
public function getAPIUrl()
{
$url = $this->baseUrl . '/api/v1/search?';
$param = [
'projects' => 'dokuwiki',
'def' => $this->def,
'path' => $this->path,
];
$url .= buildURLparams($param, '&');
return $url;
}
/**
* Return the number of results to expect
*
* @return false|int false on errors
*/
public function getResultCount()
{
$http = new DokuHTTPClient();
$http->timeout = 5;
$json = $http->get($this->getAPIUrl());
if (!$json) return false;
$data = json_decode($json, true);
if (!$data) return false;
if (!isset($data['resultCount'])) return false;
return $data['resultCount'];
}
}

164
Heuristics.php Normal file
View File

@ -0,0 +1,164 @@
<?php
namespace dokuwiki\plugin\xref;
/**
* Figure out what to send to Grok to hopefully show the right, single hit
*/
class Heuristics
{
/** @var string the definition to search */
protected $def = '';
/** @var string the path to use */
protected $path = '';
/**
* Try to gues what the given reference means and how to best search for it
*
* @param string $reference
*/
public function __construct($reference)
{
if ($reference !== '') $reference = $this->checkHash($reference);
if ($reference !== '') $reference = $this->checkFilename($reference);
if ($reference !== '') $reference = $this->checkNamespace($reference);
if ($reference !== '') $reference = $this->checkClassPrefix($reference);
if ($reference !== '') $reference = $this->checkVariable($reference);
if ($reference !== '') $reference = $this->checkFunction($reference);
if ($reference !== '') $reference = $this->checkPSRClass($reference);
if ($reference !== '') $this->def = $reference;
}
/**
* @return string
*/
public function getDef()
{
return trim(preg_replace('/[^\w]+/', '', $this->def));
}
/**
* @return string
*/
public function getPath()
{
return trim(preg_replace('/[^\w.]+/', ' ', $this->path));
}
/**
* Handle things in the form path#symbol
*
* @param string $reference
* @return string
*/
protected function checkHash($reference)
{
if (strpos($reference, '#') === false) return $reference;
list($this->path, $this->def) = explode('#', $reference, 2);
return '';
}
/**
* Known file extension?
*
* @param string $reference
* @return mixed|string
*/
protected function checkFilename($reference)
{
if (preg_match('/\.(php|js|css|html)$/', $reference)) {
$this->def = '';
$this->path = $reference;
return '';
}
return $reference;
}
/**
* Namespaces are paths
*
* @param string $reference
* @return string
*/
protected function checkNamespace($reference)
{
if (strpos($reference, '\\') === false) return $reference;
$parts = explode('\\', $reference);
$parts = array_filter($parts);
$reference = array_pop($parts); // last part may be more than a class
// our classes are in inc
if($parts[0] == 'dokuwiki') $parts[0] = 'inc';
$this->path = join(' ', $parts);
return $reference;
}
/**
* Is there something called on a class?
*/
protected function checkClassPrefix($reference)
{
if (
strpos($reference, '::') === false &&
strpos($reference, '->') === false
) {
return $reference;
}
list($class, $reference) = preg_split('/(::|->)/', $reference, 2);
$this->path .= ' ' . $class;
$this->def = $reference;
return '';
}
/**
* Clearly a variable
*
* @param string $reference
* @return string
*/
protected function checkVariable($reference)
{
if ($reference[0] == '$') {
$this->def = $reference;
return '';
}
return $reference;
}
/**
* It's a function
*
* @param string $reference
* @return string
*/
protected function checkFunction($reference)
{
if (substr($reference, -2) == '()') {
$this->def = $reference;
return '';
}
return $reference;
}
/**
* Upercase followed by lowercase letter, must be a class
*
* Those are in their own files, so add it to the path
* @param $reference
* @return mixed|string
*/
protected function checkPSRClass($reference)
{
if (preg_match('/^[A-Z][a-z]/', $reference)) {
$this->def = $reference;
$this->path .= ' ' . $reference;
return '';
}
return $reference;
}
}

86
_test/GeneralTest.php Normal file
View File

@ -0,0 +1,86 @@
<?php
namespace dokuwiki\plugin\xref\test;
use DokuWikiTest;
/**
* General tests for the xref plugin
*
* @group plugin_xref
* @group plugins
*/
class GeneralTest extends DokuWikiTest
{
/**
* Simple test to make sure the plugin.info.txt is in correct format
*/
public function testPluginInfo(): void
{
$file = __DIR__ . '/../plugin.info.txt';
$this->assertFileExists($file);
$info = confToHash($file);
$this->assertArrayHasKey('base', $info);
$this->assertArrayHasKey('author', $info);
$this->assertArrayHasKey('email', $info);
$this->assertArrayHasKey('date', $info);
$this->assertArrayHasKey('name', $info);
$this->assertArrayHasKey('desc', $info);
$this->assertArrayHasKey('url', $info);
$this->assertEquals('xref', $info['base']);
$this->assertRegExp('/^https?:\/\//', $info['url']);
$this->assertTrue(mail_isvalid($info['email']));
$this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
$this->assertTrue(false !== strtotime($info['date']));
}
/**
* Test to ensure that every conf['...'] entry in conf/default.php has a corresponding meta['...'] entry in
* conf/metadata.php.
*/
public function testPluginConf(): void
{
$conf_file = __DIR__ . '/../conf/default.php';
$meta_file = __DIR__ . '/../conf/metadata.php';
if (!file_exists($conf_file) && !file_exists($meta_file)) {
self::markTestSkipped('No config files exist -> skipping test');
}
if (file_exists($conf_file)) {
include($conf_file);
}
if (file_exists($meta_file)) {
include($meta_file);
}
$this->assertEquals(
gettype($conf),
gettype($meta),
'Both ' . DOKU_PLUGIN . 'xref/conf/default.php and ' . DOKU_PLUGIN . 'xref/conf/metadata.php have to exist and contain the same keys.'
);
if ($conf !== null && $meta !== null) {
foreach ($conf as $key => $value) {
$this->assertArrayHasKey(
$key,
$meta,
'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'xref/conf/metadata.php'
);
}
foreach ($meta as $key => $value) {
$this->assertArrayHasKey(
$key,
$conf,
'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'xref/conf/default.php'
);
}
}
}
}

45
_test/GrokTest.php Normal file
View File

@ -0,0 +1,45 @@
<?php
namespace dokuwiki\plugin\xref\test;
use dokuwiki\plugin\xref\Grok;
use DokuWikiTest;
/**
* Grok tests for the xref plugin
*
* @group plugin_xref
* @group plugins
* @group internet
*/
class GrokTest extends DokuWikiTest
{
/**
* @return \string[][]
* @see testResultCount
*/
public function provideData()
{
// These should all be unique enough to have only one result
return [
['auth.php#auth_setup'],
['inc/Menu/Item/AbstractItem.php'],
['dokuwiki\Menu\Item\AbstractItem'],
['dokuwiki\Menu\Item\AbstractItem::getLabel()'],
['dokuwiki\Menu\Item\AbstractItem->getLabel()'],
['AbstractItem::getLabel()'],
['AbstractItem->getLabel()'],
['AbstractItem'],
];
}
/**
* @dataProvider provideData
* @param string $reference
*/
public function testResultCount($reference)
{
$grok = new Grok($reference);
$this->assertEquals(1, $grok->getResultCount(), $grok->getSearchUrl());
}
}

51
_test/HeuristicsTest.php Normal file
View File

@ -0,0 +1,51 @@
<?php
namespace dokuwiki\plugin\xref\test;
use dokuwiki\plugin\xref\Heuristics;
use DokuWikiTest;
/**
* Heuristics tests for the xref plugin
*
* @group plugin_xref
* @group plugins
*/
class HeuristicsTest extends DokuWikiTest
{
/**
* @return \string[][]
* @see testHeuristics
*/
public function provideData()
{
return [
['auth.php#auth_setup', 'auth_setup', 'auth.php'],
['inc/Menu/Item/AbstractItem.php', '', 'inc Menu Item AbstractItem.php'],
['dokuwiki\Menu\Item\AbstractItem', 'AbstractItem', 'inc Menu Item AbstractItem'],
['dokuwiki\Menu\Item\AbstractItem::getLabel()', 'getLabel', 'inc Menu Item AbstractItem'],
['dokuwiki\Menu\Item\AbstractItem->getLabel()', 'getLabel', 'inc Menu Item AbstractItem'],
['AbstractItem::getLabel()', 'getLabel', 'AbstractItem'],
['AbstractItem->getLabel()', 'getLabel', 'AbstractItem'],
['$INFO', 'INFO', ''],
['foobar()', 'foobar', ''],
['FooBar()', 'FooBar', ''],
['AbstractItem', 'AbstractItem', 'AbstractItem'],
['abstractItem', 'abstractItem', ''],
];
}
/**
* @dataProvider provideData
* @param string $reference
* @param string $expDef
* @param string $expPath
*/
public function testHeuristics($reference, $expDef, $expPath)
{
$heur = new Heuristics($reference);
$this->assertEquals($expDef, $heur->getDef(), 'definition is wrong');
$this->assertEquals($expPath, $heur->getPath(), 'path is wrong');
}
}