Cleanup
[jmdict-cli.git] / jmdict.cpp
1 /*
2 jmdict, a frontend to the JMdict file. http://mandrill.fuxx0r.net/jmdict.php
3 Copyright (C) 2004 Florian Bluemel (florian.bluemel@uni-dortmund.de)
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 */
19 #include <cstdlib>
20 #include <iostream>
21 #include <ostream>
22 #include <iomanip>
23 #include <string>
24 #include <stdexcept>
25 #include <exception>
26 #include <memory>
27 #include <unistd.h>
28 #include "sqlite.h"
29 #include "kana2romaji.h"
30 using namespace std;
31
32 void usage() {
33     cout << "jmdict [options] subject\n"
34             "  -b        search for entries beginning with <subject>\n"
35             "  -f        perform a fulltext search\n"
36             "  -i        case-insensitive search (implied by -b or -f)\n"
37             "  -r        also translate kana to romaji\n"
38             "\n"
39             "  -j        translate from japanese\n"
40             "  -J        translate to japanese\n"
41             "            if neither -j nor -J is given, source language will be guessed\n"
42             "\n"
43             "  -l lang   target language is lang, where lang is a three-letter language code\n"
44             "            default: eng\n";
45 }
46
47 namespace options {
48     enum Language { UNKNOWN, JAPANESE, JAPANESE_ROMAJI, NOT_JAPANESE };
49
50     Language source = UNKNOWN;
51     string target("eng");
52     bool fulltext = false;
53     bool beginning = false;
54     bool ci_search = false;
55     bool show_romaji = false;
56
57     void getFrom(int argc, char** argv) {
58         int opt;
59         while ((opt = getopt(argc, argv, "bfirjJl:")) != -1)
60             switch (opt) {
61                 case 'b':   beginning = true;       break;
62                 case 'f':   fulltext = true;        break;
63                 case 'i':   ci_search = true;       break;
64                 case 'r':   show_romaji = true;     break;
65                 case 'j':   source = JAPANESE;      break;
66                 case 'J':   source = NOT_JAPANESE;  break;
67                 case 'l':   target = optarg;        break;
68                 case '?':   throw invalid_argument(string("unrecognized option"));
69             }
70     }
71 }
72
73 auto_ptr<sql::db> db;
74 unsigned entries(0);
75
76 int accumulate(void* to, int, char** what, char**) {
77     string& app = *static_cast<string*>(to);
78     if (app.size())
79         app += ", ";
80     app += *what;
81     return 0;
82 }
83
84 int showGloss(void* s, int, char** value, char**) {
85     string& sense = *static_cast<string*>(s);
86     if (sense != value[0]) {
87         sense = value[0];
88         cout << "  " << setw(2) << sense << ")  ";
89     }
90     else
91         cout << "       ";
92     cout << value[1] << endl;
93     return 0;
94 }
95
96 int showEntry(void*, int, char** value, char**) {
97     string kanji, kana;
98     db->exec(
99         sql::query("SELECT kanji FROM kanji WHERE entry=%s") % *value,
100         accumulate, &kanji);
101     db->exec(
102         sql::query("SELECT kana FROM reading WHERE entry=%s") % *value,
103         accumulate, &kana);
104
105     if (kanji.size())
106         cout << kanji << " (" << kana << ')';
107     else
108         cout << kana;
109
110     if(options::show_romaji) {
111         string rom;
112         kana2romaji(kana,rom);
113
114         cout << " (" << rom << ')';
115     }
116
117     cout << endl;
118     
119     string sense;
120     db->exec(
121         sql::query("SELECT sense, gloss FROM gloss WHERE lang=%Q AND entry=%s "
122                    "ORDER BY sense") % options::target % *value,
123         showGloss, &sense);
124     ++entries;
125     return 0;
126 }
127
128 string compare() {
129     if (options::fulltext)
130         return " LIKE '%%%q%%'";
131     if (options::beginning)
132         return " LIKE '%q%%'";
133     if (options::ci_search)
134         return " LIKE %Q";
135     return "=%Q";
136 }
137
138 void fromRomaji(const string& r) {
139     db->exec(
140         sql::query("SELECT DISTINCT entry FROM reading WHERE romaji" + compare()) % r,
141         showEntry);
142 }
143
144 void fromJapanese(const string& j) {
145     db->exec(
146         sql::query("SELECT DISTINCT entry FROM reading WHERE kana" + compare()) % j,
147         showEntry);
148     db->exec(
149         sql::query("SELECT DISTINCT entry FROM kanji WHERE kanji" + compare()) % j,
150         showEntry);
151 }
152
153 void toJapanese(const string& e) {
154     sql::query q;
155     q = "SELECT DISTINCT entry FROM gloss WHERE lang=%Q AND gloss" + compare();
156     db->exec(q % options::target % e, showEntry);
157 }
158
159 void guessLanguage(const std::string& subject) {
160     bool isUTF8 = subject[0] & 0x80;
161     if (options::source == options::JAPANESE && !isUTF8)
162         options::source = options::JAPANESE_ROMAJI;
163     else if (options::source == options::UNKNOWN)
164         options::source = isUTF8 ? options::JAPANESE : options::UNKNOWN;
165 }
166
167 int main(int argc, char** argv)
168 try {
169     initRomaji();
170     options::getFrom(argc, argv);
171     if (optind == argc) {
172         usage();
173         return EXIT_FAILURE;
174     }
175     string subject = argv[optind];
176     db.reset(new sql::db(DICTIONARY_PATH));
177     
178     guessLanguage(subject);
179     if (options::source == options::JAPANESE)
180         fromJapanese(subject);
181     else if (options::source == options::JAPANESE_ROMAJI)
182         fromRomaji(subject);
183     else if (options::source == options::NOT_JAPANESE)
184         toJapanese(subject);
185     else { /* options::UNKNOWN */
186         fromRomaji(subject);
187         toJapanese(subject);
188     }
189     cout << entries << " match(es) found." << endl;
190
191     return EXIT_SUCCESS;
192 }
193 catch(const std::exception& e)
194 {
195     cerr << e.what() << '\n';
196     return EXIT_FAILURE;
197 }