-
Notifications
You must be signed in to change notification settings - Fork 33
/
jtc.cpp
2974 lines (2459 loc) · 132 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
/*
* Created by Dmitry Lyssenko, 2018, ldn.softdev@gmail.com
*
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <deque>
#include <set>
#include <climits> // LONG_MAX
#include <algorithm>
#include <regex>
#include <functional>
#include <unistd.h> // STDOUT_FILENO - for term width, sysconf
#include "lib/getoptions.hpp"
#include "lib/Json.hpp"
#include "lib/Streamstr.hpp"
#include "lib/shell.hpp"
#include "lib/dbg.hpp"
#include "lib/ThreadMaster.hpp"
#include "lib/signals.hpp"
using namespace std;
#define PRGNAME "JSON transformational chains"
#define VERSION "1.76a"
#define CREATOR "Dmitry Lyssenko"
#define EMAIL "ldn.softdev@gmail.com"
// jtc: fast and 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: all of the subscripts and search lexemes cached when walked: cache facilitates
// super fast recursive 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 provides modifications (including in-place) of source json via update/insert/purge/swap
// operations; the first two optionally could undergo also a shell evaluation
// o buffered and streamed inputs: the former provides a fast read and parsing, the latter
// facilitates on-fly json manipulation
// o employs multithreaded reading and parsing when multiple files given
// o chains multiple json operations via '/' separator - that replaces shell piping '|' and
// spares cpu cycles required for json outputting and reading/parsing in the latter case
// and many more others
//
// facilitate option materialization
// 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
#include "lib/jtc_guide.hpp" // don't move this line - above definitions are used in the file
#define XSTR(X) #X
#define STR(X) XSTR(X)
#define XCHR(X) *#X
#define CHR(X) XCHR(X)
#define CHR_NULL '\0'
#define CHR_RSLD '\\'
#define CHR_NLNE '\n'
#define CHR_CRTN '\r'
#define CHR_SQTE '\''
#define CHR_DQTE '"'
#define CHR_SPCE ' '
#define CHR_JILL '\x16' // JSON illegal, various purposes
#define STR_JILL "\x16" // JSON illegal, various purposes
#define STR_DCMP "/" // delimiter for option sets
#define STR_NLNE "\n"
#define STR_DQTE "\""
#define IND_SFX c
#define DBG_APFX "_" // alternative debug prefix
#define CMP_BASE "json_1"
#define CMP_COMP "json_2"
#define JTCS_TKN "size"
#define USE_HPFX -1 // gates tracking last walked value for {$?} and -x0/-1
#define WLK_HPFX "$?" // token for $? interpolation, namespace to reset to dflt
#define WLK_RSTH "#\x16" // used as a hidden namespace flag to RESET value $? to ""
#define FILE_NSP "$file"
#define SHELLSFX ";printf \"\\x16$?\\x16\"\n"
#define SHELLRGX "([^\x16]*)\x16(\\d+)\x16"
// various return codes
#define RETURN_CODES \
RC_OK, \
RC_UNUSED, \
RC_WP_INV, \
RC_SC_MISS, \
RC_CMP_NEQ, \
RC_ARG_FAIL, \
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_SHELL (OFF_JSON + Getopt::end_of_throw) // offset for Json exceptions
#define OFF_REGEX (OFF_SHELL + Jnode::end_of_throw) // offset for Regex exceptions
#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
#define SIZE_T(N) static_cast<size_t>(N)
#define SGNS_T(N) static_cast<signed_size_t>(N)
// simple macro for to expose class aliases
// usage: REVEAL(jtc, opt, json)
#define __REFX__(A) auto & A = STITCH_2TKNS(__reveal_class__,__LINE__).A();
#define REVEAL(X, ARGS...) \
auto & STITCH_2TKNS(__reveal_class__, __LINE__) = X; \
MACRO_TO_ARGS(__REFX__, ARGS)
// generic lambda with
#define GLAMBDA(FUNC) [this](auto&&... arg) { return FUNC(std::forward<decltype(arg)>(arg)...); }
typedef vector<string> v_string;
typedef ptrdiff_t signed_size_t;
// Design notes for handling namespaces so that avoid unnecessary copies:
//
// Json class namespaces:
// o Json class namespace may refer to remote entries (pointed by ptr) but never updates one,
// instead when new entry to be recorded, even if remote entry exists, the local entry is
// created
// o walked (source) Json's NS caries transient data (for the walk duration) only,
//
// Jtc class holds 2 structures facilitating namespace handling:
// - Global NS: holds namespace values after all walks (-w) are finished in between options sets
// - WNS: walked namespace snapshots, holds namespaces (snapshots) resulting after each
// walk iteration (step)
//
// Initializing sequence:
// Jtc implements an adapter for Json JnEntry class, adding 2 methods:
// o sync_in: sync entries into caller from the argument NS
// o sync_out: sync entries from the caller class into the argument NS
//
// - before all walking begins (walk_interleaved):
// o move all NS entries out of Global MS into the first WNS snapshot (by values)
//
// - during walking:
// o clear-init Json's NS with *references* from last/latest WNS snapshot
// o make a walk-step: Json NS will record locally only *new values*, while leaving intact
// remote entries referenced
// o record resulted namespaces into WNS: remote entries reman remote (i.e copy pointers),
// local entries to be moved by values
//
// - after walking (before destroying Jtc class):
// o move all values from the last wns snapshot to Global NS
//
// = that way it's ensured that all newly created JSON values will not be copied unnecessarily
// (only moved) for the duration of all jtc operations.
class map_jnse: public Json::map_jne {
// this is an adaptor of an Json::map_jne, extended with sync_in(), sinc_out() calls
public:
#define NSOPT \
NsUpdateRef, /* reference all ns values - only non-existent (new) ones */\
NsReferAll, /* reference all ns values - local and remote */\
NsMove, /* move local ns values, reference remote entriess */\
NsUpdate, /* same as NsMove, but for non-existent (new) entries only */\
NsMoveAll /* move all ns values - local & remote */
ENUM(NsOpType, NSOPT)
void sync_out(Json::map_jne &to, NsOpType nsopt);
void sync_in(Json::map_jne &from, NsOpType nsopt);
};
#undef NSOPT
void map_jnse::sync_out(Json::map_jne &to, NsOpType nsopt) {
// sync all entries from this map to Json namespace `to``
if(nsopt <= NsOpType::NsReferAll) { // reference all entries
for(auto &ns: *this) { // for all entries in this map
if(ns.KEY.front() < CHR_SPCE and ns.KEY.front() > CHR_NULL) // sync only valid namespaces
continue;
auto found = to.find(ns.KEY);
if(found == to.end())
to.emplace(ns.KEY, Json::JnEntry{}).first->VALUE.ptr(&ns.VALUE.ref());
else {
if(nsopt == NsOpType::NsUpdateRef) continue;
found->VALUE.ptr(&ns.VALUE.ref());
}
}
return;
}
// else: either NsMove
for(auto &ns: *this) { // for all entries in this map
if(ns.KEY.front() < CHR_SPCE and ns.KEY.front() > CHR_NULL) // sync only valid namespaces
continue;
if(ns.VALUE.is_remote() and nsopt == NsOpType::NsMove) { // refer remote
auto ip = to.emplace(ns.KEY, Json::JnEntry{}); // ip: insertion pair
ip.first->VALUE.ptr(&ns.VALUE.ref()); // override existing or refer new
continue;
}
auto found = to.find(ns.KEY);
if(found != to.end()) // found entry in `to`
found->VALUE = move(ns.VALUE.ref()); // overwrite to's entry
else // no local entry exists
to.emplace(ns.KEY, move(ns.VALUE.ref())); // emplace new entry
}
}
void map_jnse::sync_in(Json::map_jne &from, NsOpType nsopt) {
// sync all entries `from` map according to given handling type `nsopt`
if(nsopt <= NsOpType::NsReferAll) { // reference all entries
for(auto &ns: from) { // for all entries in from
if(ns.KEY.front() < CHR_SPCE and ns.KEY.front() > CHR_NULL) // sync only valid namespaces
continue;
auto mp = emplace(ns.KEY, Json::JnEntry{}); // mp: my pair
mp.first->VALUE.ptr(&ns.VALUE.ref()); // override w/o version check
}
return;
}
// else: either NsMove or NsMoveAll or NsUpdate
for(auto &ns: from) { // for all entries in from
if(ns.KEY.front() < CHR_SPCE and ns.KEY.front() > CHR_NULL) // sync only valid namespaces
continue;
auto found = find(ns.KEY);
if(nsopt == NsOpType::NsUpdate and found != end()) continue; // update only new records
if(ns.VALUE.is_remote() and nsopt != NsOpType::NsMoveAll) { // refer remote (NsMove/NsUpdate)
auto mp = emplace(ns.KEY, Json::JnEntry{}); // mp: my pair
mp.first->VALUE.ptr(&ns.VALUE.ref()); // override existing or refer new
continue;
}
if(found != end()) // local entry found
found->VALUE = move(ns.VALUE.ref()); // overwrite local entry
else // no local entry exists
emplace(ns.KEY, move(ns.VALUE.ref())); // emplace new entry
}
}
class GETopt: public Getopt {
// a trivial wrapper for Getopt accommodating user flags for imposition of options
public:
struct Multifactor {
// 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
friend SWAP(Multifactor, factor, offset)
Multifactor(void) = default;
Multifactor(const Multifactor &) = default;
Multifactor(Multifactor && mw) = default;
Multifactor(size_t x1, signed_size_t x2): // emplacement
factor{x1}, offset{x2} { }
Multifactor & operator=(Multifactor mw) noexcept { swap(*this, mw); return *this; }
size_t factor; // display every factor'th walk
signed_size_t offset; // starting from offset (0 based)
};
#define IMPFLG \
Install, \
Erase
ENUM(ImposeOpt, IMPFLG)
#undef IMPFLG
GETopt & reset(void)
{ Getopt::reset(); imp_.clear(); return *this; }
bool imposed(char opt) const
{ return imp_.count(opt); }
void impose(char opt, ImposeOpt x = ImposeOpt::Install)
{ if(x == ImposeOpt::Install) imp_.insert(opt); else imp_.erase(opt); }
set<char> & imp(void)
{ return imp_; }
auto & wm(void)
{ return wm_; }
char opt_eval(void) const
{ return opt_eval_; }
void opt_eval(char x)
{ opt_eval_ = x; }
bool process_last(size_t total) { // process -x/-1, if not yet done
// i.e. if -x/-1 (-x0) given but last hasn't been output yet
return any_of(wm_.begin(), wm_.end(),
[](auto &x){ return x.factor == 0 and x.offset == -1; })
and
none_of(wm_.begin(), wm_.end(),
[&](auto &x) {
return x.factor == 0?
false:
(total - 1) % x.factor == x.offset % x.factor;
});
};
bool process_walk(size_t walk) { // process walk given -xn/n?
return any_of(wm_.begin(), wm_.end(),
[&](auto &x) {
return x.factor == 0?
x.offset == static_cast<signed_size_t>(walk):
x.offset > static_cast<signed_size_t>(walk)?
false:
walk % x.factor == x.offset % x.factor;
} );
};
private:
set<char> imp_; // imposition of options
vector<Multifactor> wm_; // walk factor: facilitate -xN/M
char opt_eval_{CHR_NULL};
};
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
struct JsonStore {
// facilitate JSON storage for concurrent reading/parsing
JsonStore(void)
{ task_complete.lock(); } // DC
deque<Json> json_queue; // file may have multiple JSONs
mutex task_complete;
bool await_completion{true};
Streamstr::Filestatus file_status;
size_t err_location;
};
public:
int rc(void) const { return rc_; }
void rc(int rc) { rc_ = rc; }
GETopt & opt(signed_size_t x = -1) { // access to Getopt class instance
if(x < 0 or SIZE_T(x) >= vopt_.size()) return *opp_;
return vopt_[x];
}
map_jnse & global_ns(void) { return gns_; } // access to global namespaces
Streamstr & iss(void) { return iss_; } // access to stream class
Json & global_json(void) { return gjsn_; } // access to global JSON (-J)
size_t elocation(void) { return elocation_; }
void elocation(size_t x) { elocation_ = x; }
void decompose_opt(int argc, char *argv[]);
void display_opts(std::ostream & out);
bool is_decomposed_front(void) { return opp_ == &vopt_.front(); }
bool is_decomposed_back(void) { return opp_ == &vopt_.back(); }
void init_decomposed(void);
CommonResource & decomposed_back(void) { opp_ = &vopt_.back(); return *this; }
size_t total_decomposed(void) { return vopt_.size(); }
size_t decomposed_idx(void) {
for(size_t i = 0; i < vopt_.size(); ++i)
if(opp_ == &vopt_[i]) return i;
return -1;
}
void next_decomposed(void) {
for(size_t i = 0; i < vopt_.size(); ++i)
if(opp_ == &vopt_[i])
{ opp_ = &vopt_[i == vopt_.size() - 1? 0: i + 1]; break; }
}
char parse_opt(v_string & args);
void enable_global_output(void);
void disable_global_output(void);
void init_inputs(void);
void jsonize(Json jout);
auto & wm(void) { return wm_; }
ThreadMaster & tm(void) { return tm_; }
void decide_on_multithreaded_parsing(void);
deque<JsonStore> & json_store(void) { return jsd_; };
size_t jsq_idx(void) const { return jsq_idx_; };
void jsq_idx(size_t x) { jsq_idx_ = x; };
private:
bool is_recompile_required_(const v_string & args);
char recompile_args_(v_string &args);
void parse_arguments_(const v_string & new_args);
void convert_xyw_(void);
bool is_x_factor_(const char *str, signed_size_t * recursive = nullptr);
void fetch_dispatcher_(void);
void read_and_parse_json_(const string & fn, JsonStore & js);
Json gjsn_{ ARY{} }; // global json (facilitates -J)
map_jnse gns_; // global namespaces
bool read_from_cin_{false}; // read from <cin>? or files
Streamstr iss_; // input Streamstr
GETopt opt_; // user options template
GETopt * opp_{&opt_}; // pointer to a current Getopt
vector<GETopt> vopt_; // decomposed user options
vector<GETopt::Multifactor>
wm_; // walk factor: facilitate -xN/M
int rc_{RC_OK};
size_t elocation_{SIZE_T(-1)}; // exception location
ThreadMaster tm_; // thread manager
deque<JsonStore> jsd_; // JsonStore dequeue
size_t jsq_idx_{0}; // index into jsd_ store
public:
DEBUGGABLE(iss_, gjsn_, tm_)
};
class Jtc {
struct BoundJit {
// a trivial class to pair up destination walks (-w) with source walks (-i/u/c)
// and respective name-spaces
friend SWAP(BoundJit, dst, src, ns, lbl)
BoundJit(void) = default;
BoundJit(const BoundJit &) = default;
BoundJit(BoundJit && bj) = default;
BoundJit(Json::iterator &d, Json::iterator &s): // for emplacement
dst{d}, src{s} { ns.sync_in(s.json().ns(), map_jnse::NsMove); }
BoundJit(Json::iterator &d, Json::iterator &s, Json::map_jne &n):
dst{d}, src{s} { ns.sync_in(n, map_jnse::NsMove); }
BoundJit & operator=(BoundJit bj) noexcept
{ swap(*this, bj); return *this; }
bool update_lbl(void)
{ return lbl.empty() or lbl.front() != CHR_JILL; }
void reset_lbl(void) { lbl = STR_JILL; }
Json::iterator dst; // -w walks (iterators) go here
Json::iterator src; // -i/u/c walks (iterators) go here
map_jnse ns; // NS from respective -i/u/c iters.
string lbl{STR_JILL}; // facilitates -u for label update
// invalid lbl initially
};
struct Grouping {
// facilitates a pair of group/counter values, used in jsonized_output_ary_
friend SWAP(Grouping, group, counter)
Grouping(void) = default;
Grouping(const Grouping &) = default;
Grouping(Grouping && gr) = default;
Grouping(size_t x1, size_t x2): // emplacement
group{x1}, counter{x2} { }
Grouping & operator=(Grouping gr) noexcept { swap(*this, gr); return *this; }
bool operator>(const Grouping &rhs)
{ return group > rhs.group and counter > rhs.counter; }
size_t group{0};
size_t counter{0};
COUTABLE(Grouping, group, counter) // for debugs only
};
struct ShellReturn {
// keeping return result from running shell commands: string and return codes values
string str;
int rc;
};
typedef vector<Json::iterator> vec_jit;
typedef vector<BoundJit> vec_bjit;
typedef deque<Json::iterator> deq_jit;
typedef void (wlk_subscr)(Json::iterator &wi, Grouping grp);
typedef map<size_t, Json> map_json;
#define MERGEOBJ \
Preserve, /* clashing labels of merged object not overwritten */ \
Overwrite /* clashing labels of merged objects overwritten */
ENUM(MergeObj, MERGEOBJ)
#define JSONZIZE \
Jsonize, /* parameter for write_json() */\
Dont_jsonize
ENUM(Jsonizaion, JSONZIZE)
#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_mixed, /* source is in jsrc_[0]: -u<JSON> -u<walk> -u... */\
Src_optarg /* all sources are in jsrc_: -u<JSON> -u<FILE> -u<TMP> */
ENUMSTR(Jitsrc, JITSRC)
#define ECLI \
No_exec, \
Per_walk_exec, \
Bulk_exec
ENUM(Ecli, ECLI)
public:
Jtc(void) = delete;
Jtc(CommonResource & cr): cr_{cr} {
ecli_ = opt()[CHR(OPT_EXE)].hits() == 0?
Ecli::No_exec: opt()[CHR(OPT_EXE)].hits() == 1?
Ecli::Bulk_exec: Ecli::Per_walk_exec;
merge_ = opt()[CHR(OPT_MDF)].hits() % 2 == 1;// flag used by -i/-u options
is_tpw_ = opt()[CHR(OPT_TMP)].hits() > 1 and // is tmp per walk?
opt()[CHR(OPT_SEQ)].hits() < 2 and // no '-nn' given
opt()[CHR(OPT_TMP)].hits() == opt()[CHR(OPT_WLK)].hits();
// ready jinp_
jinp_.tab(opt()[CHR(OPT_IND)].hits() > 0 or // -t given, or
opt()[CHR(OPT_RAW)].hits() == 0? // no -r given
abs(opt()[CHR(OPT_IND)].str() == STR(IND_SFX)? // -tc?
atoi(opt()[CHR(OPT_IND)].c_str(0)):
static_cast<int>(opt()[CHR(OPT_IND)])): 1)
.merge_clashing(opt()[CHR(OPT_MDF)].hits() >= 2)
.raw(opt()[CHR(OPT_RAW)].hits() % 2)
.semicompact(opt()[CHR(OPT_IND)].str().back() == CHR(IND_SFX))
.quote_solidus(opt()[CHR(OPT_QUT)].hits() % 2 == 1);
jinp_.callback(GLAMBDA(shell_callback_)).engage_callbacks();
}
// expose private objects
GETopt & opt(signed_size_t x = -1) { return cr_.opt(x); }
auto & json(void) { return jinp_; }
auto & jout(void) { return jout_; }
// user methods
bool parsejson(Streamstr::const_iterator &jsp, Streamstr::const_iterator &pse);
bool move_fetched_json(Streamstr::const_iterator &jsp);
void write_json(Json & jsn, Jsonizaion allow = Jsonize);
bool demux_opt(void);
void compare_jsons(void);
void compare_bindings(void);
bool 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(Json::iterator &it, 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});
map_jnse & last_wns_snapshot(void) { return wns_[last_dwi_ptr_]; }
vec_bjit & itr_pairs(void) { return psrc_; }
private:
void display_location_(Streamstr::const_iterator & start);
void exception_locus_(Streamstr::const_iterator & start);
void exception_spot_(Streamstr::const_iterator & start);
void ready_params_(char option);
void parse_option_arg_(Json &, const string & arg, const string & jfile);
void ready_params_walks_(const string & arg, size_t jkey);
void read_json_(string &jfile, const string &fname);
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);
vector<ShellReturn> run_bulk_shell_(void);
bool execute_cli_(Json &update, Json::iterator &jit,
Json::map_jne &ns, ShellReturn *);
string interpolate_shell_str_(Json::iterator &jit, Json::map_jne &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_(wlk_subscr Jtc::* subscriber);
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);
bool shell_callback_(const std::string &lxm, Json::iterator &jit) {
Json tmp;
if(DBG()(0)) DBG().increment(+2, tmp, -2); // imbue current dbg +3
tmp = Json::interpolate(lxm, jit, jit.json().ns(), Json::Dont_parse);
sh_.system( tmp.str() );
return sh_.rc() == 0;
}
size_t walk_options_size_(void) const {
// return a number of walks except the source itself
switch(jsrt_) {
case Src_input: // all -i<WLK> -i<WLK> ...
return ecli_ == Ecli::No_exec? jsrc_.size(): jsrc_.size() - 1;
case Src_mixed: // mix: -i<JSN> -i<WLK> ...
return jsrc_.size() - 1;
case Src_optarg: // -i<JSN> -i<JSN> ...
return jsrc_.size();
}
return 0; // covering linux compiler warning
}
size_t walk_options_start_idx_(void) const
// return starting idx for the given type of a source
{ return jsrt_ == Src_optarg? 0: 1; }
// private member types:
CommonResource & cr_;
Json jout_; // json output (-j, -jj)
Json jinp_; // input JSON for jtc
vec_bjit psrc_; // binding dst walks with src's
map_json jtmp_; // for resolving <TMP> in -u/i/c
map_json jsrc_; // holding params of -i/u/c
Json::iterator jits_; // current source json iterator
Jitsrc jsrt_{Jitsrc::Src_input}; // source type of jits_
size_t jscur_{0}; // current walk in jsrc_
map<Json::iterator*, map_jnse>
wns_; // namespaces for walked (-w) paths
map_jnse ins_; // holds interleaved namespaces
Json::iterator * last_dwi_ptr_{nullptr};
Jnode hwlk_{ARY{STR{}}}; // last walked value (interpolated)
set<string> c2a_; // used in jsonized_output_obj_()
map<size_t, string> tpw_; // tmp per walk, output_by_iterator
bool is_tpw_; // templates pertain to walks
bool is_multi_walk_{false}; // multiple -w or single iterable?
bool convert_req_{false}; // used in output_by_iterator
Ecli ecli_{Ecli::No_exec}; // -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
wlk_subscr Jtc::* subscriber_; // method ptr for output processor
Shell sh_;
long arg_max_{-1}; // shell's ARG_MAX
// output conditions:
bool inquote_{opt()[CHR(OPT_RAW)].hits() >= 2}; // facilitate -rr
bool unquote_{opt()[CHR(OPT_QUT)].hits() >= 2}; // facilitate -qq
bool measure_{opt()[CHR(OPT_SZE)].hits() >= 1}; // facilitate -z
bool write_to_file_ // facilitate -f when file op.
{opt(0)[0].hits() > 0 and opt()[CHR(OPT_FRC)].hits() > 0};
bool glean_lbls_{opt()[CHR(OPT_LBL)].hits()>=2}; // facilitate -ll
bool size_only_{opt()[CHR(OPT_SZE)].hits() > 1}; // facilitate -zz
bool use_hpfx_{opt().imposed(USE_HPFX)}; // facilitate '$?' or -x0/-1
ofstream fout_ // open file handler if needed
{write_to_file_? cr_.iss().filename().c_str(): "", Jtc::mod_};
ostream & xout_{write_to_file_? fout_: cout}; // demux cout/file outputs
static ios_base::openmode
mod_; // write to file (1st json)
public:
DEBUGGABLE(jinp_, jout_, sh_)
};
#undef MERGEOBJ
#undef JSONZIZE
#undef JITSRC
ios_base::openmode Jtc::mod_{ios_base::out}; // write to file (first json)
// list of standalone calls
void run_decomposed_optsets(CommonResource &, Streamstr::const_iterator &);
void run_single_optset(CommonResource &, Streamstr::const_iterator &, Json &in, Json &out);
string sh_quote_str(const string &src);
int main(int argc, char *argv[]) {
Signal sgn;
CommonResource cr;
REVEAL(cr, opt)
opt.prolog("\n" PRGNAME "\nVersion " VERSION " (built on " __DATE__ \
"), developed by " CREATOR " (" EMAIL ")\n");
opt[CHR(OPT_ALL)].desc("process all JSONs from source, or disable multithreading if multiple"
" sources given");
opt[CHR(OPT_CMN)].desc("a common part of a walk-path, prepended to every followed -" STR(OPT_PRT)
" option").name("common_wp");
opt[CHR(OPT_CMP)].desc("compare with JSON (given as JSON/walk/template): display delta between"
" given JSONs").name("j|w|t");
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 argument 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 (suffix `" STR(IND_SFX)
"' enforces semi-compact format)").bind("3").name("indent");
opt[CHR(OPT_INS)].desc("insert JSON (given as JSON/walk/template); see with -" STR(OPT_GDE)
" for more").name("j|w|t");
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 all walked elements from one JSON into a JSON array (-"
STR(OPT_JSN) STR(OPT_JSN) " wrap into an object)");
opt[CHR(OPT_LBL)].desc("print labels (if present) for walked JSONs; together with -"
STR(OPT_JSN) " wrap into objects");
opt[CHR(OPT_MDF)].desc("modifier: toggle merging for options -" STR(OPT_INS) ", -" STR(OPT_UPD)
", -" STR(OPT_JSN) STR(OPT_JSN) "; 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 ones walked)");
opt[CHR(OPT_PRT)].desc("an individual part of a walk-path, prepended by preceding -" STR(OPT_CMN)
" option").name("partial_wp");
opt[CHR(OPT_QUT)].desc("enforce strict quoted solidus parsing"
" (-" STR(OPT_QUT) STR(OPT_QUT) ": unquote JSON strings)");
opt[CHR(OPT_RAW)].desc("print JSON in a raw (compact, one-line) format"
" (-" STR(OPT_RAW) STR(OPT_RAW) " 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 pairs 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) " print size only)");
opt[CHR(OPT_TMP)].desc("a template to interpolate and apply upon -" STR(OPT_INS) "/"
STR(OPT_UPD) "/" STR(OPT_CMP) " and standalone -"
STR(OPT_WLK) " operations").name("template");
opt[CHR(OPT_UPD)].desc("update with JSON (given as either JSON/walk/template); see with -"
STR(OPT_GDE) " for more").name("j|w|t");
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(s) to read from, or next set of options chained over '/'").name("args")
.bind("<stdin>");
opt.epilog(R"(
this tool provides ability to:
- parse, validate and display JSON (in a compact, semi-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 arrive) 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.decompose_opt(argc, argv); // from now on opt is dynamic!
// use cr.opt() instead
if(cr.opt()[CHR(OPT_DBG)]) // caveat: only debug in the first
sgn.install_all(); // decomposition will trigger it
DEBUGGABLE()
DBG().use_ostream(cerr)
.level(cr.opt()[CHR(OPT_DBG)])
.alt_prefix(DBG_APFX);
#ifdef BG_mTS
DBG().stamp_ms(true);
#endif
#ifdef BG_uTS
DBG().stamp_us(true);
#endif
#ifdef BG_dTS
DBG().stamp_delta(true);
#endif
if(DBG()(0)) cr.display_opts(DOUT());
ios_base::sync_with_stdio(false); // speedup cout & cin
cin.tie(nullptr);
// decide if multithreaded parsing to be engaged
cr.init_inputs();
cr.decide_on_multithreaded_parsing();
// ready to read json (ready stream buffer, produce iterator)
Streamstr::const_iterator jsp = cr.iss().begin(); // global parse pointer
// execute as per read options
try {
// if streamed cin, then 1st option set is processed differently: each (newly read) json
// will trigger processing of all subsequent option sets
do run_decomposed_optsets(cr, jsp);
while(jsp.is_streamed() and jsp != cr.iss().end());
}
catch(Jnode::stdException & e) {
DBG(1) DOUT() << "exception raised by: " << e.where() << endl;
cerr << cr.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;
cerr << cr.opt().prog_name() << " json" << (cr.elocation() == SIZE_T(-1)? "":" parsing" )
<< " exception"
<< (cr.elocation() == SIZE_T(-1)? string{""}:
(" (" + (cr.iss().filename().empty()? cr.opt()[0].str(0):cr.iss().filename())
+ ":" + to_string(cr.elocation()) + ")"))
<< ": " << e.what() << endl;
cr.rc(e.code() + OFF_JSON);
}
catch(Shell::stdException & e) {
DBG(1) DOUT() << "exception raised by: " << e.where() << endl;
cerr << cr.opt().prog_name() << " shell exception: " << e.what() << endl;
cr.rc(e.code() + OFF_SHELL);
}
catch(std::regex_error & e) {
cerr << "regexp exception: " << e.what() << endl;
cr.rc(e.code() + OFF_REGEX);
}
if(cr.global_json().is_empty()) exit(cr.rc());
Jtc jtc(cr.decomposed_back()); // need for access to write_json()
for(const char *o = STR(OPT_JAL) STR(OPT_JSN) STR(OPT_QUT) STR(OPT_RAW); *o != CHR_NULL; ++o)
jtc.opt()[*o].reset(); // above options to be ignored
jtc.write_json(cr.global_json());
exit(cr.rc());
}
void run_decomposed_optsets(CommonResource &cr, Streamstr::const_iterator &jsp) {
DEBUGGABLE()
#include "lib/dbgflow.hpp"
// run demux_opt for all decomposed options (in vopt_)
Json itrmi, itrmo; // interim input/output
if(DBG()(0)) { itrmi.DBG().severity(NDBG); itrmo.DBG().severity(NDBG); }
cr.global_ns().clear();
do { // do all opt sets
DBG().level(cr.opt()[CHR(OPT_DBG)]);
DBG(1) DOUT() << "pass for set[" << cr.decomposed_idx() << "]" << endl;
itrmo = ARY(); // output store
// in a streamed cin option processing of -J is reduced to -j - because of a difference in
// the was streamed cin processed vs other modes.
if(jsp.is_streamed() and cr.opt()[CHR(OPT_JAL)].hits() > 0) {
cr.disable_global_output();
cr.opt()[CHR(OPT_JAL)].reset();
if(cr.opt()[CHR(OPT_JSN)].hits() == 0) cr.opt()[CHR(OPT_JSN)].hit();
cerr << "notice: in " << STRENM(Streamstr::Strmod, Streamstr::Strmod::streamed_cin)
<< " mode, behavior of option -" STR(OPT_JAL) " is reduced to -" STR(OPT_JSN) << endl;
}
GUARD(cr.opt())
// the output of demux_opt() could be multiple JSONs (to the console) or single json (to gjsn_),
// to ensure option sets processing always impose -J (redirect to gjsn_)
if(not cr.is_decomposed_back()) { // output to be redirected to -J
if(cr.opt()[CHR(OPT_JAL)].hits() == 0) // impose one if not given
{ cr.enable_global_output(); cr.opt().impose(CHR(OPT_JAL)); } // and indicate -J was imposed
else // -J given by user
itrmo.push_back(ARY{}); // provide single storage
}
// process all jsons either from <input>, or from interim (itrmi)
run_single_optset(cr, jsp, itrmi, itrmo);
itrmi = move(itrmo);
cr.next_decomposed(); // point opt() to the next set
DBG(1) DOUT() << "moving to processing next option set[" << cr.decomposed_idx() << "]" << endl;
} while(not cr.is_decomposed_front()); // until wraps back to 1st set
}
void run_single_optset(CommonResource &cr,
Streamstr::const_iterator &jsp, Json &itrmi, Json &itrmo) {
// process json(s) from a single option set
do {
Jtc jtc(cr);
if(cr.is_decomposed_front()) { // in optset[0] - always parse json
auto pse = jsp;
if(jtc.parsejson(jsp, pse) == false) // if nothing left, or streamed cin
break; // move to a next set
}
else { // take jsons from itrmi
if(itrmi.is_empty()) break; // nothing left, move to next set
jtc.json().root() = move(itrmi.front());
itrmi.erase(0).normalize_idx();
}
bool maybe_tampered = jtc.demux_opt(); // run jtc operations
if(not cr.is_decomposed_back()) { // interim decomposed step
if(maybe_tampered) cr.global_json().normalize_idx(); // sanitize arrays indices
if(cr.opt().imposed(CHR(OPT_JAL))) // -J was imposed
for(auto &v: cr.global_json().root()) itrmo.push_back(move(v));
else // -J given by user
for(auto &v: cr.global_json().root()) itrmo.front().push_back(move(v));
cr.global_json() = ARY{};
}
cr.global_ns().sync_in(jtc.itr_pairs().empty()? jtc.last_wns_snapshot():
jtc.itr_pairs().back().ns, map_jnse::NsOpType::NsMoveAll);
if(cr.is_decomposed_front() and jsp.is_streamed()) break; // if streamed cin move to next set
} while((cr.opt()[CHR(OPT_ALL)].hits() > 0 and not cr.opt().imposed(CHR(OPT_ALL))) or // -a given
(cr.opt()[CHR(OPT_JAL)].hits() > 0 and not cr.opt().imposed(CHR(OPT_JAL))) or // -J given
(cr.is_decomposed_front() and not cr.json_store().empty())); // multiple files
}
//
// CR PUBLIC methods definitions
//
// populate vector of options (vopt_) with each decomposed GETopt
// initially opt() will be pointing to a global opt_ (defined in main),
// after init_decomposed it will point to first GETopt in vopt_
void CommonResource::decompose_opt(int argc, char *argv[]) {
#include "lib/dbgflow.hpp"
// decompose options by delimiter '/' and then parse each option separately
v_string args{argv, argv + argc};
v_string newargs;
do {
newargs.clear();
char chr_eval = parse_opt(args); // returns '\0', or 'i', or 'u'
if(opt().arguments() and opt()[0].str(1) == STR_DCMP) { // found '/' delimiter, decompose
newargs.push_back(move(args[0])); // move progname to newargs
args.clear();
args.push_back(newargs[0]); // reinstate progname in args
for(auto & o: opt().ordinal()) // copy all options to newargs
if(o.kind() == Option::opt) {
newargs.push_back(string{o.id() == CHR(OPT_RDT)? "": "-"} + static_cast<char>(o.id()));
if(o.type() == Option::parametric) newargs.push_back(o.str()); // copy argument e.g. "[]"
}
for(int i = 2; i <= opt().arguments(); ++i) // copy now all values to args
args.push_back(opt()[0].str(i)); // excluding first '/' argument
if(newargs.size() > 1) { // re-parse if there are any params
opt().Getopt::reset(); // reset opt (w/o touching imposed)
parse_arguments_(newargs);
}
}
if(newargs.size() != 1) { // record only if newargs is empty
vopt_.push_back(opt()); // or has arguments (b/s progname)
vopt_.back().opt_eval(chr_eval);
if(not wm().empty())
{ vopt_.back().wm() = move(wm()); wm().clear(); }
}
opt().reset();
}
while(not newargs.empty()); // i.e. repeat while '/' detected
if(vopt_.size() > 1) { // copy all standalone args from
for(auto & val: vopt_.back()[0]) vopt_.front()[0] = val; // last Getopt to front one
vopt_.back()[0].reset(); // and reset last Getopt's arg.
}
init_decomposed(); // make opt() point to vopt_.front
}
void CommonResource::init_decomposed(void) {
// init opp_ point to the front decomposed set,
// also remove non-transient options single -l, -r, -t, -q, '-', -f from interims sets
// - bare qualifier '-' allowed only in 1st set and not in the others
for(auto &opt: vopt_)
for(const char *o = STR(OPT_RAW) STR(OPT_IND) STR(OPT_QUT)
STR(OPT_SZE) STR(OPT_FRC) STR(OPT_RDT); *o != CHR_NULL; ++o) {
if(&opt == &vopt_.front()) { // handle 1st set
if(*o == CHR(OPT_RDT)) continue; // '-' allowed only in 1st set