1 /** @file xapian-inspect.cc
2 * @brief Inspect the contents of a glass table for development or debugging.
4 /* Copyright (C) 2007,2008,2009,2010,2011,2012 Olly Betts
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 #include <cstdio> // For sprintf().
28 #include "glass_cursor.h"
29 #include "glass_table.h"
30 #include "glass_version.h"
31 #include "filetests.h"
32 #include "stringutils.h"
36 #include "gnu_getopt.h"
40 #define PROG_NAME "xapian-inspect"
41 #define PROG_DESC "Inspect the contents of a glass table for development or debugging"
46 static bool keys
= true, tags
= true;
48 static void show_usage() {
49 cout
<< "Usage: " PROG_NAME
" [OPTIONS] TABLE\n\n"
51 " --help display this help and exit\n"
52 " --version output version information and exit" << endl
;
56 display_nicely(const string
& data
) {
57 string::const_iterator i
;
58 for (i
= data
.begin(); i
!= data
.end(); ++i
) {
59 unsigned char ch
= *i
;
60 if (ch
< 32 || ch
>= 127) {
62 case '\n': cout
<< "\\n"; break;
63 case '\r': cout
<< "\\r"; break;
64 case '\t': cout
<< "\\t"; break;
67 sprintf(buf
, "\\x%02x", int(ch
));
71 } else if (ch
== '\\') {
83 "next : Next entry (alias 'n' or '')\n"
84 "prev : Previous entry (alias 'p')\n"
85 "goto K : Goto entry with key K (alias 'g')\n"
86 "until K: Display entries until key K (alias 'u')\n"
87 "open T : Open table T instead (alias 'o') - e.g. open postlist\n"
88 "keys : Toggle showing keys (default: true) (alias 'k')\n"
89 "tags : Toggle showing tags (default: true) (alias 't')\n"
90 "help : Show this (alias 'h' or '?')\n"
91 "quit : Quit this utility (alias 'q')" << endl
;
95 show_entry(GlassCursor
& cursor
)
97 if (cursor
.after_end()) {
98 cout
<< "After end" << endl
;
103 display_nicely(cursor
.current_key
);
109 display_nicely(cursor
.current_tag
);
115 do_until(GlassCursor
& cursor
, const string
& target
)
117 if (cursor
.after_end()) {
118 cout
<< "At end already." << endl
;
122 if (!target
.empty()) {
123 int cmp
= target
.compare(cursor
.current_key
);
126 cout
<< "Already after specified key." << endl
;
128 cout
<< "Already at specified key." << endl
;
133 while (cursor
.next()) {
135 if (!target
.empty()) {
136 cmp
= target
.compare(cursor
.current_key
);
138 cout
<< "No exact match, stopping at entry before." << endl
;
139 cursor
.find_entry_lt(cursor
.current_key
);
149 cout
<< "Reached end." << endl
;
153 main(int argc
, char **argv
)
155 const struct option long_opts
[] = {
156 {"help", no_argument
, 0, OPT_HELP
},
157 {"version", no_argument
, 0, OPT_VERSION
},
162 while ((c
= gnu_getopt_long(argc
, argv
, "", long_opts
, 0)) != -1) {
165 cout
<< PROG_NAME
" - " PROG_DESC
"\n\n";
169 cout
<< PROG_NAME
" - " PACKAGE_STRING
<< endl
;
177 if (argc
- optind
!= 1) {
182 // Path to the table to inspect.
183 string
table_name(argv
[optind
]);
184 bool arg_is_directory
= dir_exists(table_name
);
185 if (endswith(table_name
, ".DB"))
186 table_name
.resize(table_name
.size() - 2);
187 else if (!endswith(table_name
, '.'))
189 if (arg_is_directory
&& !file_exists(table_name
+ "DB")) {
190 cerr
<< argv
[0] << ": You need to specify a single Btree table, not a database directory." << endl
;
195 size_t slash
= table_name
.rfind('/');
196 if (slash
!= string::npos
) {
197 db_dir
.assign(table_name
, 0, slash
);
201 GlassVersion
version_file(db_dir
);
203 glass_revision_number_t rev
= version_file
.get_revision();
208 open_different_table
:
210 Glass::table_type table_code
;
211 if (endswith(table_name
, "docdata.")) {
212 table_code
= Glass::DOCDATA
;
213 } else if (endswith(table_name
, "spelling.")) {
214 table_code
= Glass::SPELLING
;
215 } else if (endswith(table_name
, "synonym.")) {
216 table_code
= Glass::SYNONYM
;
217 } else if (endswith(table_name
, "termlist.")) {
218 table_code
= Glass::TERMLIST
;
219 } else if (endswith(table_name
, "position.")) {
220 table_code
= Glass::POSITION
;
221 } else if (endswith(table_name
, "postlist.")) {
222 table_code
= Glass::POSTLIST
;
224 cout
<< "Unknown table." << endl
;
228 GlassTable
table("", table_name
, true);
229 table
.open(0, version_file
.get_root(table_code
), rev
);
231 cout
<< "No entries!" << endl
;
235 GlassCursor
cursor(&table
);
236 cursor
.find_entry(string());
242 cout
<< "? " << flush
;
246 if (cin
.eof()) break;
248 if (endswith(input
, '\r'))
249 input
.resize(input
.size() - 1);
251 if (input
.empty() || input
== "n" || input
== "next") {
252 if (cursor
.after_end() || !cursor
.next()) {
253 cout
<< "At end already." << endl
;
257 } else if (input
== "p" || input
== "prev") {
258 // If the cursor has fallen off the end, point it back at
260 if (cursor
.after_end()) cursor
.find_entry(cursor
.current_key
);
261 cursor
.find_entry_lt(cursor
.current_key
);
262 if (cursor
.current_key
.empty()) {
263 cout
<< "At start already." << endl
;
267 } else if (startswith(input
, "u ")) {
268 do_until(cursor
, input
.substr(2));
270 } else if (startswith(input
, "until ")) {
271 do_until(cursor
, input
.substr(6));
273 } else if (input
== "u" || input
== "until") {
274 do_until(cursor
, string());
276 } else if (startswith(input
, "g ")) {
277 if (!cursor
.find_entry(input
.substr(2))) {
278 cout
<< "No exact match, going to entry before." << endl
;
281 } else if (startswith(input
, "goto ")) {
282 if (!cursor
.find_entry(input
.substr(5))) {
283 cout
<< "No exact match, going to entry before." << endl
;
286 } else if (startswith(input
, "o ")) {
288 if (!table_name
.empty()) table_name
+= '/';
289 table_name
+= input
.substr(2);
290 if (endswith(table_name
, ".DB"))
291 table_name
.resize(table_name
.size() - 2);
292 else if (!endswith(table_name
, '.'))
294 goto open_different_table
;
295 } else if (startswith(input
, "open ")) {
297 if (!table_name
.empty()) table_name
+= '/';
298 table_name
+= input
.substr(5);
299 if (endswith(table_name
, ".DB"))
300 table_name
.resize(table_name
.size() - 2);
301 else if (!endswith(table_name
, '.'))
303 goto open_different_table
;
304 } else if (input
== "t" || input
== "tags") {
306 cout
<< "Showing tags: " << boolalpha
<< tags
<< endl
;
307 } else if (input
== "k" || input
== "keys") {
309 cout
<< "Showing keys: " << boolalpha
<< keys
<< endl
;
310 } else if (input
== "q" || input
== "quit") {
312 } else if (input
== "h" || input
== "help" || input
== "?") {
316 cout
<< "Unknown command." << endl
;
320 } catch (const Xapian::Error
&error
) {
321 cerr
<< argv
[0] << ": " << error
.get_description() << endl
;