a51f61bf3cf9957ff1a52c6fd133a1690ba656f4
[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 "sqlite.h"
28 #include "kana2romaji.h"
29 using namespace std;
30
31 void usage() {
32     cout << "jmdict [options] subject\n"
33             "  -b        search for entries beginning with <subject>\n"
34             "  -f        perform a fulltext search\n"
35             "  -i        case-insensitive search (implied by -b or -f)\n"
36             "  -r        also translate kana to romaji\n"
37             "\n"
38             "  -j        translate from japanese\n"
39             "  -J        translate to japanese\n"
40             "            if neither -j nor -J is given, source language will be guessed\n"
41             "\n"
42             "  -l lang   target language is lang, where lang is a three-letter language code\n"
43             "            default: eng\n";
44 }
45
46 namespace options {
47     enum Language { UNKNOWN, JAPANESE, JAPANESE_ROMAJI, NOT_JAPANESE };
48
49     Language source = UNKNOWN;
50     string target("eng");
51     bool fulltext = false;
52     bool beginning = false;
53     bool ci_search = false;
54     bool show_romaji = false;
55
56     void getFrom(int argc, char** argv) {
57         int opt;
58         while ((opt = getopt(argc, argv, "bfirjJl:")) != -1)
59             switch (opt) {
60                 case 'b':   beginning = true;       break;
61                 case 'f':   fulltext = true;        break;
62                 case 'i':   ci_search = true;       break;
63                 case 'r':   show_romaji = true;     break;
64                 case 'j':   source = JAPANESE;      break;
65                 case 'J':   source = NOT_JAPANESE;  break;
66                 case 'l':   target = optarg;        break;
67                 case '?':   throw invalid_argument(string("unrecognized option"));
68             }
69     }
70 }
71
72 auto_ptr<sql::db> db;
73 unsigned entries(0);
74
75 int accumulate(void* to, int, char** what, char**) {
76     string& app = *static_cast<string*>(to);
77     if (app.size())
78         app += ", ";
79     app += *what;
80     return 0;
81 }
82
83 int showGloss(void* s, int, char** value, char**) {
84     string& sense = *static_cast<string*>(s);
85     if (sense != value[0]) {
86         sense = value[0];
87         cout << "  " << setw(2) << sense << ")  ";
88     }
89     else
90         cout << "       ";
91     cout << value[1] << endl;
92     return 0;
93 }
94
95 int showEntry(void*, int, char** value, char**) {
96     string kanji, kana;
97     db->exec(
98         sql::query("SELECT kanji FROM kanji WHERE entry=%s") % *value,
99         accumulate, &kanji);
100     db->exec(
101         sql::query("SELECT kana FROM reading WHERE entry=%s") % *value,
102         accumulate, &kana);
103
104     if (kanji.size())
105         cout << kanji << " (" << kana << ')';
106     else
107         cout << kana;
108
109     if(options::show_romaji) {
110         string rom;
111         kana2romaji(kana,rom);
112
113         cout << " (" << rom << ')';
114     }
115
116     cout << endl;
117     
118     string sense;
119     db->exec(
120         sql::query("SELECT sense, gloss FROM gloss WHERE lang=%Q AND entry=%s "
121                    "ORDER BY sense") % options::target % *value,
122         showGloss, &sense);
123     ++entries;
124     return 0;
125 }
126
127 string compare() {
128     if (options::fulltext)
129         return " LIKE '%%%q%%'";
130     if (options::beginning)
131         return " LIKE '%q%%'";
132     if (options::ci_search)
133         return " LIKE %Q";
134     return "=%Q";
135 }
136
137 void fromRomaji(const string& r) {
138     db->exec(
139         sql::query("SELECT DISTINCT entry FROM reading WHERE romaji" + compare()) % r,
140         showEntry);
141 }
142
143 void fromJapanese(const string& j) {
144     db->exec(
145         sql::query("SELECT DISTINCT entry FROM reading WHERE kana" + compare()) % j,
146         showEntry);
147     db->exec(
148         sql::query("SELECT DISTINCT entry FROM kanji WHERE kanji" + compare()) % j,
149         showEntry);
150 }
151
152 void toJapanese(const string& e) {
153     sql::query q;
154     q = "SELECT DISTINCT entry FROM gloss WHERE lang=%Q AND gloss" + compare();
155     db->exec(q % options::target % e, showEntry);
156 }
157
158 void guessLanguage(const std::string& subject) {
159     bool isUTF8 = subject[0] & 0x80;
160     if (options::source == options::JAPANESE && !isUTF8)
161         options::source = options::JAPANESE_ROMAJI;
162     else if (options::source == options::UNKNOWN)
163         options::source = isUTF8 ? options::JAPANESE : options::NOT_JAPANESE;
164 }
165
166 int main(int argc, char** argv)
167 try {
168     initRomaji();
169     options::getFrom(argc, argv);
170     if (optind == argc) {
171         usage();
172         return EXIT_FAILURE;
173     }
174     string subject = argv[optind];
175     db.reset(new sql::db(DICTIONARY_PATH));
176     
177     guessLanguage(subject);
178     if (options::source == options::JAPANESE)
179         fromJapanese(subject);
180     else if (options::source == options::JAPANESE_ROMAJI)
181         fromRomaji(subject);
182     else
183         toJapanese(subject);
184     cout << entries << " match(es) found." << endl;
185
186     return EXIT_SUCCESS;
187 }
188 catch(const std::exception& e)
189 {
190     cerr << e.what() << '\n';
191     return EXIT_FAILURE;
192 }