Make TEST_EQUAL() arguments side-effect free
[xapian.git] / xapian-core / bin / xapian-inspect.cc
blob0be2a4455f266524b4316873eb54aa93e1a5f031
1 /** @file xapian-inspect.cc
2 * @brief Inspect the contents of a glass table for development or debugging.
3 */
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
21 #include <config.h>
23 #include <iomanip>
24 #include <iostream>
25 #include <string>
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"
34 #include <xapian.h>
36 #include "gnu_getopt.h"
38 using namespace std;
40 #define PROG_NAME "xapian-inspect"
41 #define PROG_DESC "Inspect the contents of a glass table for development or debugging"
43 #define OPT_HELP 1
44 #define OPT_VERSION 2
46 static bool keys = true, tags = true;
48 static void show_usage() {
49 cout << "Usage: " PROG_NAME " [OPTIONS] TABLE\n\n"
50 "Options:\n"
51 " --help display this help and exit\n"
52 " --version output version information and exit" << endl;
55 static void
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) {
61 switch (ch) {
62 case '\n': cout << "\\n"; break;
63 case '\r': cout << "\\r"; break;
64 case '\t': cout << "\\t"; break;
65 default: {
66 char buf[20];
67 sprintf(buf, "\\x%02x", int(ch));
68 cout << buf;
71 } else if (ch == '\\') {
72 cout << "\\\\";
73 } else {
74 cout << ch;
79 static void
80 show_help()
82 cout << "Commands:\n"
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;
94 static void
95 show_entry(GlassCursor & cursor)
97 if (cursor.after_end()) {
98 cout << "After end" << endl;
99 return;
101 if (keys) {
102 cout << "Key: ";
103 display_nicely(cursor.current_key);
104 cout << endl;
106 if (tags) {
107 cout << "Tag: ";
108 cursor.read_tag();
109 display_nicely(cursor.current_tag);
110 cout << endl;
114 static void
115 do_until(GlassCursor & cursor, const string & target)
117 if (cursor.after_end()) {
118 cout << "At end already." << endl;
119 return;
122 if (!target.empty()) {
123 int cmp = target.compare(cursor.current_key);
124 if (cmp <= 0) {
125 if (cmp)
126 cout << "Already after specified key." << endl;
127 else
128 cout << "Already at specified key." << endl;
129 return;
133 while (cursor.next()) {
134 int cmp = 1;
135 if (!target.empty()) {
136 cmp = target.compare(cursor.current_key);
137 if (cmp < 0) {
138 cout << "No exact match, stopping at entry before." << endl;
139 cursor.find_entry_lt(cursor.current_key);
140 return;
143 show_entry(cursor);
144 if (cmp == 0) {
145 return;
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},
158 {NULL, 0, 0, 0}
161 int c;
162 while ((c = gnu_getopt_long(argc, argv, "", long_opts, 0)) != -1) {
163 switch (c) {
164 case OPT_HELP:
165 cout << PROG_NAME " - " PROG_DESC "\n\n";
166 show_usage();
167 exit(0);
168 case OPT_VERSION:
169 cout << PROG_NAME " - " PACKAGE_STRING << endl;
170 exit(0);
171 default:
172 show_usage();
173 exit(1);
177 if (argc - optind != 1) {
178 show_usage();
179 exit(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, '.'))
188 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;
191 exit(1);
194 string db_dir;
195 size_t slash = table_name.rfind('/');
196 if (slash != string::npos) {
197 db_dir.assign(table_name, 0, slash);
198 } else {
199 db_dir = ".";
201 GlassVersion version_file(db_dir);
202 version_file.read();
203 glass_revision_number_t rev = version_file.get_revision();
205 show_help();
206 cout << endl;
208 open_different_table:
209 try {
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;
223 } else {
224 cout << "Unknown table." << endl;
225 exit(1);
228 GlassTable table("", table_name, true);
229 table.open(0, version_file.get_root(table_code), rev);
230 if (table.empty()) {
231 cout << "No entries!" << endl;
232 exit(0);
235 GlassCursor cursor(&table);
236 cursor.find_entry(string());
237 cursor.next();
239 while (!cin.eof()) {
240 show_entry(cursor);
241 wait_for_input:
242 cout << "? " << flush;
244 string input;
245 getline(cin, input);
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;
254 goto wait_for_input;
256 continue;
257 } else if (input == "p" || input == "prev") {
258 // If the cursor has fallen off the end, point it back at
259 // the last entry.
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;
264 goto wait_for_input;
266 continue;
267 } else if (startswith(input, "u ")) {
268 do_until(cursor, input.substr(2));
269 goto wait_for_input;
270 } else if (startswith(input, "until ")) {
271 do_until(cursor, input.substr(6));
272 goto wait_for_input;
273 } else if (input == "u" || input == "until") {
274 do_until(cursor, string());
275 goto wait_for_input;
276 } else if (startswith(input, "g ")) {
277 if (!cursor.find_entry(input.substr(2))) {
278 cout << "No exact match, going to entry before." << endl;
280 continue;
281 } else if (startswith(input, "goto ")) {
282 if (!cursor.find_entry(input.substr(5))) {
283 cout << "No exact match, going to entry before." << endl;
285 continue;
286 } else if (startswith(input, "o ")) {
287 table_name = db_dir;
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, '.'))
293 table_name += '.';
294 goto open_different_table;
295 } else if (startswith(input, "open ")) {
296 table_name = db_dir;
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, '.'))
302 table_name += '.';
303 goto open_different_table;
304 } else if (input == "t" || input == "tags") {
305 tags = !tags;
306 cout << "Showing tags: " << boolalpha << tags << endl;
307 } else if (input == "k" || input == "keys") {
308 keys = !keys;
309 cout << "Showing keys: " << boolalpha << keys << endl;
310 } else if (input == "q" || input == "quit") {
311 break;
312 } else if (input == "h" || input == "help" || input == "?") {
313 show_help();
314 goto wait_for_input;
315 } else {
316 cout << "Unknown command." << endl;
317 goto wait_for_input;
320 } catch (const Xapian::Error &error) {
321 cerr << argv[0] << ": " << error.get_description() << endl;
322 exit(1);