Add option --json-output (-j)

If given -j, format the output of -l and of searches as JSON.
This commit is contained in:
Peter
2017-07-07 08:46:26 +02:00
parent 5f0f6e036f
commit 3105823e8b
10 changed files with 162 additions and 62 deletions

View File

@@ -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;
}

View File

@@ -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);
};

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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_;
};

View File

@@ -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();
}

View File

@@ -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);