forked from ldn-softdev/jtc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jtc.cpp
1862 lines (1518 loc) · 81.3 KB
/
jtc.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include <iostream>
#include <fstream>
#include <sstream>
#include <deque>
#include <set>
#include <climits> // LONG_MAX
#include <algorithm>
#include "lib/getoptions.hpp"
#include "lib/Json.hpp"
#include "lib/Streamstr.hpp"
#include "lib/shell.hpp"
#include "lib/dbg.hpp"
using namespace std;
#define VERSION "1.74"
#define CREATOR "Dmitry Lyssenko"
#define EMAIL "ldn.softdev@gmail.com"
// jtc: fast a powerful utility to manipulate json format
//
// jtc main features:
// o walk-path: a set of lexemes (subscripts, searches, directives) which define how
// jtc should walk the JSON tree (+ supported REGEX)
// o cache: most of the lexemes (all except few dynamic types) cached when walked,
// cache facilitates super fast walking even huge JSON trees
// o namespaces: let "memorizing" and reusing memorized JSON values later for interpolation
// o templating: let produce custom json out of static and previously memorized values
// in the namespace, allows jsonizing stringified JSONs and stringify JSONs
// o buffered and streamed inputs: the former provides a fast read and parsing, the latter
// facilitates on-fly json manipulation
// and many more other features
// option definitions
#define OPT_RDT -
#define OPT_ALL a
#define OPT_CMP c
#define OPT_DBG d
#define OPT_EXE e
#define OPT_FRC f
#define OPT_GDE g
#define OPT_INS i
#define OPT_JAL J
#define OPT_JSN j
#define OPT_LBL l
#define OPT_MDF m
#define OPT_SEQ n
#define OPT_PRG p
#define OPT_QUT q
#define OPT_RAW r
#define OPT_SWP s
#define OPT_TMP T
#define OPT_IND t
#define OPT_UPD u
#define OPT_WLK w
#define OPT_CMN x
#define OPT_PRT y
#define OPT_SZE z
// facilitate option materialization
#define STR(X) XSTR(X)
#define XSTR(X) #X
#define CHR(X) XCHR(X)
#define XCHR(X) *#X
#define NULL_CHR '\0'
#define RSLD_CHR '\\'
#define NLNE_CHR '\n'
#define CRTN_CHR '\r'
#define SQTE_CHR '\''
#define DQTE_CHR '"'
#define SPCE_CHR ' '
#define CMP_BASE "json_1"
#define CMP_COMP "json_2"
#define SIZE_PFX "size: "
#define WLK_HPFX "$?"
#include "lib/jtc_guide.hpp"
// various return codes
#define RETURN_CODES \
RC_OK, \
RC_UNUSED, \
RC_WP_INV, \
RC_SC_MISS, \
RC_CMP_NEQ, \
RC_END
ENUM(ReturnCodes, RETURN_CODES)
// return code exception offsets
#define OFF_GETOPT RC_END // offset for Getopt exceptions
#define OFF_JSON (OFF_GETOPT + Getopt::end_of_throw) // offset for Json exceptions
#define OFF_REGEX (OFF_JSON + Jnode::end_of_throw) // offset for Regex exceptions
// simple macro for to expose class aliases
// usage: REVEAL(jtc, opt, json)
#define __REVEAL_TKN1__(X,Y) X ## Y
#define __REVEAL_TKN2__(X,Y) __REVEAL_TKN1__(X, Y)
#define __REFX__(A) auto & A = __REVEAL_TKN2__(__reveal_class__,__LINE__).A();
#define REVEAL(X, ARGS...) \
auto & __REVEAL_TKN2__(__reveal_class__, __LINE__) = X; \
MACRO_TO_ARGS(__REFX__, ARGS)
#define GLAMBDA(FUNC) [this](auto&&... arg) { return FUNC(std::forward<decltype(arg)>(arg)...); }
#define DBG_WIDTH 67 // max print len upon parser's dbg
#define KEY first // semantic for map's pair
#define VALUE second // instead of first/second
#define OPN first // range semantic
#define CLS second // instead of first/second
typedef vector<string> v_string;
class CommonResource {
// the class facilitates -J option and global namespace:
// caters user opt (which is copied to each Jtc instance), input string
// and json for jsonization of all processed/walked jsons
typedef ptrdiff_t signed_size_t;
struct Multiwalk {
// a trivial class to pair up the walk multiplier and the first index to display:
// only each factor'th walk will be displayed after offset's
Multiwalk(void) = default;
Multiwalk(size_t x1, signed_size_t x2): // emplacement
factor{x1}, offset{x2} { }
size_t factor; // display every factor'th walk
signed_size_t offset; // starting from offset (0 based)
};
public:
int rc(void) const { return rc_; }
void rc(int rc) { rc_ = rc; }
Getopt & opt(void) { return opt_; }; // access to Getopt class instance
size_t opt_e_found(void) { return opt_e_found_; } // used for recompile once
char opt_ui(void) { return opt_ui_; }; // -i or -u for recompile
Json::map_jn & global_ns(void) { return gns_; }; // access to global namespaces
Streamstr & iss(void) { return iss_; }; // access to stream class
Json & global_json(void) { return gjns_; }; // access to global JSON (-J)
auto & wm(void) { return wm_; };
void parse_opt(int argc, char *argv[]);
Streamstr & read_inputs(void);
void jsonize(Json jout);
private:
bool is_recompile_required_(int argc, char *argv[]);
void recompile_args_(v_string &args, v_string &new_args);
void parse_rebuilt_(v_string & new_args);
void convert_xyw_(void);
bool is_x_factor_(const char *str, signed_size_t * recursive = nullptr);
Json gjns_{ ARY{} }; // global json (facilitates -J)
Json::map_jn gns_; // global namespaces
Streamstr iss_; // input Streamstr
Getopt opt_; // user options
vector<Multiwalk> wm_; // walk factor: facilitate -xN/M
int rc_{RC_OK};
size_t opt_e_found_{0}; // used for recompile once -e found
char opt_ui_{NULL_CHR}; // either -i or -u for recompile
bool aimp_{false}; // '-a' imposed?
bool jimp_{false}; // '-j' imposed?
public:
DEBUGGABLE(iss_, gjns_)
};
class Jtc {
struct BoundJit {
// a trivial class to pair up destination walks (-w) with source walks (-i/u/c)
// and respective name-spaces
BoundJit(void) = default;
BoundJit(Json::iterator d, Json::iterator s): // for emplacement
dst{d}, src{s}, ns{src.json().ns()}, lbl{"\n"} {}
bool update_lbl(void)
{ return lbl.empty() or lbl.front() != '\n'; }
void reset_lbl(void) { lbl = "\n"; }
Json::iterator dst; // -w walks (iterators) go here
Json::iterator src; // -i/u/c walks (iterators) go here
Json::map_jn ns; // NS from respective -i/u/c iters.
string lbl{"\n"}; // facilitates -u for label update
// init'ed with "\n" - invalid lbl
};
struct Grouping {
// facilitates a pair of group/counter values, used in jsonized_output_ary_
Grouping(void) = default;
Grouping(size_t x1, size_t x2): // emplacement
group{x1}, counter{x2} { }
bool operator>(const Grouping &rhs)
{ return group > rhs.group and counter > rhs.counter; }
size_t group{0};
size_t counter{0};
};
typedef ptrdiff_t signed_size_t;
typedef vector<Json::iterator> vec_jit;
typedef vector<BoundJit> vec_bjit;
typedef deque<Json::iterator> deq_jit;
typedef void (Jtc::*mptr)(Json::iterator &wi, Grouping grp);
typedef map<size_t, Json> map_json;
typedef map<size_t, Json::iterator> map_jit;
typedef vector<size_t> vec_wlk;
typedef map<const Json::iterator*, Json::map_jn> map_ns;
#define MERGEOBJ \
preserve, /* clashing labels of merged object not overwritten */ \
overwrite /* clashing labels of merged objects overwritten */
ENUM(MergeObj, MERGEOBJ)
#define JITSRC /* type of source for iterations in -i/u/c operation*/\
src_input, /* source is input JSON: `jinp_`: -u<walk> walks input JSON here*/\
src_jexec, /* source is in `jexc_`: 1) -eu ... \; 2) -u<STATIC> -u<walk> */\
src_optarg /* source is in `jsrc_`: when -u<STATIC>, i.e., w/o walks is used*/
ENUMSTR(Jitsrc, JITSRC)
public:
Jtc(void) = delete;
Jtc(CommonResource & cr): cr_{cr}, opt_{cr.opt()} {
ecli_ = opt_[CHR(OPT_EXE)].hits() > 0; // flag used by -i/-u options
merge_ = opt_[CHR(OPT_MDF)].hits() > 0;// flag used by -i/-u options
is_tmp_per_walk_ = opt_[CHR(OPT_TMP)].hits() > 1 and
opt_[CHR(OPT_SEQ)].hits() < 2 and
opt_[CHR(OPT_TMP)].hits() == opt_[CHR(OPT_WLK)].hits();
// ready jinp_
jinp_.tab(opt_[CHR(OPT_IND)].hits() > 0 or not opt_[CHR(OPT_RAW)]?
abs(opt_[CHR(OPT_IND)]): 1)
.raw(opt_[CHR(OPT_RAW)])
.quote_solidus(opt_[CHR(OPT_QUT)].hits() % 2 == 1);
jinp_.callback(GLAMBDA(shell_callback_)).engage_callbacks();
jexc_.type(Jnode::Neither);
}
// expose private objects
auto & opt(void) { return opt_; }
auto & json(void) { return jinp_; }
auto & jout(void) { return jout_; }
// user methods
void parsejson(Streamstr::const_iterator & jsp);
void write_json(Json & jsn, bool jsnize = true);
void demux_opt(void);
void compare_jsons(void);
void compare_bingings(void);
void upsert_json(char op);
void collect_itr_bindings(Json::iterator &it, Grouping = {0,0});
void update_by_iterator(Json::iterator &it, Grouping = {0,0});
bool advance_to_next_src(signed_size_t i = -1);
void apply_src_walks(char op);
void purge_json(void);
void swap_json(void);
void walk_json(void);
void output_by_iterator(Json::iterator &wi, Grouping = {0, 0});
void output_by_iterator(Json::iterator &&wi, Grouping grp = {0, 0})
{ output_by_iterator(wi, grp); }
private:
void location_(Streamstr::const_iterator & start);
void ready_params_(char option);
void maybe_update_lbl_(void);
void compare_jsons_(const Jnode &, set<const Jnode*> &,
const Jnode &, set<const Jnode*> &);
void merge_jsons_(Json::iterator &dst, Json::iterator src, string *lbl);
void merge_into_object_(Jnode &dst, const Jnode &src, MergeObj mode);
void merge_into_array_(Jnode &dst, const Jnode &src, MergeObj mode);
void update_jsons_(Json::iterator &dst, Json::iterator src, string *lbl);
bool processed_by_cli_(Json::iterator &it);
bool execute_cli_(Json &update, Json::iterator &jit, const Json::map_jn &ns);
string reconcile_ui_(Json::iterator &jit, const Json::map_jn &ns);
void crop_out_(void);
bool remove_others_(set<const Jnode*> &ws, Jnode &jn);
vec_jit collect_walks_(const string &walk_path);
void walk_interleaved_(void);
void update_wlk_history_(Json::iterator &);
void process_walk_iterators_(deque<deq_jit> &walk_iterators);
size_t build_front_grid_(vector<vector<signed_size_t>> &, const deque<deq_jit> &);
void process_offsets_(deque<deq_jit> &, vector<vector<signed_size_t>> &,
size_t, vector<size_t> &);
void console_output_(Json::iterator &, Json &jref, Grouping unused);
void jsonized_output_(Json::iterator &, Json &jref, Grouping unused);
void jsonized_output_obj_(Json::iterator &, Json &jref, Grouping);
void jsonized_output_ary_(Json::iterator &, Json &jref, Grouping);
void merge_ns_(Json::map_jn &to, const Json::map_jn &from)
{ for(const auto & ns: from) to[ns.KEY] = ns.VALUE; }
bool shell_callback_(const std::string &lxm, Json::iterator jit) {
// reminder:
//typedef std::function<bool(const std::string &, const Jnode &)> uws_callback;
Json tmp;
DBG().increment(+2, tmp, -2);
tmp = Json::interpolate(lxm, jit, jit.json().ns(), Json::dont_parse);
sh_.system( tmp.str() );
return sh_.rc() == 0;
}
// private member types:
CommonResource & cr_;
Getopt opt_;
Json jinp_; // input JSON for jtc
Json jout_; // json output (-j, -jj)
Json jexc_; // json for -e (or mixed args use)
// jexc_ is used to facilitates sources of -e[iuc] arguments and -[iuc] for mixed arguments
// usage, e.g: -i<json> -i<wlk1> -i<wlk2> ... (in both use-cases only 1 json source allowed)
map_json jsrc_; // static JSONs -iuc (no mixed arg)
// jsrc_ facilitates cases when -[iuc] contain json only (no walks), multiple allowed
vec_bjit psrc_; // binding dst walks with src's
vec_wlk wsrc_; // enlist all walks in -[iuc]<walk>
Json::iterator jits_; // current source iterator
Jitsrc jitt_{Jitsrc::src_input}; // source of jits_
size_t wcur_{0}; // current walk in wsrc_
map_ns wns_; // namespaces for walked (-w) paths
Jnode hwlk_{ARY{STR{""}}}; // walks history init value
set<string> c2a_; // used in jsonized_output_obj_()
map<size_t, string> tpw_; // tmp per walk, output_by_iterator
bool is_tmp_per_walk_; // templates pertain to walks
bool is_tmp_per_upst_; // templates pertain to walks, -i/u
bool is_multi_walk_{false}; // multiple -w or single iterable?
bool convert_req_{false}; // used in output_by_iterator
bool ecli_{false}; // -e status for insert/update
bool merge_{false}; // -m status for insert/update
bool lbl_update_{false}; // label update operation detected
Grouping last_; // used in output_by_iterator
size_t key_{0}; // general purpose counter
size_t upst_key_{0}; // template idx for -iu round-robin
size_t wcnt_{0}; // counts number of walks
mptr subscriber_; // method ptr for output processor
Shell sh_;
public:
DEBUGGABLE(jinp_, jout_, jexc_, sh_)
};
#undef MERGEOBJ
STRINGIFY(Jtc::Jitsrc, JITSRC)
#undef JITSRC
// list of standalone callse
string sh_quote_str(const string &src);
int main(int argc, char *argv[]) {
CommonResource cr;
REVEAL(cr, opt)
opt.prolog("\nJSON test console\nVersion " VERSION \
", developed by " CREATOR " (" EMAIL ")\n");
opt[CHR(OPT_ALL)].desc("process all inputs (by default only the first JSON processed); -"
STR(OPT_FRC) " is ignored");
opt[CHR(OPT_CMN)].desc("a common part of a path, prepended to every followed -" STR(OPT_PRT)
" option").name("common_wp");
opt[CHR(OPT_CMP)].desc("compare JSONs: display delta between given JSONs").name("f|j|w");
opt[CHR(OPT_DBG)].desc("turn on debugs (multiple calls increase verbosity)");
opt[CHR(OPT_EXE)].desc("make option parameters for -" STR(OPT_INS) ", -" STR(OPT_UPD)
" undergo a shell evaluation; see -" STR(OPT_GDE) " for more info");
opt[CHR(OPT_FRC)].desc("apply changes into the file (instead of printing resulting JSON"
" to stdout)");
opt[CHR(OPT_GDE)].desc("mini USER-GUIDE: explain walk path syntax, usage notes, some examples");
opt[CHR(OPT_IND)].desc("indent for pretty printing").bind("3").name("indent");
opt[CHR(OPT_INS)].desc("insert either a static JSON, or pointed by a walk-path; see with -"
STR(OPT_GDE) " for more").name("f|j|w");
opt[CHR(OPT_JAL)].desc("wrap all processed JSONs into an array (option -"
STR(OPT_ALL) " assumed, buffered read imposed)");
opt[CHR(OPT_JSN)].desc("wrap walked JSON elements into a JSON array (-" STR(OPT_JSN) STR(OPT_JSN)
" wrap into a JSON object)");
opt[CHR(OPT_LBL)].desc("print labels (if present) for walked JSON; together with -"
STR(OPT_JSN) " wrap into objects");
opt[CHR(OPT_MDF)].desc("modifier: toggle merging for options -" STR(OPT_INS) ", -" STR(OPT_UPD)
"; see with -" STR(OPT_GDE) " for more info");
opt[CHR(OPT_PRG)].desc("purge all walked JSON elements (-" STR(OPT_PRG) STR(OPT_PRG)
": purge all elements except walked)");
opt[CHR(OPT_PRT)].desc("an individual partial path, prepended by preceding -" STR(OPT_CMN)
" option").name("partial_wp");
opt[CHR(OPT_QUT)].desc("enforce strict quoted solidus parsing "
"(-qq: unquote isolated JSON strings)");
opt[CHR(OPT_RAW)].desc("print JSON in a raw (compact, one-line) format"
" (-rr stringify resulting JSON)");
opt[CHR(OPT_SEQ)].desc("do not print/process walks interleaved (i.e. print/process all walks "
"sequentially)");
opt[CHR(OPT_SWP)].desc("swap around JSON elements pointed by a pair of walks");
opt[CHR(OPT_SZE)].desc("print size (number of nodes in JSON) at the end of output (-"
STR(OPT_SZE) STR(OPT_SZE) " prints size only)");
opt[CHR(OPT_TMP)].desc("a template to interpolate and apply upon -" STR(OPT_INS) ", -" STR(OPT_UPD)
" and standalone -" STR(OPT_WLK) " operations").name("template");
opt[CHR(OPT_UPD)].desc("update static JSON, or pointed by a walk-path; see with -"
STR(OPT_GDE) " for more").name("f|j|w");
opt[CHR(OPT_WLK)].desc("a standalone walk path (multiple may be given); see with -"
STR(OPT_GDE) " for more").name("walkpath");
opt[0].desc("file to read json from").name("json_file").bind("<stdin>");
opt.epilog(R"(
this tool provides ability to:
- parse, validate and display JSON (in a compact and pretty formats)
- walk about input JSON using various subscript and search criteria (see with -)" STR(OPT_GDE)
R"( for more)
- manipulate JSON via purge/insert/copy/merge/update/replace/move/swap/interpolate operations
- compare JSONs (print diffs)
by default, input JSONs processed via buffered read (first read, then parse); streamed read (i.e.
parse JSON immediately as data arrives) is engaged when option -)"
STR(OPT_ALL) R"( given and <stdin> input selected
(though -)" STR(OPT_JAL) R"( overrides the streamed read and reverts to buffered)
for walk-path explanation, usage notes and examples run with -)" STR(OPT_GDE) R"(
for a complete user guide visit https://github.com/ldn-softdev/jtc/blob/master/User%20Guide.md
)");
opt.variadic();
// parse options
cr.parse_opt(argc, argv);
DEBUGGABLE()
DBG().use_ostream(cerr)
.level(opt[CHR(OPT_DBG)]);
// read json (ready stream buffer, or read file/cin)
Streamstr::const_iterator jsp = cr.read_inputs().begin(); // global parse pointer
Streamstr::const_iterator pse; // parsed json end iterator
// execute as per read options
try {
do {
Jtc jtc(cr);
jtc.parsejson(jsp);
jtc.demux_opt();
pse = jsp; // to verify exception
} while(jsp != cr.iss().end() and opt[CHR(OPT_ALL)].hits() > 0);
}
catch(Jnode::stdException & e) {
DBG(1) DOUT() << "exception raised by: " << e.where() << endl;
cerr << opt.prog_name() << " jnode exception: " << e.what() << endl;
cr.rc(e.code() + OFF_JSON);
}
catch(Json::stdException & e) {
DBG(1) DOUT() << "exception raised by: " << e.where() << endl; // might need suppression
if(e.code() == Jnode::unexpected_end_of_string and jsp.is_streamed()) {
for(;pse.offset() < jsp.offset(); ++pse) if(*pse > ' ') break;
if(pse.offset() == jsp.offset()) {
DBG(1) DOUT() << "suppressing exception: blank trails past last parsed json" << endl;
exit(cr.rc()); // provide normal exit then
}
}
cerr << opt.prog_name() << " json exception: " << e.what() << endl;
cr.rc(e.code() + OFF_JSON);
}
catch(std::regex_error & e) {
cerr << "regexp exception: " << e.what() << endl;
cr.rc(e.code() + OFF_REGEX);
}
if(cr.global_json().empty()) exit(cr.rc());
Jtc jtc(cr); // global (-J) resulting json
for(const char *o = STR(OPT_JAL) STR(OPT_JSN) STR(OPT_QUT) STR(OPT_RAW); *o != NULL_CHR; ++o)
jtc.opt()[*o].reset(); // above options to be ignored
jtc.write_json(cr.global_json());
exit(cr.rc());
}
//
// CR PUBLIC methods definitions
//
void CommonResource::parse_opt(int argc, char *argv[]) {
// 1. parse options, if option -e detected, rebuild -u/i's arguments and parse with rebuilt args
// 2. reinstate -w options from -x/-y
// 3. process options dependencies
v_string args{argv, argv + argc};
if(is_recompile_required_(argc, argv)) { // re-compiling required?
v_string new_args; // newly rebuilt args go here
recompile_args_(args, new_args); // rebuild -u/i's arguments as one
parse_rebuilt_(new_args);
}
else // parse normally
try { opt_.parse(argc, argv); }
catch(Getopt::stdException &e)
{ opt_.usage(); exit(e.code() + OFF_GETOPT); }
convert_xyw_(); // -w = -x + -y...
if(opt_[CHR(OPT_GDE)].hits() > 0) exit(print_guide()); // -g, print guide
if(opt_[CHR(OPT_JAL)].hits() > 0) { // -J:
if(opt_[CHR(OPT_ALL)].hits() == 0) aimp_ = true; // indicate that -a was imposed
opt_[CHR(OPT_ALL)].hit(); // ensure -a hit unconditionally
if(opt_[CHR(OPT_JSN)].hits() == 0) // if no -j given
{ opt_[CHR(OPT_JSN)].hit(); jimp_ = true; } // impose one and remember
}
if(opt_[CHR(OPT_ALL)].hits() > 0 and opt_[CHR(OPT_FRC)].hits() > 0) {
cerr << "notice: ignoring option -" STR(OPT_FRC) " b/c of multi-input processing" << endl;
opt_[CHR(OPT_FRC)].reset(); // -a (-J), ensure -f ignored
}
}
Streamstr & CommonResource::read_inputs(void) {
// initialize `iss` with correct read mode: streamed/cin/buffered
bool read_from_cin{opt_[0].hits() == 0 or opt_[CHR(OPT_RDT)].hits() > 0}; // no arg, or forced '-'
DBG(0)
DOUT() << "reading json from " << (read_from_cin? "<stdin>": "file-arguments:") << endl;
for(int arg = 1; arg <= opt_.arguments(); ++arg) {
iss_.source_file(opt_[0].str(arg)); // will set read mode to file
DBG(0) DOUT() << "source file: " << opt_[0].str(arg) << endl;
}
if(opt_.arguments() > 1) opt_[CHR(OPT_ALL)].hit(); // impose -a if multiple files
if(read_from_cin) { // either stream, or buffered
if(opt_[CHR(OPT_ALL)].hits() > 0 and opt_[CHR(OPT_JAL)].hits() == 0) // -a, no -J, stream read
iss_.reset(Streamstr::streamed_cin);
else { // cin buffered
if(opt_[CHR(OPT_JAL)].hits() > 0 and aimp_ == false) // -J given and -a wasn't imposed
cerr << "notice: option -" STR(OPT_JAL) " cancels streaming input" << endl;
iss_.reset(Streamstr::buffered_cin);
}
}
return iss_;
}
void CommonResource::jsonize(Json jout) {
// put all walked json results into a global json
if(jimp_ and jout.is_iterable()) { // -j was imposed
for(auto &jn: jout) gjns_.push_back(move(jn)); // therefore push one by one
return;
}
// no -j were imposed:
gjns_.push_back(move(jout)); // move jout as it is
}
//
// CR PRIVATE methods definitions
//
bool CommonResource::is_recompile_required_(int argc, char *argv[]) {
// check if option -e is present in the arguments (if so, indicate re-parsing is required)
opt_.suppress_opterr(true);
try { opt_.parse(argc, argv); }
catch(Getopt::stdException &e) { }
bool rr = opt_[CHR(OPT_EXE)];
opt_.reset().suppress_opterr(false);
return rr;
}
void CommonResource::recompile_args_(v_string & args, v_string &new_args) {
// recompile argv minding -u/i's arguments, put re-parsed args into new_args
bool semicolon_found = false;
for(auto &arg: args) { // go through all args
if(semicolon_found) // -i/u already found and processed
{ new_args.push_back(arg); continue; } // push arg w/o any processing
if(opt_e_found_ > 0) { // facing -i/u; ';' not found yet,
if(arg.back() == ';') // ';' found
{ semicolon_found = true; arg.pop_back(); } // trim trailing ';'
if(not arg.empty()) {
if(opt_e_found_++ == 1) new_args.back() += arg; // first argument
else new_args.push_back(string("-") + opt_ui_ + arg); // any subsequent arg
}
continue;
}
if(arg.front() == '-') // opt, see if opt -i/u is present
for(const char &chr: arg) {
if(&chr == &arg[0]) continue; // skip first char '-'
if(not opt_.defined(chr)) break; // undefined option, process arg
if(chr == CHR(OPT_UPD) or chr == CHR(OPT_INS)) { // opt -i/u found, indicate&record
opt_e_found_ = 1;
opt_ui_ = chr;
if(arg.back() == ';') // ';' found
{ semicolon_found = true; arg.pop_back(); ++opt_e_found_; }
else
if(&arg.back() != &chr) // arg is attached to the option
++opt_e_found_;
break;
}
}
new_args.push_back(arg);
}
if(opt_e_found_ > 0 and not semicolon_found) {
cerr << "fail: don't see parameter termination of '-" << opt_ui_ << "' - `\\;'" << endl;
exit(RC_SC_MISS);
}
}
void CommonResource::parse_rebuilt_(v_string & new_args) {
// parse rebuilt arguments (after recompilation for -e option)
char *nargv[new_args.size()]; // here, build a new argv
for(size_t i = 0; i < new_args.size(); ++i) {
nargv[i] = new char[new_args[i].size()+1];
stpcpy(nargv[i], new_args[i].c_str());
}
try { opt_.reset().parse(new_args.size(), nargv); } // re-parse newly rebuilt args
catch(Getopt::stdException & e)
{ opt_.usage(); exit(e.code() + OFF_GETOPT); }
for(size_t i = 0; i < new_args.size(); ++i) // clean up nargv now
delete [] nargv[i];
}
void CommonResource::convert_xyw_(void) {
// convert -x, -y options into -w:
// standalone (isolated) -x and -y also converted to -w
string last_x, last_y;
v_string new_w; // record new -w options here
for(auto &option: opt_.ordinal()) { // go by options order
if(option.id() == CHR(OPT_CMN)) { // option -x, process it
if(is_x_factor_(option.c_str())) continue;
if(not last_x.empty() and last_y.empty()) // it's like: -x... -x...
new_w.push_back(move(last_x)); // standalone -x is converted to -w
last_x = move(option.str());
last_y.clear();
continue;
}
if(option.id() == CHR(OPT_PRT)) { // option -y
last_y = move(option.str());
new_w.push_back(last_x + last_y);
}
}
if(not last_x.empty() and last_y.empty()) // option -x... is given alone
new_w.push_back(move(last_x));
for(auto &opt_w: new_w) // move all new '-w' to opt
opt_[CHR(OPT_WLK)] = opt_w;
}
bool CommonResource::is_x_factor_(const char * x_str, signed_size_t * recursive) {
// process -xN/N option here: notation -x/N is allowed, all other forms return `false`
signed_size_t x1, x2;
char *endptr;
x1 = strtol(x_str, &endptr, 10);
if(recursive) // resolving a second value now
{ if(*endptr == '\0')
{ *recursive = x1; return true; } return false; }
x2 = abs(x1) - 1;
if(*endptr == '/' and endptr[1] != '\0')
{ if(not is_x_factor_(endptr + 1, &x2)) return false; } // resolve second value here
else
if(*endptr != '\0') return false;
wm_.emplace_back(abs(x1), x2);
return true;
}
//
// Jtc PUBLIC methods definitions
//
void Jtc::parsejson(Streamstr::const_iterator & jsp) {
// parse read json text via Streamstr iterator
Streamstr::const_iterator jbegin = jsp; // for debugs / location_ only
try
{ jinp_.parse(jsp, cr_.iss().is_streamed()? Json::relaxed_no_trail: Json::relaxed_trailing); }
catch(Json::stdException & e) {
if(e.code() >= Jnode::start_of_json_parsing_exceptions and
e.code() <= Jnode::end_of_json_parsing_exceptions)
DBG(0) location_(jbegin);
throw e;
}
if(opt_[CHR(OPT_WLK)].hits() == 0 and opt_[CHR(OPT_TMP)].hits() >= 1) // no -w, but -T is given
opt_[CHR(OPT_WLK)] = "[^0]"; // provide default walk off root
}
void Jtc::write_json(Json & json, bool jsonize) {
// write whole json to output (demultiplexing file and stdout), featuring:
// inquoting/unquoting json string, putting json into array (-j), printing size to stdout
if(opt_[CHR(OPT_SZE)].hits() > 1) // -zz
{ cout << json.size() << endl; return; }
bool write_to_file{opt_[0].hits() > 0 and opt_[CHR(OPT_FRC)].hits() > 0}; // args and -f given
bool unquote{opt_[CHR(OPT_QUT)].hits() >= 2}; // -qq given, unquote
bool inquote{opt_[CHR(OPT_RAW)].hits() >= 2}; // -rr given, inquote
if(jsonize and opt_[CHR(OPT_JSN)].hits() == 1) // -j given, force jsonizing
json = ARY{ move(json) };
if(not unquote and inquote)
json.root() = json.inquote_str(json.to_string(Jnode::Raw));
DBG(0)
DOUT() << "outputting json to " << (write_to_file?
cr_.iss().filename():
opt_[CHR(OPT_JSN)]? "<json>": "<stdout>") << endl;
if(opt_[CHR(OPT_JAL)].hits() > 0) // -J, jsonize to global
{ cr_.jsonize(move(json)); return; }
ofstream fout{write_to_file? cr_.iss().filename().c_str(): nullptr};
ostream & xout = write_to_file? fout: cout; // demux cout/file outputs
if(unquote and json.is_string())
{ if(not json.str().empty()) xout << json.unquote_str(json.str()) << endl; }
else xout << json << endl;
if(opt_[CHR(OPT_SZE)])
cout << SIZE_PFX << json.size() << endl;
}
void Jtc::demux_opt(void) {
// demultiplex functional options, execute once in order: -[ciuspw]
for(char opt: STR(OPT_CMP)STR(OPT_INS)STR(OPT_UPD)STR(OPT_SWP)STR(OPT_PRG)STR(OPT_WLK)) {
if(opt == NULL_CHR or opt_[opt].hits() == 0) continue;
DBG(1) DOUT() << "option: '-" << opt << "', hits: " << opt_[opt].hits() << endl;
switch(opt) {
case CHR(OPT_CMP): return compare_jsons(); // will print result itself
case CHR(OPT_WLK): return walk_json(); // will print result itself
case CHR(OPT_INS):
case CHR(OPT_UPD): upsert_json(opt); break;
case CHR(OPT_PRG): purge_json(); break;
case CHR(OPT_SWP): swap_json(); break;
default: continue;
}
break;
}
// this write will be used only by -[iups]
write_json(jinp_);
}
void Jtc::compare_jsons() {
// plug-in compare_by_iterator and let process walks (print results)
cr_.wm().clear();
if(opt_[CHR(OPT_WLK)].hits() == 0) // no -w?
opt_[CHR(OPT_WLK)] = "[^0]"; // then walk from root
ready_params_(CHR(OPT_CMP));
subscriber_ = &Jtc::collect_itr_bindings;
walk_interleaved_();
compare_bingings();
if(opt_[CHR(OPT_JSN)].hits() > 0)
write_json(jout_);
}
void Jtc::compare_bingings(void) {
// compare two JSONs: one pointed by iterator (it), another taken from params
static string lbl[2] { CMP_BASE, CMP_COMP };
for(auto &pair: psrc_) {
vector<Json> jv{2}; // 2 JSONs to compare
jv.front()[lbl[0]] = *pair.dst; // 1st comes form walk_interleaved
jv.back()[lbl[1]] = *pair.src; // 2nd comes from -c <walk-path>
DBG().increment(+2, jv.front(), jv.back(), -2);
vector<set<const Jnode*>> node_set{2}; // preserved different node ptrs
compare_jsons_(jv.front()[lbl[0]], node_set.front(),
jv.back() [lbl[1]], node_set.back());
for(size_t i = 0; i < node_set.size(); ++i)
DBG(1) DOUT() << "found diffs (" << lbl[i]
<< ", instance " << key_++ << "): " << node_set[i].size() << endl;
for(size_t i = 0; i < jv.size(); ++i)
if(jv[i][ lbl[i] ].is_iterable()) // if root is iterable
remove_others_(node_set[i], jv[i][ lbl[i] ]); // remove then all matching nodes
else // root is atomic
if(node_set[i].empty()) jv[i][ lbl[i] ] = OBJ{}; // set is as an empty set {}
for(auto &json: jv) { // output compared jsons
auto jit = json.walk("[0]");
if(not jit->empty()) cr_.rc(RC_CMP_NEQ);
output_by_iterator(jit);
}
if(jv.front().front().type() != jv.back().front().type())
cr_.rc(RC_CMP_NEQ);
}
}
// insert handles 4 parameter types (the same applies to updates):
// 1. static insert ( -i <json> )
// 2. insert from file ( -i <file> ) - file must contain a valid JSON
// 3. insert from cli ( -e -i <cli> \; ) - cli must return a valid JSON
// 4. insert by walk-path (-i <src walk-path>) effectively performing copy operation
// Destination walks (-w <dst walk-path>) define mode of insertion:
// - if insertion point is single (only one walk given and it's non-iterable), then
// all source jsons/walks will be attempted to be inserted into a single location
// - if multiple insertions points given (ether multiple -w, or single -w with
// an iterable walk-path), then sources are inserted in a circular manner
//
// insert matrix (src -> dst):
// insert w/o -m
// to \ from | [3,4] | {"a":3,"c":4} | "a":3,"c":4 | "c"
//---------------+---------------+---------------------+-----------------------+---------------+
// [1,2] | [1,2,[3,4] | [1,2,{"a":3,"c":4}] | [1,2,{"a":3},{"c":4}] | [1,2,"c"]
// {"a":1,"b":2} | {"a":1,"b":2} | {"a":1,"b":2,"c":4} | {"a":1,"b":2,"c":4} | {"a":1,"b":2}
// "a":1 | "a":1 | "a":1 | "a":1 | "a":1
//
// insert with -m
// to \ from | [3,4] | {"a":3,"c":4} | "a":3,"c":4 | "c"
//-------------+---------------------+-----------------------+-----------------------+------------+
// [1,2] | [1,2,3,4] | [1,2,3,4] | [1,2,3,4] | [1,2,"c"]
//{"a":1,"b":2}|{"a":[1,3],"b":[2,4]}|{"a":[1,3],"b":2,"c":4}|{"a":[1,3],"b":2,"c":4}|{"a":1,"b":2}
// "a":1 | "a":[1,3,4] | "a":[1,3,4] | "a":[1,3,4] | "a":[1,"c"]
//
// update matrix (src -> dst):
// update w/o -m
// to \ from | [3,4] | {"a":3,"c":4} | "a":3 | "c"
//---------------+-----------+-------------------+-------+--------+
// [1,2] | [3,4] | {"a":3,"c":4} | 3 | "c"
// {"a":1,"b":2} | [3,4] | {"a":3,"c":4} | 3 | "c"
// "a":1 | "a":[3,4] | "a":{"a":3,"c":4} | "a":3 | "a":"c"
//
// update with -m
// to \ from | [3,4] | {"a":3,"c":4} | "a":3 | "c"
//---------------+---------------+---------------------+---------------+----------------+
// [1,2] | [3,4] | [3,4] | [3,2] | ["c",2]
// {"a":1,"b":2} | {"a":3,"b":4} | {"a":3,"b":2,"c":4} | {"a":3,"b":2} | {"a":"c","b":2}
// "a":1 | "a":[3,4] | "a":{"a":3,"c":4} | "a":{"a":3} | "a":"c"
//
// Basic principles:
// o w/o '-m' src param (-i src) is considered as a whole, with '-m' as an mergeable iterable
// o insert operation never rewrites the dst data (-w dst), while '-m' may extend it
// o update operation rewrites the dst data (-w dst)
void Jtc::upsert_json(char op) {
// plug-in <insert/update>_by_iterator and let process walks (options -i/-u)
static mptr upsert_meth[2] = {&Jtc::collect_itr_bindings, &Jtc::update_by_iterator};
if(opt_[CHR(OPT_WLK)].hits() == 0) // no -w?
opt_[CHR(OPT_WLK)] = "[^0]"; // then walk from root
ready_params_(op);
is_tmp_per_upst_ = opt()[CHR(OPT_TMP)].hits() > 1 and opt()[CHR(OPT_SEQ)].hits() < 2 and
opt()[CHR(OPT_TMP)].hits() == wsrc_.size();
subscriber_ = upsert_meth[op == CHR(OPT_UPD)];
walk_interleaved_();
if(ecli_ == false or not wsrc_.empty()) // no -e, or -e with -i/u
apply_src_walks(op);
maybe_update_lbl_(); // will update lbl if any pending
if(opt_[CHR(OPT_PRG)].hits() > 0) // only work when walk-path is src
purge_json();
}
void Jtc::collect_itr_bindings(Json::iterator &it, Grouping unused) {
// facilitate insert each/all -[iu] processed jsons:
// per each walked destination (-w, facilitated by &it), collect each respective source(s) (jits_)
// Grouping arg is unused here, but is required by subscriber_'s definition
if(processed_by_cli_(it)) return; // -ei ... \; w/o trailing -i<wlk>
merge_ns_(cr_.global_ns(), wns_[&it]); // syncup global ns
if(jits_.walk_size() > 0) // walk_size > 0 means init'ed jits
merge_ns_(jits_.json().ns(), cr_.global_ns()); // syncup current json source
while(advance_to_next_src()) {
DBG(1) DOUT() << "optarg idx [" << wcur_ << "] out of "
<< (jitt_ == src_optarg? jsrc_.size(): wsrc_.size()) << " ("
<< (jitt_ == src_optarg? "static":opt_.ordinal(wsrc_[wcur_]).c_str())
<< ")" << endl;
psrc_.emplace_back(it, jits_); // collect iterator
if(is_multi_walk_) break;
}
}
void Jtc::update_by_iterator(Json::iterator &it, Grouping unused) {
// update each/all -u processed jsons
// Grouping arg is unused here, but is required by subscriber_'s definition
if(lbl_update_ == false) // not faced label update yet
lbl_update_ = not it.walks().empty() and it.walks().back().jsearch == Json::key_of_json;
else // lbl update occurred, then
if(not it.is_valid()) // verify sanity of dst walks
{ cerr << "error: destination walk became invalid, skipping update" << endl; return; }
collect_itr_bindings(it);
}
bool Jtc::advance_to_next_src(signed_size_t i) {
// iterate current (valid) walk, or begin iterating next one (if available), may come with:
// i = -1 - first non-recursive call
// i != wcur_ - must process next src,
// i == 0/wcur_ - all src-itr walked (loop detect for single/multi walks, recursive)
auto idx = [&i, this](void){ return wcur_ = i >= 0? i: wcur_; }; // index select
DBG(4) DOUT() << "walk src: " << i << ", walk/json: '"
<< (jitt_ == src_optarg?
jsrc_[idx()].to_string(Jnode::Raw, 1):
opt_.ordinal(wsrc_[idx()]).str()) << "'" << endl;
size_t jc_size = jitt_ == src_optarg? jsrc_.size(): wsrc_.size(); // json source container size
if(jits_.is_valid()) { // if true, walk_size must be > 0
if((++jits_).is_valid()) // end() not reached yet
{ wcur_ = idx(); return true; }
return advance_to_next_src(idx() + 1 >= jc_size? 0: idx() + 1); // else use next src
}
// jits_ could be invalid due to: 1. init call (walk_size == 0),
// 2. change source (reached end, i >= 0, walk_size here is always > 0)
if(not is_multi_walk_ and i == 0) // single_walk, change source
{ jits_ = Json::iterator{}; return false; } // and loop => end of operations
auto & srcj = jitt_ == src_input? jinp_: jitt_ == src_jexec? jexc_: jsrc_[idx()];
if(jits_.walk_size() == 0) // merge upon jits_ initialization
merge_ns_(srcj.ns(), cr_.global_ns()); // merge global ns to -u/i's ns
switch(jitt_) {
case src_input: jits_ = srcj.walk(opt_.ordinal(wsrc_[idx()]).str()); break;
case src_jexec: jits_ = srcj.walk(opt_.ordinal(wsrc_[idx()]).str()); break;
case src_optarg: jits_ = srcj.walk(); break;
}
if(jits_.is_valid()) // jits_ resolved
{ wcur_ = idx(); return true; }
if(i == static_cast<signed_size_t>(wcur_)) // multiwalk: no more valid itr/src
{ jits_ = Json::iterator{}; return false; } // fail operation
return advance_to_next_src(idx() + 1 >= jc_size? 0: idx() + 1); // return next src itr
}
void Jtc::apply_src_walks(char op) {
// apply all src walks collected in <insert/update>_by_iterator
typedef void (Jtc::*ups_ptr)(Json::iterator &dst, Json::iterator src, string *lbl);
static ups_ptr upsert[2] = {&Jtc::merge_jsons_, &Jtc::update_jsons_};
vec_bjit vsrc(psrc_.size()); // copy of valid iterators
for(auto &pair: psrc_) {
if(not pair.dst.is_valid())
{ cerr << "error: dst walk " << key_ << " invalided by prior operations" << endl; continue; }
if(jitt_ == src_input and not pair.src.is_valid())
{ cerr << "error: src walk " << key_ << " invalided by prior operations" << endl; continue; }
if(not ecli_) {
Json tmp;
tmp.type(Jnode::Neither);
DBG().increment(+2, tmp, -2);
if(opt_[CHR(OPT_TMP)].hits() == 0) // no -T given
(this->*upsert[op == CHR(OPT_UPD)])(pair.dst, pair.src, &pair.lbl); // upsert w/o interpolation
else { // -T(s) given
// if number of templates matches number of -[iu]<walk>s, then apply template per relevant walk
// otherwise apply templates round-robin
if(is_tmp_per_upst_) { // template per walk:
if(tpw_.count(pair.src.walk_id()) == 0) { // relate interleaved walks to tmp
auto & r = tpw_[pair.src.walk_id()]; // create entry first
r = tpw_.size() < opt_[CHR(OPT_TMP)].size()? opt_[CHR(OPT_TMP)].str(tpw_.size()): "";
}
tmp = Json::interpolate(tpw_[pair.src.walk_id()], pair.src, pair.ns);
}
else { // round-robin (1 templ. or 1 walk)