Skip to content

Commit 9997148

Browse files
committed
Cli: Add basic 'cdb' CLI
Based on the maintenance/cdb.php script in MediaWiki 1.25, <https://gerrit.wikimedia.org/g/mediawiki/core/+/REL1_25/maintenance/cdb.php>. Change-Id: I858dbd5746584282ddc29b7efa49c217423fc76f
1 parent 46e63b6 commit 9997148

File tree

8 files changed

+259
-4
lines changed

8 files changed

+259
-4
lines changed

.phpcs.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<ruleset>
33
<rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki">
44
<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic" />
5-
<exclude name="MediaWiki.Commenting.FunctionComment.MissingParamTag" />
5+
<exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate" />
66
<exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.NewLineComment" />
77
</rule>
88
<file>.</file>

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@ In cases where `dba_*` functions are not present or are not compiled with CDB
1010
support, a pure-PHP implementation is provided for falling back.
1111

1212
Additional documentation about the library can be found on
13-
[MediaWiki.org](https://www.mediawiki.org/wiki/CDB).
13+
[mediawiki.org](https://www.mediawiki.org/wiki/CDB).
1414

1515

1616
Usage
1717
-----
1818

1919
// Reading a CDB file
20-
$cdb = \Cdb\Reader::open( 'db.cdb' );
20+
$cdb = Cdb\Reader::open( 'db.cdb' );
2121
$foo = $cdb->get( 'somekey' );
2222

2323
// Writing to a CDB file
24-
$cdb = \Cdb\Writer::open( 'anotherdb.cdb' );
24+
$cdb = Cdb\Writer::open( 'anotherdb.cdb' );
2525
$cdb->set( 'somekey', $foo );
2626

27+
// Using the CLI
28+
$ cdb ./myfile.cdb [get|list|match] <parameter>
29+
2730

2831
Running tests
2932
-------------

bin/cdb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env php
2+
<?php declare(strict_types=1);
3+
4+
require_once __DIR__ . '/../vendor/autoload.php';
5+
6+
$cli = new Cdb\Cli( STDOUT, $argv );
7+
$cli->run();
8+
exit( $cli->getExitCode() );

src/Cli.php

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php
2+
/**
3+
* @copyright
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License along
15+
* with this program; if not, write to the Free Software Foundation, Inc.,
16+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
* http://www.gnu.org/copyleft/gpl.html
18+
*/
19+
20+
namespace Cdb;
21+
22+
/**
23+
* @internal For use by bin/cdb only.
24+
*/
25+
final class Cli {
26+
/** @var int */
27+
private $exitCode = 0;
28+
29+
/** @var resource */
30+
private $out;
31+
32+
/** @var string */
33+
private $self;
34+
35+
/** @var string */
36+
private $filepath;
37+
38+
/** @var string */
39+
private $action;
40+
41+
/** @var string[] */
42+
private $params;
43+
44+
/**
45+
* @param resource $out An open output handle for fwrite()
46+
* @param string[] $argv
47+
*/
48+
public function __construct( $out, array $argv ) {
49+
$this->out = $out;
50+
$this->self = $argv[0] ?? './bin/cdb';
51+
$this->filepath = $argv[1] ?? '';
52+
$this->action = $argv[2] ?? '';
53+
$this->params = array_slice( $argv, 3 );
54+
}
55+
56+
public function run() {
57+
try {
58+
switch ( $this->action ) {
59+
case 'get':
60+
$this->runGet();
61+
break;
62+
case 'list':
63+
$this->runList();
64+
break;
65+
case 'match':
66+
$this->runMatch();
67+
break;
68+
default:
69+
$this->exitCode = 1;
70+
$this->help();
71+
break;
72+
}
73+
} catch ( \Throwable $e ) {
74+
$this->exitCode = 1;
75+
$this->output( $e );
76+
}
77+
}
78+
79+
private function runGet() : void {
80+
if ( count( $this->params ) !== 1 ) {
81+
$this->error( "The 'get' action requires one parameter." );
82+
return;
83+
}
84+
$key = $this->params[0];
85+
86+
$dbr = Reader::open( $this->filepath );
87+
$value = $dbr->get( $key );
88+
if ( $value == false ) {
89+
$this->error( "Key '$key' not found." );
90+
return;
91+
}
92+
$this->output( $value );
93+
}
94+
95+
private function runList() : void {
96+
if ( count( $this->params ) > 1 ) {
97+
$this->error( "The 'list' action accepts only one parameter." );
98+
return;
99+
}
100+
$max = (int)( $this->params[0] ?? '100' );
101+
102+
$dbr = Reader::open( $this->filepath );
103+
$key = $dbr->firstkey();
104+
$count = 0;
105+
while ( $key !== false && $count < $max ) {
106+
$this->output( $key );
107+
$count++;
108+
$key = $dbr->nextkey();
109+
}
110+
if ( $count === $max && $key !== false ) {
111+
$this->output( "\n(more keys exist…)" );
112+
}
113+
}
114+
115+
private function runMatch() : void {
116+
if ( count( $this->params ) !== 1 ) {
117+
$this->error( "The 'match' action requires one parameter." );
118+
return;
119+
}
120+
$pattern = $this->params[0];
121+
if ( preg_match( $pattern, '' ) === false ) {
122+
$this->error( 'Invalid regular expression pattern.' );
123+
return;
124+
}
125+
126+
$dbr = Reader::open( $this->filepath );
127+
$key = $dbr->firstkey();
128+
while ( $key !== false ) {
129+
if ( preg_match( $pattern, $key ) ) {
130+
$this->output( $key );
131+
}
132+
$key = $dbr->nextkey();
133+
}
134+
}
135+
136+
private function help() : void {
137+
$this->output( <<<TEXT
138+
usage: {$this->self} <file> [<action>=list] [<parameters...>]
139+
140+
actions:
141+
get <key> Get the value for a given key.
142+
list [<max>=100] List all keys in the file
143+
match <pattern> List keys matching a regular expression.
144+
The pattern must include delimiters (e.g. / or #).
145+
TEXT
146+
);
147+
}
148+
149+
private function error( string $text ) : void {
150+
$this->exitCode = 1;
151+
$this->output( "\nerror: $text\n------\n" );
152+
$this->help();
153+
}
154+
155+
private function output( string $text ) : void {
156+
fwrite( $this->out, $text . "\n" );
157+
}
158+
159+
public function getExitCode() : int {
160+
return $this->exitCode;
161+
}
162+
}

src/Writer/DBA.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
*/
2828
class DBA extends Writer {
2929
/**
30+
* @param string $fileName
3031
* @throws Exception
3132
*/
3233
public function __construct( $fileName ) {

tests/CliTest.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
namespace Cdb\Test;
3+
4+
use Cdb\Cli;
5+
6+
/**
7+
* @covers Cdb\Cli
8+
*/
9+
class CliTest extends \PHPUnit\Framework\TestCase {
10+
private const FIXTURE = __DIR__ . '/fixture/example.cdb';
11+
private $out;
12+
13+
protected function setUp() : void {
14+
$this->out = fopen( 'php://memory', 'rw' );
15+
}
16+
17+
private function getOutput() {
18+
fseek( $this->out, 0 );
19+
return fread( $this->out, 2048 );
20+
}
21+
22+
public function testContructor() {
23+
$cli = new Cli( $this->out, [] );
24+
$this->assertInstanceOf( Cli::class, $cli );
25+
$this->assertSame( '', $this->getOutput(), 'output' );
26+
$this->assertSame( 0, $cli->getExitCode(), 'exit code' );
27+
}
28+
29+
public function testRunNoargs() {
30+
$cli = new Cli( $this->out, [ 'cdb' ] );
31+
$cli->run();
32+
33+
$this->assertStringContainsString( 'usage: cdb', $this->getOutput(), 'output' );
34+
$this->assertSame( 1, $cli->getExitCode(), 'exit code' );
35+
}
36+
37+
public function testRunGet() {
38+
$cli = new Cli( $this->out, [ 'cdb', self::FIXTURE, 'get', 'answer' ] );
39+
$cli->run();
40+
41+
$this->assertSame( "42\n", $this->getOutput(), 'output' );
42+
$this->assertSame( 0, $cli->getExitCode(), 'exit code' );
43+
}
44+
45+
public function testRunList() {
46+
$cli = new Cli( $this->out, [ 'cdb', self::FIXTURE, 'list' ] );
47+
$cli->run();
48+
49+
$this->assertSame( "foo\nanswer\nfalse\ntrue\n", $this->getOutput(), 'output' );
50+
$this->assertSame( 0, $cli->getExitCode(), 'exit code' );
51+
}
52+
53+
public function testRunListMax() {
54+
$cli = new Cli( $this->out, [ 'cdb', self::FIXTURE, 'list', '3' ] );
55+
$cli->run();
56+
57+
$this->assertSame(
58+
"foo\nanswer\nfalse\n\n(more keys exist…)\n",
59+
$this->getOutput(),
60+
'output'
61+
);
62+
$this->assertSame( 0, $cli->getExitCode(), 'exit code' );
63+
}
64+
65+
public function testRunMatch() {
66+
$cli = new Cli( $this->out, [ 'cdb', self::FIXTURE, 'match', '/a.+e/' ] );
67+
$cli->run();
68+
69+
$this->assertSame( "answer\nfalse\n", $this->getOutput(), 'output' );
70+
$this->assertSame( 0, $cli->getExitCode(), 'exit code' );
71+
}
72+
}

tests/buildFixture.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
require_once __DIR__ . '/../vendor/autoload.php';
3+
4+
$dbw = new Cdb\Writer\PHP( __DIR__ . '/fixture/example.cdb' );
5+
$dbw->set( 'foo', 'bar' );
6+
$dbw->set( 'answer', '42' );
7+
$dbw->set( 'false', '0' );
8+
$dbw->set( 'true', '1' );
9+
$dbw->close();

tests/fixture/example.cdb

2.12 KB
Binary file not shown.

0 commit comments

Comments
 (0)