mirror of
https://github.com/Dushistov/sdcv.git
synced 2025-12-15 17:31:56 +00:00
Add option --json-output (-j)
If given -j, format the output of -l and of searches as JSON.
This commit is contained in:
@@ -248,7 +248,7 @@ void Library::LookupData(const std::string &str, TSearchResultList& res_list)
|
||||
}
|
||||
}
|
||||
|
||||
void Library::print_search_result(FILE *out, const TSearchResult & res)
|
||||
void Library::print_search_result(FILE *out, const TSearchResult & res, bool &first_result)
|
||||
{
|
||||
std::string loc_bookname, loc_def, loc_exp;
|
||||
|
||||
@@ -257,18 +257,30 @@ void Library::print_search_result(FILE *out, const TSearchResult & res)
|
||||
loc_def = utf8_to_locale_ign_err(res.def);
|
||||
loc_exp = utf8_to_locale_ign_err(res.exp);
|
||||
}
|
||||
if(json_) {
|
||||
if(!first_result) {
|
||||
fputs(",", out);
|
||||
} else {
|
||||
first_result=false;
|
||||
}
|
||||
fprintf(out,"{\"dict\": \"%s\",\"word\":\"%s\",\"definition\":\"%s\"}",
|
||||
json_escape_string(res.bookname).c_str(),
|
||||
json_escape_string(res.def).c_str(),
|
||||
json_escape_string(res.exp).c_str());
|
||||
|
||||
fprintf(out,
|
||||
"-->%s%s%s\n"
|
||||
"-->%s%s%s\n"
|
||||
"%s\n\n",
|
||||
colorize_output_ ? NAME_OF_DICT_VISFMT : "",
|
||||
utf8_output_ ? res.bookname.c_str() : loc_bookname.c_str(),
|
||||
colorize_output_ ? ESC_END : "",
|
||||
colorize_output_ ? SEARCH_TERM_VISFMT : "",
|
||||
utf8_output_ ? res.def.c_str() : loc_def.c_str(),
|
||||
colorize_output_ ? ESC_END : "",
|
||||
utf8_output_ ? res.exp.c_str() : loc_exp.c_str());
|
||||
} else {
|
||||
fprintf(out,
|
||||
"-->%s%s%s\n"
|
||||
"-->%s%s%s\n"
|
||||
"%s\n\n",
|
||||
colorize_output_ ? NAME_OF_DICT_VISFMT : "",
|
||||
utf8_output_ ? res.bookname.c_str() : loc_bookname.c_str(),
|
||||
colorize_output_ ? ESC_END : "",
|
||||
colorize_output_ ? SEARCH_TERM_VISFMT : "",
|
||||
utf8_output_ ? res.def.c_str() : loc_def.c_str(),
|
||||
colorize_output_ ? ESC_END : "",
|
||||
utf8_output_ ? res.exp.c_str() : loc_exp.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -346,6 +358,11 @@ bool Library::process_phrase(const char *loc_str, IReadLine &io, bool force)
|
||||
/*nothing*/;
|
||||
}
|
||||
|
||||
sdcv_pager pager(force);
|
||||
bool first_result = true;
|
||||
if(json_) {
|
||||
fputc('[', pager.get_stream());
|
||||
}
|
||||
if (!res_list.empty()) {
|
||||
/* try to be more clever, if there are
|
||||
one or zero results per dictionary show all
|
||||
@@ -370,8 +387,9 @@ bool Library::process_phrase(const char *loc_str, IReadLine &io, bool force)
|
||||
}//if (!force)
|
||||
|
||||
if (!show_all_results && !force) {
|
||||
printf(_("Found %zu items, similar to %s.\n"), res_list.size(),
|
||||
utf8_output_ ? get_impl(str) : utf8_to_locale_ign_err(get_impl(str)).c_str());
|
||||
if(!json_)
|
||||
printf(_("Found %zu items, similar to %s.\n"), res_list.size(),
|
||||
utf8_output_ ? get_impl(str) : utf8_to_locale_ign_err(get_impl(str)).c_str());
|
||||
for (size_t i = 0; i < res_list.size(); ++i) {
|
||||
const std::string loc_bookname = utf8_to_locale_ign_err(res_list[i].bookname);
|
||||
const std::string loc_def = utf8_to_locale_ign_err(res_list[i].def);
|
||||
@@ -390,9 +408,8 @@ bool Library::process_phrase(const char *loc_str, IReadLine &io, bool force)
|
||||
choice_readline->read(_("Your choice[-1 to abort]: "), str_choise);
|
||||
sscanf(str_choise.c_str(), "%d", &choise);
|
||||
if (choise >= 0 && choise < int(res_list.size())) {
|
||||
sdcv_pager pager;
|
||||
io.add_to_history(res_list[choise].def.c_str());
|
||||
print_search_result(pager.get_stream(), res_list[choise]);
|
||||
print_search_result(pager.get_stream(), res_list[choise], first_result);
|
||||
break;
|
||||
} else if (choise == -1){
|
||||
break;
|
||||
@@ -401,20 +418,24 @@ bool Library::process_phrase(const char *loc_str, IReadLine &io, bool force)
|
||||
res_list.size()-1);
|
||||
}
|
||||
} else {
|
||||
sdcv_pager pager(force);
|
||||
fprintf(pager.get_stream(), _("Found %zu items, similar to %s.\n"),
|
||||
res_list.size(), utf8_output_ ? get_impl(str) : utf8_to_locale_ign_err(get_impl(str)).c_str());
|
||||
for (const TSearchResult& search_res : res_list)
|
||||
print_search_result(pager.get_stream(), search_res);
|
||||
for (const TSearchResult& search_res : res_list) {
|
||||
if(!json_)
|
||||
fprintf(pager.get_stream(), _("Found %zu items, similar to %s.\n"),
|
||||
res_list.size(), utf8_output_ ? get_impl(str) : utf8_to_locale_ign_err(get_impl(str)).c_str());
|
||||
print_search_result(pager.get_stream(), search_res, first_result);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
std::string loc_str;
|
||||
if (!utf8_output_)
|
||||
loc_str = utf8_to_locale_ign_err(get_impl(str));
|
||||
|
||||
printf(_("Nothing similar to %s, sorry :(\n"), utf8_output_ ? get_impl(str) : loc_str.c_str());
|
||||
if(!json_)
|
||||
printf(_("Nothing similar to %s, sorry :(\n"), utf8_output_ ? get_impl(str) : loc_str.c_str());
|
||||
}
|
||||
|
||||
if(json_) {
|
||||
fputs("]\n", pager.get_stream());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,19 +25,22 @@ typedef std::vector<TSearchResult> TSearchResultList;
|
||||
//of it
|
||||
class Library : public Libs {
|
||||
public:
|
||||
Library(bool uinput, bool uoutput, bool colorize_output):
|
||||
utf8_input_(uinput), utf8_output_(uoutput), colorize_output_(colorize_output) {}
|
||||
Library(bool uinput, bool uoutput, bool colorize_output, bool use_json)
|
||||
: utf8_input_(uinput), utf8_output_(uoutput), colorize_output_(colorize_output), json_(use_json) {
|
||||
setVerbose(!use_json);
|
||||
}
|
||||
|
||||
bool process_phrase(const char *loc_str, IReadLine &io, bool force = false);
|
||||
private:
|
||||
bool utf8_input_;
|
||||
bool utf8_output_;
|
||||
bool colorize_output_;
|
||||
bool utf8_output_;
|
||||
bool colorize_output_;
|
||||
bool json_;
|
||||
|
||||
void SimpleLookup(const std::string &str, TSearchResultList& res_list);
|
||||
void LookupWithFuzzy(const std::string &str, TSearchResultList& res_list);
|
||||
void LookupWithRule(const std::string &str, TSearchResultList& res_lsit);
|
||||
void LookupData(const std::string &str, TSearchResultList& res_list);
|
||||
void print_search_result(FILE *out, const TSearchResult & res);
|
||||
void print_search_result(FILE *out, const TSearchResult & res, bool &first_result);
|
||||
};
|
||||
|
||||
|
||||
44
src/sdcv.cpp
44
src/sdcv.cpp
@@ -59,7 +59,7 @@ namespace glib
|
||||
using StrArr = ResourceWrapper<gchar *, gchar *, free_str_array>;
|
||||
}
|
||||
|
||||
static void list_dicts(const std::list<std::string> &dicts_dir_list);
|
||||
static void list_dicts(const std::list<std::string> &dicts_dir_list, bool use_json);
|
||||
|
||||
int main(int argc, char *argv[]) try {
|
||||
setlocale(LC_ALL, "");
|
||||
@@ -75,6 +75,7 @@ int main(int argc, char *argv[]) try {
|
||||
gboolean show_list_dicts = FALSE;
|
||||
glib::StrArr use_dict_list;
|
||||
gboolean non_interactive = FALSE;
|
||||
gboolean json_output = FALSE;
|
||||
gboolean utf8_output = FALSE;
|
||||
gboolean utf8_input = FALSE;
|
||||
glib::CharStr opt_data_dir;
|
||||
@@ -91,6 +92,8 @@ int main(int argc, char *argv[]) try {
|
||||
_("bookname") },
|
||||
{ "non-interactive", 'n', 0, G_OPTION_ARG_NONE, &non_interactive,
|
||||
_("for use in scripts"), nullptr },
|
||||
{ "json-output", 'j', 0, G_OPTION_ARG_NONE, &json_output,
|
||||
_("print the result formatted as JSON."), nullptr },
|
||||
{ "utf8-output", '0', 0, G_OPTION_ARG_NONE, &utf8_output,
|
||||
_("output must be in utf8"), nullptr },
|
||||
{ "utf8-input", '1', 0, G_OPTION_ARG_NONE, &utf8_input,
|
||||
@@ -144,7 +147,7 @@ int main(int argc, char *argv[]) try {
|
||||
dicts_dir_list.push_back(std::string(homedir) + G_DIR_SEPARATOR + ".stardict" + G_DIR_SEPARATOR + "dic");
|
||||
dicts_dir_list.push_back(data_dir);
|
||||
if (show_list_dicts) {
|
||||
list_dicts(dicts_dir_list);
|
||||
list_dicts(dicts_dir_list, json_output);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -196,7 +199,7 @@ int main(int argc, char *argv[]) try {
|
||||
fprintf(stderr, _("g_mkdir failed: %s\n"), strerror(errno));
|
||||
}
|
||||
|
||||
Library lib(utf8_input, utf8_output, colorize);
|
||||
Library lib(utf8_input, utf8_output, colorize, json_output);
|
||||
lib.load(dicts_dir_list, order_list, disable_list);
|
||||
|
||||
std::unique_ptr<IReadLine> io(create_readline_object());
|
||||
@@ -209,7 +212,7 @@ int main(int argc, char *argv[]) try {
|
||||
|
||||
std::string phrase;
|
||||
while (io->read(_("Enter word or phrase: "), phrase)) {
|
||||
if (!lib.process_phrase(phrase.c_str(), *io))
|
||||
if (!lib.process_phrase(phrase.c_str(), *io))
|
||||
return EXIT_FAILURE;
|
||||
phrase.clear();
|
||||
}
|
||||
@@ -224,17 +227,32 @@ int main(int argc, char *argv[]) try {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void list_dicts(const std::list<std::string> &dicts_dir_list)
|
||||
static void list_dicts(const std::list<std::string> &dicts_dir_list, bool use_json)
|
||||
{
|
||||
bool first_entry = true;
|
||||
if(!use_json)
|
||||
printf(_("Dictionary's name Word count\n"));
|
||||
std::list<std::string> order_list, disable_list;
|
||||
for_each_file(dicts_dir_list, ".ifo", order_list,
|
||||
disable_list, [](const std::string &filename, bool) -> void {
|
||||
DictInfo dict_info;
|
||||
if (dict_info.load_from_ifo_file(filename, false)) {
|
||||
const std::string bookname = utf8_to_locale_ign_err(dict_info.bookname);
|
||||
printf("%s %d\n", bookname.c_str(), dict_info.wordcount);
|
||||
else
|
||||
fputc('[', stdout);
|
||||
std::list<std::string> order_list, disable_list;
|
||||
for_each_file(dicts_dir_list, ".ifo", order_list,
|
||||
disable_list, [use_json, &first_entry](const std::string &filename, bool) -> void {
|
||||
DictInfo dict_info;
|
||||
if (dict_info.load_from_ifo_file(filename, false)) {
|
||||
const std::string bookname = utf8_to_locale_ign_err(dict_info.bookname);
|
||||
if(use_json) {
|
||||
if(first_entry) {
|
||||
first_entry=false;
|
||||
} else {
|
||||
fputc(',', stdout); // comma between entries
|
||||
}
|
||||
});
|
||||
printf("{\"name\": \"%s\", \"wordcount\": \"%d\"}", json_escape_string(bookname).c_str(), dict_info.wordcount);
|
||||
} else {
|
||||
printf("%s %d\n", bookname.c_str(), dict_info.wordcount);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(use_json)
|
||||
fputs("]\n", stdout);
|
||||
|
||||
}
|
||||
|
||||
@@ -439,7 +439,7 @@ namespace {
|
||||
if (idxfile)
|
||||
fclose(idxfile);
|
||||
}
|
||||
bool load(const std::string& url, gulong wc, gulong fsize) override;
|
||||
bool load(const std::string& url, gulong wc, gulong fsize, bool verbose) override;
|
||||
const gchar *get_key(glong idx) override;
|
||||
void get_data(glong idx) override { get_key(idx); }
|
||||
const gchar *get_key_and_data(glong idx) override {
|
||||
@@ -481,7 +481,7 @@ namespace {
|
||||
const gchar *read_first_on_page_key(glong page_idx);
|
||||
const gchar *get_first_on_page_key(glong page_idx);
|
||||
bool load_cache(const std::string& url);
|
||||
bool save_cache(const std::string& url);
|
||||
bool save_cache(const std::string& url, bool verbose);
|
||||
static std::list<std::string> get_cache_variant(const std::string& url);
|
||||
};
|
||||
|
||||
@@ -492,7 +492,7 @@ namespace {
|
||||
public:
|
||||
WordListIndex() : idxdatabuf(nullptr) {}
|
||||
~WordListIndex() { g_free(idxdatabuf); }
|
||||
bool load(const std::string& url, gulong wc, gulong fsize) override;
|
||||
bool load(const std::string& url, gulong wc, gulong fsize, bool verbose) override;
|
||||
const gchar *get_key(glong idx) override { return wordlist[idx]; }
|
||||
void get_data(glong idx) override;
|
||||
const gchar *get_key_and_data(glong idx) override {
|
||||
@@ -592,7 +592,7 @@ namespace {
|
||||
return res;
|
||||
}
|
||||
|
||||
bool OffsetIndex::save_cache(const std::string& url)
|
||||
bool OffsetIndex::save_cache(const std::string& url, bool verbose)
|
||||
{
|
||||
const std::list<std::string> vars = get_cache_variant(url);
|
||||
for (const std::string& item : vars) {
|
||||
@@ -604,13 +604,15 @@ namespace {
|
||||
if (fwrite(&wordoffset[0], sizeof(wordoffset[0]), wordoffset.size(), out)!=wordoffset.size())
|
||||
continue;
|
||||
fclose(out);
|
||||
printf("save to cache %s\n", url.c_str());
|
||||
if(verbose) {
|
||||
printf("save to cache %s\n", url.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OffsetIndex::load(const std::string& url, gulong wc, gulong fsize)
|
||||
bool OffsetIndex::load(const std::string& url, gulong wc, gulong fsize, bool verbose)
|
||||
{
|
||||
wordcount=wc;
|
||||
gulong npages=(wc-1)/ENTR_PER_PAGE+2;
|
||||
@@ -633,7 +635,7 @@ namespace {
|
||||
p1 += index_size;
|
||||
}
|
||||
wordoffset[j]=p1-idxdatabuffer;
|
||||
if (!save_cache(url))
|
||||
if (!save_cache(url, verbose))
|
||||
fprintf(stderr, "cache update failed\n");
|
||||
}
|
||||
|
||||
@@ -741,7 +743,7 @@ namespace {
|
||||
return bFound;
|
||||
}
|
||||
|
||||
bool WordListIndex::load(const std::string& url, gulong wc, gulong fsize)
|
||||
bool WordListIndex::load(const std::string& url, gulong wc, gulong fsize, bool verbose)
|
||||
{
|
||||
gzFile in = gzopen(url.c_str(), "rb");
|
||||
if (in == nullptr)
|
||||
@@ -856,7 +858,7 @@ bool Dict::Lookup(const char *str, glong &idx) {
|
||||
return idx_file->lookup(str, idx);
|
||||
}
|
||||
|
||||
bool Dict::load(const std::string& ifofilename)
|
||||
bool Dict::load(const std::string& ifofilename, bool verbose)
|
||||
{
|
||||
gulong idxfilesize;
|
||||
if (!load_ifofile(ifofilename, idxfilesize))
|
||||
@@ -890,7 +892,7 @@ bool Dict::load(const std::string& ifofilename)
|
||||
idx_file.reset(new OffsetIndex);
|
||||
}
|
||||
|
||||
if (!idx_file->load(fullfilename, wordcount, idxfilesize))
|
||||
if (!idx_file->load(fullfilename, wordcount, idxfilesize, verbose))
|
||||
return false;
|
||||
|
||||
fullfilename=ifofilename;
|
||||
@@ -944,7 +946,7 @@ Libs::~Libs()
|
||||
void Libs::load_dict(const std::string& url)
|
||||
{
|
||||
Dict *lib=new Dict;
|
||||
if (lib->load(url))
|
||||
if (lib->load(url, verbose_))
|
||||
oLib.push_back(lib);
|
||||
else
|
||||
delete lib;
|
||||
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
guint32 wordentry_size;
|
||||
|
||||
virtual ~IIndexFile() {}
|
||||
virtual bool load(const std::string& url, gulong wc, gulong fsize) = 0;
|
||||
virtual bool load(const std::string& url, gulong wc, gulong fsize, bool verbose) = 0;
|
||||
virtual const gchar *get_key(glong idx) = 0;
|
||||
virtual void get_data(glong idx) = 0;
|
||||
virtual const gchar *get_key_and_data(glong idx) = 0;
|
||||
@@ -105,9 +105,9 @@ private:
|
||||
class Dict : public DictBase {
|
||||
public:
|
||||
Dict() {}
|
||||
Dict(const Dict&) = delete;
|
||||
Dict& operator=(const Dict&) = delete;
|
||||
bool load(const std::string& ifofilename);
|
||||
Dict(const Dict&) = delete;
|
||||
Dict& operator=(const Dict&) = delete;
|
||||
bool load(const std::string& ifofilename, bool verbose);
|
||||
|
||||
gulong narticles() const { return wordcount; }
|
||||
const std::string& dict_name() const { return bookname; }
|
||||
@@ -141,12 +141,13 @@ private:
|
||||
class Libs {
|
||||
public:
|
||||
Libs(std::function<void(void)> f = std::function<void(void)>()) {
|
||||
progress_func = f;
|
||||
iMaxFuzzyDistance = MAX_FUZZY_DISTANCE; //need to read from cfg.
|
||||
}
|
||||
progress_func = f;
|
||||
iMaxFuzzyDistance = MAX_FUZZY_DISTANCE; //need to read from cfg.
|
||||
}
|
||||
void setVerbose(bool verbose) { verbose_ = verbose; }
|
||||
~Libs();
|
||||
Libs(const Libs&) = delete;
|
||||
Libs& operator=(const Libs&) = delete;
|
||||
Libs(const Libs&) = delete;
|
||||
Libs& operator=(const Libs&) = delete;
|
||||
|
||||
void load_dict(const std::string& url);
|
||||
void load(const std::list<std::string>& dicts_dirs,
|
||||
@@ -180,7 +181,8 @@ public:
|
||||
private:
|
||||
std::vector<Dict *> oLib; // word Libs.
|
||||
int iMaxFuzzyDistance;
|
||||
std::function<void(void)> progress_func;
|
||||
std::function<void(void)> progress_func;
|
||||
bool verbose_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
@@ -90,3 +92,27 @@ void for_each_file(const std::list<std::string>& dirs_list, const std::string& s
|
||||
for (const std::string& item : dirs_list)
|
||||
__for_each_file(item, suff, order_list, disable_list, f);
|
||||
}
|
||||
|
||||
// based on https://stackoverflow.com/questions/7724448/simple-json-string-escape-for-c/33799784#33799784
|
||||
std::string json_escape_string(const std::string &s) {
|
||||
std::ostringstream o;
|
||||
for (auto c = s.cbegin(); c != s.cend(); c++) {
|
||||
switch (*c) {
|
||||
case '"': o << "\\\""; break;
|
||||
case '\\': o << "\\\\"; break;
|
||||
case '\b': o << "\\b"; break;
|
||||
case '\f': o << "\\f"; break;
|
||||
case '\n': o << "\\n"; break;
|
||||
case '\r': o << "\\r"; break;
|
||||
case '\t': o << "\\t"; break;
|
||||
default:
|
||||
if ('\x00' <= *c && *c <= '\x1f') {
|
||||
o << "\\u"
|
||||
<< std::hex << std::setw(4) << std::setfill('0') << (int)*c;
|
||||
} else {
|
||||
o << *c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return o.str();
|
||||
}
|
||||
|
||||
@@ -60,3 +60,4 @@ extern std::string utf8_to_locale_ign_err(const std::string& utf8_str);
|
||||
extern void for_each_file(const std::list<std::string>& dirs_list, const std::string& suff,
|
||||
const std::list<std::string>& order_list, const std::list<std::string>& disable_list,
|
||||
const std::function<void (const std::string&, bool)>& f);
|
||||
extern std::string json_escape_string(const std::string &str);
|
||||
|
||||
Reference in New Issue
Block a user