mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
588 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af39fc737d | ||
|
|
f8a700e400 | ||
|
|
708ff02e8d | ||
|
|
2edf64182b | ||
|
|
b290a65602 | ||
|
|
05520c2168 | ||
|
|
5ab584afac | ||
|
|
b1f139c965 | ||
|
|
0cb2ff5b2e | ||
|
|
d6ff198d63 | ||
|
|
e515b8c2db | ||
|
|
b63b9644aa | ||
|
|
461c3e719c | ||
|
|
643f2467ee | ||
|
|
4edb3e9021 | ||
|
|
41c08891d3 | ||
|
|
85d059340b | ||
|
|
badd476730 | ||
|
|
65e6e492cd | ||
|
|
6a698332dd | ||
|
|
49615f5581 | ||
|
|
4f574efdc4 | ||
|
|
73cf6224f2 | ||
|
|
4bc98776a6 | ||
|
|
1910563bfe | ||
|
|
2ddf8dd4ed | ||
|
|
49b810cedd | ||
|
|
391ab8d70c | ||
|
|
b026ebf2c3 | ||
|
|
5e544a5b7e | ||
|
|
60511f5f92 | ||
|
|
59296b7d12 | ||
|
|
fccd03036f | ||
|
|
6829d8cb78 | ||
|
|
449fb8952c | ||
|
|
afffb2c3ba | ||
|
|
829ca3b424 | ||
|
|
d19e9f6917 | ||
|
|
6a0a4efa7b | ||
|
|
288aec942c | ||
|
|
7c4cf72468 | ||
|
|
b025000f20 | ||
|
|
1ba1e90e58 | ||
|
|
e0d1aed5b9 | ||
|
|
e6fd6905eb | ||
|
|
0a2a3c4374 | ||
|
|
ac076cd34a | ||
|
|
8170eec6d1 | ||
|
|
23083ed974 | ||
|
|
93aaa8ccee | ||
|
|
d7716551cf | ||
|
|
c791e5c663 | ||
|
|
a6d4dbb8b8 | ||
|
|
c7ebdda530 | ||
|
|
a0d940638c | ||
|
|
c09513803a | ||
|
|
f95226e267 | ||
|
|
f479a7e8e3 | ||
|
|
f7d4695837 | ||
|
|
5ee52f9506 | ||
|
|
c2159d9861 | ||
|
|
95bc1e1599 | ||
|
|
e037c57725 | ||
|
|
e32bc43c4e | ||
|
|
a8bdc12d83 | ||
|
|
ae59ad57f4 | ||
|
|
c321600afa | ||
|
|
4c95efa469 | ||
|
|
996ee0b433 | ||
|
|
bb9cb0c60e | ||
|
|
a125495eec | ||
|
|
7fa2835e13 | ||
|
|
e0f5cb3c59 | ||
|
|
87550b7fe5 | ||
|
|
5554301c29 | ||
|
|
3b0aaac626 | ||
|
|
65cb472604 | ||
|
|
fbba38c9b7 | ||
|
|
e5948c4f16 | ||
|
|
4eaadf98d0 | ||
|
|
eedbb0a99f | ||
|
|
7dabc57b65 | ||
|
|
fcd3a90f67 | ||
|
|
88e99e5336 | ||
|
|
a8cfccd3af | ||
|
|
3355e80d85 | ||
|
|
f528b28938 | ||
|
|
5b7b390a33 | ||
|
|
4f12e09e78 | ||
|
|
ee732fbf0b | ||
|
|
1507f929a2 | ||
|
|
c11c3df84f | ||
|
|
06bb3ac826 | ||
|
|
778f8c6916 | ||
|
|
9f43a4a265 | ||
|
|
bb6f07d147 | ||
|
|
759456e375 | ||
|
|
5e59803037 | ||
|
|
a0cb691601 | ||
|
|
fea8510061 | ||
|
|
b380ea2892 | ||
|
|
d66a709213 | ||
|
|
2fc39b3865 | ||
|
|
ee07edbd88 | ||
|
|
b11661a1be | ||
|
|
eac218980e | ||
|
|
80e7f46538 | ||
|
|
086f0ec6b9 | ||
|
|
89cbe63343 | ||
|
|
07cd3d4b8b | ||
|
|
b7b6988e76 | ||
|
|
9de2039c31 | ||
|
|
767709fef5 | ||
|
|
d9ae8e1e5a | ||
|
|
b0fa0e5b86 | ||
|
|
6777d639c0 | ||
|
|
de8dcff803 | ||
|
|
765ada4dc6 | ||
|
|
1405584892 | ||
|
|
8c9c326342 | ||
|
|
e90b00957b | ||
|
|
71f5f76213 | ||
|
|
b9e304e7a4 | ||
|
|
d473c39a44 | ||
|
|
9624410add | ||
|
|
b55fe48bd8 | ||
|
|
721dd57ed4 | ||
|
|
6fc3566acd | ||
|
|
4b63d92a3c | ||
|
|
3ccd32a47e | ||
|
|
3f913afbb9 | ||
|
|
ff598e1933 | ||
|
|
23de61a8d7 | ||
|
|
64135a16e1 | ||
|
|
06d8715cbe | ||
|
|
e15633023f | ||
|
|
6f0a329331 | ||
|
|
55511de9af | ||
|
|
2db69c91c9 | ||
|
|
33e35d10dd | ||
|
|
dfdbbbb24a | ||
|
|
55c4d01a91 | ||
|
|
7dc3d62bb6 | ||
|
|
11116804c5 | ||
|
|
f68b24323e | ||
|
|
8f166a9848 | ||
|
|
3c36db9285 | ||
|
|
605d6fab9b | ||
|
|
1f9a3f5f6c | ||
|
|
a06320f13c | ||
|
|
279996533d | ||
|
|
efe942727d | ||
|
|
e4dc70cc84 | ||
|
|
8ade1275e2 | ||
|
|
e1e05d85e3 | ||
|
|
b99467432e | ||
|
|
6b07143af7 | ||
|
|
ed234e37ce | ||
|
|
c0e4917d52 | ||
|
|
2713893f87 | ||
|
|
6bb221e973 | ||
|
|
7eb01a81da | ||
|
|
5c117204fa | ||
|
|
a4fa8f1341 | ||
|
|
69caccd2d3 | ||
|
|
67fb924e0e | ||
|
|
b64187fe32 | ||
|
|
8e6ceba2ac | ||
|
|
6ef04e1e77 | ||
|
|
10029420a5 | ||
|
|
f91093d5fe | ||
|
|
090432d241 | ||
|
|
22d5bd3615 | ||
|
|
0d477841da | ||
|
|
1f72817d74 | ||
|
|
125d04a75b | ||
|
|
08f6a90603 | ||
|
|
da398765b8 | ||
|
|
d356fa0d0b | ||
|
|
d22bfc241b | ||
|
|
954affea23 | ||
|
|
b0d1afb601 | ||
|
|
b286636909 | ||
|
|
bdf47c9797 | ||
|
|
1cc20d52bb | ||
|
|
651d9edf88 | ||
|
|
903605df39 | ||
|
|
0f9facc84b | ||
|
|
5af86b1333 | ||
|
|
2bd2a85a4c | ||
|
|
ceb76e5c17 | ||
|
|
44322f0248 | ||
|
|
0347516d82 | ||
|
|
a46386e093 | ||
|
|
f5c3beb159 | ||
|
|
9864afc4e7 | ||
|
|
69fae2d9cb | ||
|
|
83c13ce392 | ||
|
|
d83c46eec2 | ||
|
|
65802f9e0e | ||
|
|
07309e1685 | ||
|
|
24e906bae6 | ||
|
|
f084f2bb23 | ||
|
|
96a4161a92 | ||
|
|
5cc01e43bc | ||
|
|
9de2573009 | ||
|
|
29521f2e3e | ||
|
|
af5724ba29 | ||
|
|
0a39d29c53 | ||
|
|
72cd3e4a2a | ||
|
|
d40ad9649d | ||
|
|
63313ebb02 | ||
|
|
de3bfaef60 | ||
|
|
108b5cb093 | ||
|
|
b116f40348 | ||
|
|
ea9df0eede | ||
|
|
b7dd3e8e0a | ||
|
|
dc13fa99f7 | ||
|
|
179049a535 | ||
|
|
2fa8b24272 | ||
|
|
f1dbe13f21 | ||
|
|
02258fbaae | ||
|
|
6f0538173b | ||
|
|
6840ea8c78 | ||
|
|
70b88fa778 | ||
|
|
bfc1a621c4 | ||
|
|
166f866f28 | ||
|
|
b3598aaa43 | ||
|
|
14ac791eaf | ||
|
|
25293a6894 | ||
|
|
d828b214cc | ||
|
|
9e47685271 | ||
|
|
699fce9da4 | ||
|
|
f52de57652 | ||
|
|
b7554e6e76 | ||
|
|
ec25511f1b | ||
|
|
c6a52012ab | ||
|
|
63ded205e8 | ||
|
|
d1c1ab0a75 | ||
|
|
6ec8386f9e | ||
|
|
4dbe3636c2 | ||
|
|
4a5bd0ff5b | ||
|
|
44f36833cf | ||
|
|
789ea02096 | ||
|
|
7c27491dd6 | ||
|
|
720cc8f798 | ||
|
|
abda0e38af | ||
|
|
5cd4c347b0 | ||
|
|
1a4d8158ba | ||
|
|
8a65822b0b | ||
|
|
b7148adf20 | ||
|
|
64d38e9f03 | ||
|
|
3a387e65be | ||
|
|
56ba7c9a43 | ||
|
|
d203ec7b56 | ||
|
|
e2f79a3dae | ||
|
|
2d7be26ad5 | ||
|
|
350a8343e9 | ||
|
|
a3f8f9df10 | ||
|
|
35fd5b7ae4 | ||
|
|
2d237e7e8e | ||
|
|
74c7a4e027 | ||
|
|
f18c5161e0 | ||
|
|
eeeeeffd7b | ||
|
|
96955ffa9c | ||
|
|
9361b8b3e9 | ||
|
|
24dcb56466 | ||
|
|
728cbe991a | ||
|
|
854f5f0fc9 | ||
|
|
feba7b04fa | ||
|
|
0621307391 | ||
|
|
924eb6c462 | ||
|
|
52eef67e37 | ||
|
|
38d35185bc | ||
|
|
d8c29b26c1 | ||
|
|
e3f4eedd51 | ||
|
|
690da9ee74 | ||
|
|
1f7f1b0def | ||
|
|
1aa5ec1d40 | ||
|
|
a065a47b37 | ||
|
|
625cfdac75 | ||
|
|
4dbdd4a805 | ||
|
|
8a6af1720d | ||
|
|
0652f67a91 | ||
|
|
df52383ffb | ||
|
|
707ad09ba5 | ||
|
|
cf389bed4a | ||
|
|
ff5b23251b | ||
|
|
9925b26b9d | ||
|
|
93dbe80a77 | ||
|
|
27604289f4 | ||
|
|
3f36a18791 | ||
|
|
1e541cd65f | ||
|
|
5204a13685 | ||
|
|
3d3eaf3034 | ||
|
|
4fb44dbc47 | ||
|
|
784513dd18 | ||
|
|
865a55645c | ||
|
|
949bf1c1d7 | ||
|
|
19fe718cfb | ||
|
|
290579ac7f | ||
|
|
d7392f7b58 | ||
|
|
a3cebec2fd | ||
|
|
b81fd638d7 | ||
|
|
2344638da4 | ||
|
|
8be006fba4 | ||
|
|
53a4a47ce3 | ||
|
|
5988d0cffa | ||
|
|
b7640946ac | ||
|
|
d061b2f9f9 | ||
|
|
8c0046a622 | ||
|
|
586ffb833b | ||
|
|
9771e7001c | ||
|
|
8da9a81702 | ||
|
|
d97f1d8be2 | ||
|
|
dad61ec615 | ||
|
|
676fc63219 | ||
|
|
972e2b9575 | ||
|
|
aad15ccc6e | ||
|
|
5fc13bdccd | ||
|
|
95fec2984e | ||
|
|
64d1e58f97 | ||
|
|
4b3fbb878f | ||
|
|
26a09e6ec0 | ||
|
|
ceafed30f9 | ||
|
|
b8b2c9de61 | ||
|
|
f5fdf98c38 | ||
|
|
29986db8f8 | ||
|
|
1f4e3a9cde | ||
|
|
b6da773dde | ||
|
|
8020d4253b | ||
|
|
3c701fe98e | ||
|
|
97d1aa2b26 | ||
|
|
d05391e244 | ||
|
|
d1cec1ad18 | ||
|
|
fe5842e5f9 | ||
|
|
e0d8cd6bf6 | ||
|
|
8f5ffe47ff | ||
|
|
5acc1e661e | ||
|
|
a9c0ef571c | ||
|
|
7a28531f2f | ||
|
|
5de2bea1b4 | ||
|
|
7320b8d3c9 | ||
|
|
2f70e6f27a | ||
|
|
a9e871ee00 | ||
|
|
bc4bab9380 | ||
|
|
665d9079fa | ||
|
|
7b54a44fcf | ||
|
|
94148e4398 | ||
|
|
f8553162ca | ||
|
|
be532bf2fe | ||
|
|
e9b8265ca3 | ||
|
|
0f8b864321 | ||
|
|
e5bcfedbe9 | ||
|
|
a5f5fb2562 | ||
|
|
84de9c078d | ||
|
|
c7f5261036 | ||
|
|
6e35356a84 | ||
|
|
b2fe3e6738 | ||
|
|
774badfef4 | ||
|
|
c4e9516aa6 | ||
|
|
323089eb64 | ||
|
|
53289366a5 | ||
|
|
cda9a82906 | ||
|
|
2dbde6b9fb | ||
|
|
f8c1c3c1b4 | ||
|
|
238a1241d2 | ||
|
|
8a61ef072a | ||
|
|
4f178d2317 | ||
|
|
133e55105c | ||
|
|
5e5468af3b | ||
|
|
9b4972e46e | ||
|
|
23543ee031 | ||
|
|
75c7d40c44 | ||
|
|
44b8a5e80f | ||
|
|
77c8f22a79 | ||
|
|
386a0ca3c3 | ||
|
|
53a20d4421 | ||
|
|
478208b7c4 | ||
|
|
1159d0a212 | ||
|
|
8861b392a8 | ||
|
|
16bde80334 | ||
|
|
8a3fb32f36 | ||
|
|
48dcc15281 | ||
|
|
d7040e3933 | ||
|
|
ef579d4ccf | ||
|
|
785ee68a76 | ||
|
|
90fe9c6512 | ||
|
|
a9ade5a832 | ||
|
|
8d6e3a6a75 | ||
|
|
7a6689eb40 | ||
|
|
e6660e2460 | ||
|
|
28169b04f7 | ||
|
|
742cf748ac | ||
|
|
54c06cdb4c | ||
|
|
ef5f745da3 | ||
|
|
25025f2771 | ||
|
|
ebb8b34b31 | ||
|
|
9a5e4ee828 | ||
|
|
0a2dd29940 | ||
|
|
50dfce86c9 | ||
|
|
e55006e935 | ||
|
|
d8fed62f03 | ||
|
|
52a39bf31e | ||
|
|
d84254c30f | ||
|
|
18bb4eee96 | ||
|
|
86b9fe3ef9 | ||
|
|
2c15048ddb | ||
|
|
ce2ee42f71 | ||
|
|
c2c49dcb17 | ||
|
|
60de18391c | ||
|
|
c86f8b426b | ||
|
|
b3532e0a61 | ||
|
|
b3b60665e4 | ||
|
|
df08b055cf | ||
|
|
e822313e82 | ||
|
|
a9f25c9d76 | ||
|
|
0a8c268dcc | ||
|
|
a0e70279a8 | ||
|
|
25c9c22d8d | ||
|
|
d46d555b07 | ||
|
|
fb87f638f2 | ||
|
|
facc81d1f4 | ||
|
|
c1f9065c68 | ||
|
|
be84cc3082 | ||
|
|
a2571da1a1 | ||
|
|
6d6e476ac8 | ||
|
|
ae0c042ae6 | ||
|
|
867ec92d3a | ||
|
|
113586b5e0 | ||
|
|
c38f19e0a9 | ||
|
|
8ca85b1c64 | ||
|
|
08870f8ec9 | ||
|
|
94b217984c | ||
|
|
2f5a481cc3 | ||
|
|
1a4064429d | ||
|
|
1b22e1d812 | ||
|
|
297522cbdd | ||
|
|
be991fdacd | ||
|
|
be08214773 | ||
|
|
9e971ebeae | ||
|
|
f340db5795 | ||
|
|
ab852ceafa | ||
|
|
06a843e9b2 | ||
|
|
ebdc092688 | ||
|
|
27089d1ca1 | ||
|
|
1853585d22 | ||
|
|
0124d26086 | ||
|
|
0d68bea3dc | ||
|
|
54603b3607 | ||
|
|
c86aeca325 | ||
|
|
a4f124f24f | ||
|
|
090b377fa5 | ||
|
|
3a4d62d820 | ||
|
|
6053c8c136 | ||
|
|
c5a45ba7d5 | ||
|
|
56a0771cd1 | ||
|
|
8072e66d46 | ||
|
|
26153b3eb5 | ||
|
|
f2e10f21c7 | ||
|
|
d3ecf7aa88 | ||
|
|
ee8ffd458a | ||
|
|
8f15dba812 | ||
|
|
92ce195424 | ||
|
|
e725a2cec5 | ||
|
|
37de7a3505 | ||
|
|
e96794a420 | ||
|
|
120ef8e4dc | ||
|
|
db1954da25 | ||
|
|
e552dbb6ab | ||
|
|
298126864f | ||
|
|
d30cd8cc75 | ||
|
|
e6ee86930b | ||
|
|
0ea3c71df7 | ||
|
|
ef99bedf45 | ||
|
|
6e1a5aef2c | ||
|
|
70bd860287 | ||
|
|
0abda0098c | ||
|
|
a409a7e82b | ||
|
|
0e4f9e8579 | ||
|
|
0c932ba7dc | ||
|
|
cb48ba7173 | ||
|
|
dc4f8a6adb | ||
|
|
1f6d7a50b2 | ||
|
|
6fc3bdf58f | ||
|
|
87b33e8603 | ||
|
|
cc7eb84388 | ||
|
|
9e3f8ebd0a | ||
|
|
28bcbd321f | ||
|
|
9dd4503b23 | ||
|
|
185d1faadb | ||
|
|
7b004cb456 | ||
|
|
54c73eb34f | ||
|
|
3b03a0852a | ||
|
|
a00e6c5316 | ||
|
|
9d1a5b17b8 | ||
|
|
c5f80a105d | ||
|
|
c17f8df8f8 | ||
|
|
66c8390d54 | ||
|
|
dda9b1f087 | ||
|
|
683de28c64 | ||
|
|
51fa1a87b7 | ||
|
|
b9ac6a3d9d | ||
|
|
499974c27e | ||
|
|
72bd88cfa5 | ||
|
|
6980be3800 | ||
|
|
2933ea1684 | ||
|
|
cf2f23d747 | ||
|
|
45e9ad870f | ||
|
|
53b2c64747 | ||
|
|
359ca5a117 | ||
|
|
79baa49eaa | ||
|
|
2cda78ce0b | ||
|
|
6d1e61c410 | ||
|
|
86639acf70 | ||
|
|
3beee3f804 | ||
|
|
1ed8e7017e | ||
|
|
5514d2300b | ||
|
|
5bb0934710 | ||
|
|
cdd78af4bc | ||
|
|
d07e436065 | ||
|
|
b2a0964de2 | ||
|
|
69e6bb354d | ||
|
|
ec25886528 | ||
|
|
c2000a446b | ||
|
|
08ba0083be | ||
|
|
909b62be54 | ||
|
|
0facf35d60 | ||
|
|
a7c4813703 | ||
|
|
373e0a5eb7 | ||
|
|
124c57f9d9 | ||
|
|
fb73a793bd | ||
|
|
c22394b540 | ||
|
|
b1ff47022b | ||
|
|
82f1230db8 | ||
|
|
a3190f1504 | ||
|
|
44ee869e47 | ||
|
|
5d2d37a5f8 | ||
|
|
e3fc944709 | ||
|
|
6ee9db9a70 | ||
|
|
0d75edfb67 | ||
|
|
037314af8a | ||
|
|
2a283b4ef7 | ||
|
|
7ef556f22b | ||
|
|
6b566fd983 | ||
|
|
001925a4d4 | ||
|
|
3dee7b89d2 | ||
|
|
c955815aab | ||
|
|
3720bf8211 | ||
|
|
5f09aabf4c | ||
|
|
98751cf607 | ||
|
|
2ec3b59da2 | ||
|
|
56c923228d | ||
|
|
c122b9a843 | ||
|
|
15f51d4bf0 | ||
|
|
219105f999 | ||
|
|
955ecc2547 | ||
|
|
1d5fbd5ad0 | ||
|
|
1605938c2c | ||
|
|
2780903b60 | ||
|
|
81672d4efe | ||
|
|
6a696c81db | ||
|
|
805729e4f7 | ||
|
|
7db31006da | ||
|
|
8409be1cdf | ||
|
|
c03c4813d4 | ||
|
|
5ee6a9b9ca | ||
|
|
8efc46deae | ||
|
|
e4d5769f29 | ||
|
|
fea6e0db3d | ||
|
|
6823d43325 | ||
|
|
da4b1a6449 | ||
|
|
3a90629822 | ||
|
|
8aa69fc9ba | ||
|
|
690442d02e | ||
|
|
d1c545cca0 | ||
|
|
c4af37ed68 | ||
|
|
01845ea923 | ||
|
|
58bdb3ed21 | ||
|
|
0cb8a47ccb | ||
|
|
35ceb01222 | ||
|
|
c1b803364a | ||
|
|
364c1a8af3 | ||
|
|
2a0290aeac | ||
|
|
4ea8d5c4ee | ||
|
|
eeb16443d4 | ||
|
|
eebd319246 | ||
|
|
5a3c3a7152 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
bin
|
||||
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
version of yq:
|
||||
operating system:
|
||||
|
||||
**Input Yaml**
|
||||
Concise yaml document(s) (as simple as possible to show the bug)
|
||||
data1.yml:
|
||||
```yaml
|
||||
this: should really work
|
||||
```
|
||||
|
||||
data2.yml:
|
||||
```yaml
|
||||
but: it strangely didn't
|
||||
```
|
||||
|
||||
**Command**
|
||||
The command you ran:
|
||||
```
|
||||
yq merge data1.yml data2.yml
|
||||
```
|
||||
|
||||
**Actual behavior**
|
||||
|
||||
```yaml
|
||||
cat: meow
|
||||
```
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
```yaml
|
||||
this: should really work
|
||||
but: it strangely didn't
|
||||
```
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
36
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
If we have data1.yml like:
|
||||
|
||||
```yaml
|
||||
country: Australia
|
||||
```
|
||||
|
||||
And we run a command:
|
||||
|
||||
```bash
|
||||
yq predictWeather data1.yml
|
||||
```
|
||||
|
||||
it could output
|
||||
|
||||
```yaml
|
||||
temp: 32
|
||||
```
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
34
.github/workflows/go.yml
vendored
Normal file
34
.github/workflows/go.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Build
|
||||
on: [push]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.15
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.15
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
if [ -f Gopkg.toml ]; then
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure
|
||||
fi
|
||||
|
||||
- name: Download deps
|
||||
run: |
|
||||
export PATH=${PATH}:`go env GOPATH`/bin
|
||||
scripts/devtools.sh
|
||||
- name: Build
|
||||
run: |
|
||||
export PATH=${PATH}:`go env GOPATH`/bin
|
||||
make local build
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -6,7 +6,9 @@
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
bin
|
||||
build
|
||||
build-done
|
||||
.DS_Store
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
@@ -20,8 +22,19 @@ _cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
coverage.out
|
||||
coverage.html
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
yaml
|
||||
vendor/
|
||||
tmp/
|
||||
cover/
|
||||
yq
|
||||
|
||||
# snapcraft
|
||||
parts/
|
||||
prime/
|
||||
.snapcraft/
|
||||
yq*.snap
|
||||
|
||||
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at mikefarah@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
8
CONTRIBUTING.md
Normal file
8
CONTRIBUTING.md
Normal file
@@ -0,0 +1,8 @@
|
||||
1. Install (golang)[https://golang.org/]
|
||||
1. Run `scripts/devtools.sh` to install the required devtools
|
||||
2. Run `make [local] vendor` to install the vendor dependencies
|
||||
2. Run `make [local] test` to ensure you can run the existing tests
|
||||
3. Write unit tests - (see existing examples). Changes will not be accepted without corresponding unit tests.
|
||||
4. Make the code changes.
|
||||
5. `make [local] test` to lint code and run tests
|
||||
6. Profit! ok no profit, but raise a PR and get kudos :)
|
||||
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
FROM golang:1.15 as builder
|
||||
|
||||
WORKDIR /go/src/mikefarah/yq
|
||||
|
||||
# cache devtools
|
||||
COPY ./scripts/devtools.sh /go/src/mikefarah/yq/scripts/devtools.sh
|
||||
RUN ./scripts/devtools.sh
|
||||
|
||||
COPY . /go/src/mikefarah/yq
|
||||
|
||||
RUN CGO_ENABLED=0 make local build
|
||||
|
||||
# Choose alpine as a base image to make this useful for CI, as many
|
||||
# CI tools expect an interactive shell inside the container
|
||||
FROM alpine:3.12 as production
|
||||
|
||||
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
||||
RUN chmod +x /usr/bin/yq
|
||||
|
||||
ARG VERSION=none
|
||||
LABEL version=${VERSION}
|
||||
|
||||
WORKDIR /workdir
|
||||
|
||||
ENTRYPOINT [/usr/bin/yq]
|
||||
30
Dockerfile.dev
Normal file
30
Dockerfile.dev
Normal file
@@ -0,0 +1,30 @@
|
||||
FROM golang:1.15
|
||||
|
||||
COPY scripts/devtools.sh /opt/devtools.sh
|
||||
|
||||
RUN set -e -x \
|
||||
&& /opt/devtools.sh
|
||||
|
||||
# install mkdocs
|
||||
RUN set -ex \
|
||||
&& buildDeps=' \
|
||||
build-essential \
|
||||
python3-dev \
|
||||
' \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||
$buildDeps \
|
||||
python3 \
|
||||
python3-setuptools \
|
||||
python3-wheel \
|
||||
python3-pip \
|
||||
&& pip3 install --upgrade \
|
||||
pip \
|
||||
'Markdown>=2.6.9' \
|
||||
'mkdocs>=0.16.3' \
|
||||
'mkdocs-material>=1.10.1' \
|
||||
'markdown-include>=0.5.1' \
|
||||
&& apt-get purge -y --auto-remove $buildDeps \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV CGO_ENABLED 0
|
||||
ENV GOPATH /go:/yq
|
||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2017 Mike Farah
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
114
Makefile
Normal file
114
Makefile
Normal file
@@ -0,0 +1,114 @@
|
||||
MAKEFLAGS += --warn-undefined-variables
|
||||
SHELL := /bin/bash
|
||||
.SHELLFLAGS := -o pipefail -euc
|
||||
.DEFAULT_GOAL := install
|
||||
|
||||
include Makefile.variables
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo 'Management commands for cicdtest:'
|
||||
@echo
|
||||
@echo 'Usage:'
|
||||
@echo ' ## Develop / Test Commands'
|
||||
@echo ' make build Build yq binary.'
|
||||
@echo ' make install Install yq.'
|
||||
@echo ' make xcompile Build cross-compiled binaries of yq.'
|
||||
@echo ' make vendor Install dependencies to vendor directory.'
|
||||
@echo ' make format Run code formatter.'
|
||||
@echo ' make check Run static code analysis (lint).'
|
||||
@echo ' make test Run tests on project.'
|
||||
@echo ' make cover Run tests and capture code coverage metrics on project.'
|
||||
@echo ' make clean Clean the directory tree of produced artifacts.'
|
||||
@echo
|
||||
@echo ' ## Utility Commands'
|
||||
@echo ' make setup Configures Minishfit/Docker directory mounts.'
|
||||
@echo
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@rm -rf bin build cover *.out
|
||||
|
||||
## prefix before other make targets to run in your local dev environment
|
||||
local: | quiet
|
||||
@$(eval DOCKRUN= )
|
||||
@mkdir -p tmp
|
||||
@touch tmp/dev_image_id
|
||||
quiet: # this is silly but shuts up 'Nothing to be done for `local`'
|
||||
@:
|
||||
|
||||
prepare: tmp/dev_image_id
|
||||
tmp/dev_image_id: Dockerfile.dev scripts/devtools.sh
|
||||
@mkdir -p tmp
|
||||
@docker rmi -f ${DEV_IMAGE} > /dev/null 2>&1 || true
|
||||
@docker build -t ${DEV_IMAGE} -f Dockerfile.dev .
|
||||
@docker inspect -f "{{ .ID }}" ${DEV_IMAGE} > tmp/dev_image_id
|
||||
|
||||
# ----------------------------------------------
|
||||
# build
|
||||
.PHONY: build
|
||||
build: build/dev
|
||||
|
||||
.PHONY: build/dev
|
||||
build/dev: test *.go
|
||||
@mkdir -p bin/
|
||||
${DOCKRUN} go build --ldflags "$(LDFLAGS)"
|
||||
${DOCKRUN} bash ./scripts/acceptance.sh
|
||||
|
||||
## Compile the project for multiple OS and Architectures.
|
||||
xcompile: check
|
||||
@rm -rf build/
|
||||
@mkdir -p build
|
||||
${DOCKRUN} bash ./scripts/xcompile.sh
|
||||
@find build -type d -exec chmod 755 {} \; || :
|
||||
@find build -type f -exec chmod 755 {} \; || :
|
||||
|
||||
.PHONY: install
|
||||
install: build
|
||||
${DOCKRUN} go install
|
||||
|
||||
# Each of the fetch should be an entry within vendor.json; not currently included within project
|
||||
.PHONY: vendor
|
||||
vendor: tmp/dev_image_id
|
||||
${DOCKRUN} go mod vendor
|
||||
|
||||
# ----------------------------------------------
|
||||
# develop and test
|
||||
|
||||
.PHONY: format
|
||||
format: vendor
|
||||
${DOCKRUN} bash ./scripts/format.sh
|
||||
|
||||
.PHONY: check
|
||||
check: format
|
||||
${DOCKRUN} bash ./scripts/check.sh
|
||||
|
||||
.PHONY: test
|
||||
test: check
|
||||
${DOCKRUN} bash ./scripts/test.sh
|
||||
|
||||
.PHONY: cover
|
||||
cover: check
|
||||
@rm -rf cover/
|
||||
@mkdir -p cover
|
||||
${DOCKRUN} bash ./scripts/coverage.sh
|
||||
@find cover -type d -exec chmod 755 {} \; || :
|
||||
@find cover -type f -exec chmod 644 {} \; || :
|
||||
|
||||
.PHONY: build-docs
|
||||
build-docs: prepare mkdocs.yml mkdocs/*
|
||||
${DOCKRUN} mkdocs build
|
||||
@find docs -type d -exec chmod 755 {} \; || :
|
||||
@find docs -type f -exec chmod 644 {} \; || :
|
||||
|
||||
.PHONY: release
|
||||
release: xcompile
|
||||
${DOCKRUN} bash ./scripts/publish.sh
|
||||
|
||||
# ----------------------------------------------
|
||||
# utilities
|
||||
|
||||
.PHONY: setup
|
||||
setup:
|
||||
@bash ./scripts/setup.sh
|
||||
38
Makefile.variables
Normal file
38
Makefile.variables
Normal file
@@ -0,0 +1,38 @@
|
||||
export PROJECT = yq
|
||||
IMPORT_PATH := github.com/mikefarah/${PROJECT}
|
||||
|
||||
export GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
||||
export GIT_DIRTY = $(shell test -n "$$(git status --porcelain)" && echo "+CHANGES" || true)
|
||||
export GIT_DESCRIBE = $(shell git describe --tags --always)
|
||||
LDFLAGS :=
|
||||
LDFLAGS += -X main.GitCommit=${GIT_COMMIT}${GIT_DIRTY}
|
||||
LDFLAGS += -X main.GitDescribe=${GIT_DESCRIBE}
|
||||
|
||||
GITHUB_TOKEN ?=
|
||||
|
||||
# Windows environment?
|
||||
CYG_CHECK := $(shell hash cygpath 2>/dev/null && echo 1)
|
||||
ifeq ($(CYG_CHECK),1)
|
||||
VBOX_CHECK := $(shell hash VBoxManage 2>/dev/null && echo 1)
|
||||
|
||||
# Docker Toolbox (pre-Windows 10)
|
||||
ifeq ($(VBOX_CHECK),1)
|
||||
ROOT := /${PROJECT}
|
||||
else
|
||||
# Docker Windows
|
||||
ROOT := $(shell cygpath -m -a "$(shell pwd)")
|
||||
endif
|
||||
else
|
||||
# all non-windows environments
|
||||
ROOT := $(shell pwd)
|
||||
endif
|
||||
|
||||
DEV_IMAGE := ${PROJECT}_dev
|
||||
|
||||
DOCKRUN := docker run --rm \
|
||||
-e LDFLAGS="${LDFLAGS}" \
|
||||
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
|
||||
-v ${ROOT}/vendor:/go/src \
|
||||
-v ${ROOT}:/${PROJECT}/src/${IMPORT_PATH} \
|
||||
-w /${PROJECT}/src/${IMPORT_PATH} \
|
||||
${DEV_IMAGE}
|
||||
185
README.md
185
README.md
@@ -1,47 +1,166 @@
|
||||
# yaml
|
||||
yaml command line tool written in go
|
||||
# yq
|
||||
|
||||
Allows you to read (and soon update) yaml files given a yaml path.
|
||||
   
|
||||
|
||||
|
||||
a lightweight and portable command-line YAML processor
|
||||
|
||||
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.
|
||||
|
||||
## Install
|
||||
|
||||
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
|
||||
|
||||
### MacOS:
|
||||
```
|
||||
go get github.com/mikefarah/yaml
|
||||
brew install yq
|
||||
```
|
||||
|
||||
## Read examples
|
||||
### Ubuntu and other Linux distros supporting `snap` packages:
|
||||
```
|
||||
yaml <yaml file> <path>
|
||||
snap install yq
|
||||
```
|
||||
|
||||
### Basic
|
||||
Given a sample.yaml file of:
|
||||
```yaml
|
||||
b:
|
||||
c: 2
|
||||
#### Snap notes
|
||||
`yq` installs with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can:
|
||||
|
||||
```
|
||||
then
|
||||
sudo cat /etc/myfile | yq r - a.path
|
||||
```
|
||||
|
||||
And to write to a root file you can either use [sponge](https://linux.die.net/man/1/sponge):
|
||||
```
|
||||
sudo cat /etc/myfile | yq w - a.path value | sudo sponge /etc/myfile
|
||||
```
|
||||
or write to a temporary file:
|
||||
```
|
||||
sudo cat /etc/myfile | yq w - a.path value | sudo tee /etc/myfile.tmp
|
||||
sudo mv /etc/myfile.tmp /etc/myfile
|
||||
rm /etc/myfile.tmp
|
||||
```
|
||||
|
||||
### wget
|
||||
|
||||
Use wget to download the pre-compiled binaries:
|
||||
|
||||
```bash
|
||||
yaml sample.yaml b.c
|
||||
wget https://github.com/mikefarah/yq/releases/download/{VERSION}/{BINARY} -O /usr/bin/yq &&\
|
||||
chmod +x /usr/bin/yq
|
||||
```
|
||||
will output the value of '2'.
|
||||
|
||||
### Arrays
|
||||
You can give an index to access a specific element:
|
||||
e.g.: given a sample file of
|
||||
```yaml
|
||||
b:
|
||||
e:
|
||||
- name: fred
|
||||
value: 3
|
||||
- name: sam
|
||||
value: 4
|
||||
```
|
||||
then
|
||||
```
|
||||
yaml sample.yaml b.e.1.name
|
||||
```
|
||||
will output 'sam'
|
||||
For instance, VERSION=3.4.0 and BINARY=yq_linux_amd64
|
||||
|
||||
## TODO
|
||||
* Updating yaml files
|
||||
* Handling '.' in path names
|
||||
|
||||
### Run with Docker
|
||||
|
||||
#### Oneshot use:
|
||||
|
||||
```bash
|
||||
docker run --rm -v "${PWD}":/workdir mikefarah/yq yq [flags] <command> FILE...
|
||||
```
|
||||
|
||||
#### Run commands interactively:
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
|
||||
```
|
||||
|
||||
It can be useful to have a bash function to avoid typing the whole docker command:
|
||||
|
||||
```bash
|
||||
yq() {
|
||||
docker run --rm -i -v "${PWD}":/workdir mikefarah/yq yq "$@"
|
||||
}
|
||||
```
|
||||
|
||||
### Go Get:
|
||||
```
|
||||
GO111MODULE=on go get github.com/mikefarah/yq/v3
|
||||
```
|
||||
|
||||
## Community Supported Installation methods
|
||||
As these are supported by the community :heart: - however, they may be out of date with the officially supported releases.
|
||||
|
||||
|
||||
### Windows:
|
||||
```
|
||||
choco install yq
|
||||
```
|
||||
Supported by @chillum (https://chocolatey.org/packages/yq)
|
||||
|
||||
### Alpine Linux
|
||||
- Enable edge/community repo by adding ```$MIRROR/alpine/edge/community``` to ```/etc/apk/repositories```
|
||||
- Update database index with ```apk update```
|
||||
- Install yq with ```apk add yq```
|
||||
|
||||
Supported by Tuan Hoang
|
||||
https://pkgs.alpinelinux.org/package/edge/community/x86/yq
|
||||
|
||||
|
||||
### On Ubuntu 16.04 or higher from Debian package:
|
||||
```sh
|
||||
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CC86BB64
|
||||
sudo add-apt-repository ppa:rmescandon/yq
|
||||
sudo apt update
|
||||
sudo apt install yq -y
|
||||
```
|
||||
Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
|
||||
|
||||
## Features
|
||||
- Written in portable go, so you can download a lovely dependency free binary
|
||||
- [Colorize the output](https://mikefarah.gitbook.io/yq/usage/output-format#colorize-output)
|
||||
- [Deep read a yaml file with a given path expression](https://mikefarah.gitbook.io/yq/commands/read#basic)
|
||||
- [List matching paths of a given path expression](https://mikefarah.gitbook.io/yq/commands/read#path-only)
|
||||
- [Return the lengths of arrays/object/scalars](https://mikefarah.gitbook.io/yq/commands/read#printing-length-of-the-results)
|
||||
- Update a yaml file given a [path expression](https://mikefarah.gitbook.io/yq/commands/write-update#basic) or [script file](https://mikefarah.gitbook.io/yq/commands/write-update#basic)
|
||||
- Update creates any missing entries in the path on the fly
|
||||
- Deeply [compare](https://mikefarah.gitbook.io/yq/commands/compare) yaml files
|
||||
- Keeps yaml formatting and comments when updating
|
||||
- [Validate a yaml file](https://mikefarah.gitbook.io/yq/commands/validate)
|
||||
- Create a yaml file given a [deep path and value](https://mikefarah.gitbook.io/yq/commands/create#creating-a-simple-yaml-file) or a [script file](https://mikefarah.gitbook.io/yq/commands/create#creating-using-a-create-script)
|
||||
- [Prefix a path to a yaml file](https://mikefarah.gitbook.io/yq/commands/prefix)
|
||||
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/usage/convert)
|
||||
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/commands/read#from-stdin)
|
||||
- [Merge](https://mikefarah.gitbook.io/yq/commands/merge) multiple yaml files with various options for [overriding](https://mikefarah.gitbook.io/yq/commands/merge#overwrite-values) and [appending](https://mikefarah.gitbook.io/yq/commands/merge#append-values-with-arrays)
|
||||
- Supports multiple documents in a single yaml file for [reading](https://mikefarah.gitbook.io/yq/commands/read#multiple-documents), [writing](https://mikefarah.gitbook.io/yq/commands/write-update#multiple-documents) and [merging](https://mikefarah.gitbook.io/yq/commands/merge#multiple-documents)
|
||||
- General shell completion scripts (bash/zsh/fish/powershell) (https://mikefarah.gitbook.io/yq/commands/shell-completion)
|
||||
|
||||
## [Usage](https://mikefarah.gitbook.io/yq/)
|
||||
|
||||
Check out the [documentation](https://mikefarah.gitbook.io/yq/) for more detailed and advanced usage.
|
||||
|
||||
```
|
||||
Usage:
|
||||
yq [flags]
|
||||
yq [command]
|
||||
|
||||
Available Commands:
|
||||
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
|
||||
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
|
||||
help Help about any command
|
||||
merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml
|
||||
new yq n [--script/-s script_file] a.b.c newValue
|
||||
prefix yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c
|
||||
read yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value'
|
||||
shell-completion Generates shell completion scripts
|
||||
validate yq v sample.yaml
|
||||
write yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue
|
||||
|
||||
Flags:
|
||||
-C, --colors print with colors
|
||||
-h, --help help for yq
|
||||
-I, --indent int sets indent level for output (default 2)
|
||||
-P, --prettyPrint pretty print
|
||||
-j, --tojson output as json. By default it prints a json document in one line, use the prettyPrint flag to print a formatted doc.
|
||||
-v, --verbose verbose mode
|
||||
-V, --version Print version information and quit
|
||||
|
||||
Use "yq [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
## Upgrade from V2
|
||||
If you've been using v2 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/upgrading-from-v2).
|
||||
|
||||
## Known Issues / Missing Features
|
||||
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
|
||||
- You cannot (yet) select multiple paths/keys from the yaml to be printed out (https://github.com/mikefarah/yq/issues/287)
|
||||
|
||||
13
action.yml
Normal file
13
action.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
name: 'yq - portable yaml processor'
|
||||
description: 'create, read, update, delete, merge, validate and do more with yaml'
|
||||
icon: command
|
||||
color: gray-dark
|
||||
inputs:
|
||||
cmd:
|
||||
description: 'The Command which should be run'
|
||||
required: true
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'github-action/Dockerfile'
|
||||
args:
|
||||
- ${{ inputs.cmd }}
|
||||
83
cmd/commands_test.go
Normal file
83
cmd/commands_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package cmd
|
||||
|
||||
// import (
|
||||
// "strings"
|
||||
// "testing"
|
||||
|
||||
// "github.com/mikefarah/yq/v3/test"
|
||||
// "github.com/spf13/cobra"
|
||||
// )
|
||||
|
||||
// func getRootCommand() *cobra.Command {
|
||||
// return New()
|
||||
// }
|
||||
|
||||
// func TestRootCmd(t *testing.T) {
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, "")
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
|
||||
// if !strings.Contains(result.Output, "Usage:") {
|
||||
// t.Error("Expected usage message to be printed out, but the usage message was not found.")
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestRootCmd_Help(t *testing.T) {
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, "--help")
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
|
||||
// if !strings.Contains(result.Output, "yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.") {
|
||||
// t.Error("Expected usage message to be printed out, but the usage message was not found.")
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestRootCmd_VerboseLong(t *testing.T) {
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, "--verbose")
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
|
||||
// if !verbose {
|
||||
// t.Error("Expected verbose to be true")
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestRootCmd_VerboseShort(t *testing.T) {
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, "-v")
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
|
||||
// if !verbose {
|
||||
// t.Error("Expected verbose to be true")
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestRootCmd_VersionShort(t *testing.T) {
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, "-V")
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// if !strings.Contains(result.Output, "yq version") {
|
||||
// t.Error("expected version message to be printed out, but the message was not found.")
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestRootCmd_VersionLong(t *testing.T) {
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, "--version")
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// if !strings.Contains(result.Output, "yq version") {
|
||||
// t.Error("expected version message to be printed out, but the message was not found.")
|
||||
// }
|
||||
// }
|
||||
19
cmd/constant.go
Normal file
19
cmd/constant.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package cmd
|
||||
|
||||
var unwrapScalar = true
|
||||
|
||||
// var writeInplace = false
|
||||
var outputToJSON = false
|
||||
|
||||
// var exitStatus = false
|
||||
var forceColor = false
|
||||
var forceNoColor = false
|
||||
var colorsEnabled = false
|
||||
var indent = 2
|
||||
var noDocSeparators = false
|
||||
var nullInput = false
|
||||
var verbose = false
|
||||
var version = false
|
||||
var shellCompletion = ""
|
||||
|
||||
// var log = logging.MustGetLogger("yq")
|
||||
64
cmd/evaluate_all_command.go
Normal file
64
cmd/evaluate_all_command.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func createEvaluateAllCommand() *cobra.Command {
|
||||
var cmdEvalAll = &cobra.Command{
|
||||
Use: "eval-all [expression] [yaml_file1]...",
|
||||
Aliases: []string{"ea"},
|
||||
Short: "Loads _all_ yaml documents of _all_ yaml files and runs expression once",
|
||||
Example: `
|
||||
yq es '.a.b | length' file1.yml file2.yml
|
||||
yq es < sample.yaml
|
||||
yq es -n '{"a": "b"}'
|
||||
`,
|
||||
Long: "Evaluate All:\nUseful when you need to run an expression across several yaml documents or files. Consumes more memory than eval",
|
||||
RunE: evaluateAll,
|
||||
}
|
||||
return cmdEvalAll
|
||||
}
|
||||
func evaluateAll(cmd *cobra.Command, args []string) error {
|
||||
// 0 args, read std in
|
||||
// 1 arg, null input, process expression
|
||||
// 1 arg, read file in sequence
|
||||
// 2+ args, [0] = expression, file the rest
|
||||
|
||||
var err error
|
||||
stat, _ := os.Stdin.Stat()
|
||||
pipingStdIn := (stat.Mode() & os.ModeCharDevice) == 0
|
||||
|
||||
out := cmd.OutOrStdout()
|
||||
|
||||
fileInfo, _ := os.Stdout.Stat()
|
||||
|
||||
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
||||
colorsEnabled = true
|
||||
}
|
||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = yqlib.EvaluateAllFileStreams("", []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
|
||||
} else {
|
||||
err = yqlib.EvaluateAllFileStreams("", []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = yqlib.EvaluateAllFileStreams(args[0], args[1:], printer)
|
||||
}
|
||||
|
||||
cmd.SilenceUsage = true
|
||||
return err
|
||||
}
|
||||
64
cmd/evalute_sequence_command.go
Normal file
64
cmd/evalute_sequence_command.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func createEvaluateSequenceCommand() *cobra.Command {
|
||||
var cmdEvalSequence = &cobra.Command{
|
||||
Use: "eval [expression] [yaml_file1]...",
|
||||
Aliases: []string{"e"},
|
||||
Short: "Apply expression to each document in each yaml file given in sequence",
|
||||
Example: `
|
||||
yq es '.a.b | length' file1.yml file2.yml
|
||||
yq es < sample.yaml
|
||||
yq es -n '{"a": "b"}'
|
||||
`,
|
||||
Long: "Evaluate Sequence:\nIterate over each yaml document, apply the expression and print the results, in sequence.",
|
||||
RunE: evaluateSequence,
|
||||
}
|
||||
return cmdEvalSequence
|
||||
}
|
||||
func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||
// 0 args, read std in
|
||||
// 1 arg, null input, process expression
|
||||
// 1 arg, read file in sequence
|
||||
// 2+ args, [0] = expression, file the rest
|
||||
|
||||
var err error
|
||||
stat, _ := os.Stdin.Stat()
|
||||
pipingStdIn := (stat.Mode() & os.ModeCharDevice) == 0
|
||||
|
||||
out := cmd.OutOrStdout()
|
||||
|
||||
fileInfo, _ := os.Stdout.Stat()
|
||||
|
||||
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
||||
colorsEnabled = true
|
||||
}
|
||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = yqlib.EvaluateFileStreamsSequence("", []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
|
||||
} else {
|
||||
err = yqlib.EvaluateFileStreamsSequence("", []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = yqlib.EvaluateFileStreamsSequence(args[0], args[1:], printer)
|
||||
}
|
||||
|
||||
cmd.SilenceUsage = true
|
||||
return err
|
||||
}
|
||||
71
cmd/root.go
Normal file
71
cmd/root.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
func New() *cobra.Command {
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "yq",
|
||||
Short: "yq is a lightweight and portable command-line YAML processor.",
|
||||
Long: `yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if version {
|
||||
cmd.Print(GetVersionDisplay())
|
||||
return nil
|
||||
}
|
||||
if shellCompletion != "" {
|
||||
switch shellCompletion {
|
||||
case "bash", "":
|
||||
return cmd.GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
return cmd.GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
return cmd.GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
return cmd.GenPowerShellCompletion(os.Stdout)
|
||||
default:
|
||||
return fmt.Errorf("Unknown variant %v", shellCompletion)
|
||||
}
|
||||
}
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
|
||||
},
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
cmd.SetOut(cmd.OutOrStdout())
|
||||
var format = logging.MustStringFormatter(
|
||||
`%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`,
|
||||
)
|
||||
var backend = logging.AddModuleLevel(
|
||||
logging.NewBackendFormatter(logging.NewLogBackend(os.Stderr, "", 0), format))
|
||||
|
||||
if verbose {
|
||||
backend.SetLevel(logging.DEBUG, "")
|
||||
} else {
|
||||
backend.SetLevel(logging.ERROR, "")
|
||||
}
|
||||
|
||||
logging.SetBackend(backend)
|
||||
},
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
|
||||
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json. Set indent to 0 to print json in one line.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&nullInput, "null-input", "n", false, "Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&noDocSeparators, "no-doc", "N", false, "Don't print document separators (---)")
|
||||
|
||||
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||
|
||||
rootCmd.Flags().StringVarP(&shellCompletion, "shellCompletion", "", "", "[bash/zsh/powershell/fish] prints shell completion script")
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
|
||||
rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors")
|
||||
rootCmd.AddCommand(createEvaluateSequenceCommand(), createEvaluateAllCommand())
|
||||
return rootCmd
|
||||
}
|
||||
49
cmd/version.go
Normal file
49
cmd/version.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The git commit that was compiled. This will be filled in by the compiler.
|
||||
var (
|
||||
GitCommit string
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "4.0.0-alpha1"
|
||||
|
||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
// such as "dev" (in development), "beta", "rc1", etc.
|
||||
VersionPrerelease = ""
|
||||
)
|
||||
|
||||
// ProductName is the name of the product
|
||||
const ProductName = "yq"
|
||||
|
||||
// GetVersionDisplay composes the parts of the version in a way that's suitable
|
||||
// for displaying to humans.
|
||||
func GetVersionDisplay() string {
|
||||
return fmt.Sprintf("%s version %s\n", ProductName, getHumanVersion())
|
||||
}
|
||||
|
||||
func getHumanVersion() string {
|
||||
version := Version
|
||||
if GitDescribe != "" {
|
||||
version = GitDescribe
|
||||
}
|
||||
|
||||
release := VersionPrerelease
|
||||
if release != "" {
|
||||
if !strings.Contains(version, release) {
|
||||
version += fmt.Sprintf("-%s", release)
|
||||
}
|
||||
if GitCommit != "" {
|
||||
version += fmt.Sprintf(" (%s)", GitCommit)
|
||||
}
|
||||
}
|
||||
|
||||
// Strip off any single quotes added by the git information.
|
||||
return strings.Replace(version, "'", "", -1)
|
||||
}
|
||||
51
cmd/version_test.go
Normal file
51
cmd/version_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package cmd
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetVersionDisplay(t *testing.T) {
|
||||
var expectedVersion = ProductName + " version " + Version
|
||||
if VersionPrerelease != "" {
|
||||
expectedVersion = expectedVersion + "-" + VersionPrerelease
|
||||
}
|
||||
expectedVersion = expectedVersion + "\n"
|
||||
tests := []struct {
|
||||
name string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Display Version",
|
||||
want: expectedVersion,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := GetVersionDisplay(); got != tt.want {
|
||||
t.Errorf("%q. GetVersionDisplay() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getHumanVersion(t *testing.T) {
|
||||
GitDescribe = "e42813d"
|
||||
GitCommit = "e42813d+CHANGES"
|
||||
var wanted string
|
||||
if VersionPrerelease == "" {
|
||||
wanted = GitDescribe
|
||||
} else {
|
||||
wanted = "e42813d-" + VersionPrerelease + " (e42813d+CHANGES)"
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Git Variables defined",
|
||||
want: wanted,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := getHumanVersion(); got != tt.want {
|
||||
t.Errorf("%q. getHumanVersion() = %v, want %v", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
61
cmd/write.go
Normal file
61
cmd/write.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package cmd
|
||||
|
||||
// import (
|
||||
// "github.com/spf13/cobra"
|
||||
// )
|
||||
|
||||
// func createWriteCmd() *cobra.Command {
|
||||
// var cmdWrite = &cobra.Command{
|
||||
// Use: "write [yaml_file] [path_expression] [value]",
|
||||
// Aliases: []string{"w"},
|
||||
// Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue",
|
||||
// Example: `
|
||||
// yq write things.yaml 'a.b.c' true
|
||||
// yq write things.yaml 'a.*.c' true
|
||||
// yq write things.yaml 'a.**' true
|
||||
// yq write things.yaml 'a.(child.subchild==co*).c' true
|
||||
// yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
|
||||
// yq write things.yaml 'a.b.c' --tag '!!float' 3
|
||||
// yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags
|
||||
// yq w -i things.yaml 'a.b.c' cat
|
||||
// yq w -i -s update_script.yaml things.yaml
|
||||
// yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array
|
||||
// yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file
|
||||
// `,
|
||||
// Long: `Updates the yaml file w.r.t the given path and value.
|
||||
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
||||
|
||||
// Append value to array adds the value to the end of array.
|
||||
|
||||
// Update Scripts:
|
||||
// Note that you can give an update script to perform more sophisticated update. Update script
|
||||
// format is list of update commands (update or delete) like so:
|
||||
// ---
|
||||
// - command: update
|
||||
// path: b.c
|
||||
// value:
|
||||
// #great
|
||||
// things: frog # wow!
|
||||
// - command: delete
|
||||
// path: b.d
|
||||
// `,
|
||||
// RunE: writeProperty,
|
||||
// }
|
||||
// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
||||
// cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
|
||||
// cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)")
|
||||
// cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
|
||||
// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||
// cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
|
||||
// cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
|
||||
// cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
|
||||
// return cmdWrite
|
||||
// }
|
||||
|
||||
// func writeProperty(cmd *cobra.Command, args []string) error {
|
||||
// var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>", true)
|
||||
// if updateCommandsError != nil {
|
||||
// return updateCommandsError
|
||||
// }
|
||||
// return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
|
||||
// }
|
||||
610
cmd/write_test.go
Normal file
610
cmd/write_test.go
Normal file
@@ -0,0 +1,610 @@
|
||||
package cmd
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "runtime"
|
||||
// "strings"
|
||||
// "testing"
|
||||
|
||||
// "github.com/mikefarah/yq/v3/test"
|
||||
// )
|
||||
|
||||
// func TestWriteCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: 7
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteKeepCommentsCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3 # comment
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: 7 # comment
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteWithTaggedStyleCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: dog
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --tag=!!str --style=tagged", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: !!str cat
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteWithDoubleQuotedStyleCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: dog
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=double", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: "cat"
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteUpdateStyleOnlyCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: dog
|
||||
// d: things
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --style=single", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: 'dog'
|
||||
// d: 'things'
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteUpdateTagOnlyCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: true
|
||||
// d: false
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --tag=!!str", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: "true"
|
||||
// d: "false"
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteWithSingleQuotedStyleCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: dog
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=single", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: 'cat'
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteWithLiteralStyleCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: dog
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=literal", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: |-
|
||||
// cat
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteWithFoldedStyleCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: dog
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=folded", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: >-
|
||||
// cat
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteEmptyMultiDocCmd(t *testing.T) {
|
||||
// content := `# this is empty
|
||||
// ---
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s c 7", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `c: 7
|
||||
|
||||
// # this is empty
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteSurroundingEmptyMultiDocCmd(t *testing.T) {
|
||||
// content := `---
|
||||
// # empty
|
||||
// ---
|
||||
// cat: frog
|
||||
// ---
|
||||
|
||||
// # empty
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d1 c 7", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `
|
||||
|
||||
// # empty
|
||||
// ---
|
||||
// cat: frog
|
||||
// c: 7
|
||||
// ---
|
||||
|
||||
// # empty
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteFromFileCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// source := `kittens: are cute # sure are!`
|
||||
// fromFilename := test.WriteTempYamlFile(source)
|
||||
// defer test.RemoveTempYamlFile(fromFilename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c -f %s", filename, fromFilename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c:
|
||||
// kittens: are cute # sure are!
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteEmptyCmd(t *testing.T) {
|
||||
// content := ``
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: 7
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteAutoCreateCmd(t *testing.T) {
|
||||
// content := `applications:
|
||||
// - name: app
|
||||
// env:`
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s applications[0].env.hello world", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `applications:
|
||||
// - name: app
|
||||
// env:
|
||||
// hello: world
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteCmdScript(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// updateScript := `- command: update
|
||||
// path: b.c
|
||||
// value: 7`
|
||||
// scriptFilename := test.WriteTempYamlFile(updateScript)
|
||||
// defer test.RemoveTempYamlFile(scriptFilename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: 7
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteCmdEmptyScript(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// updateScript := ``
|
||||
// scriptFilename := test.WriteTempYamlFile(updateScript)
|
||||
// defer test.RemoveTempYamlFile(scriptFilename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: 3
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteMultiCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3
|
||||
// ---
|
||||
// apples: great
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: 3
|
||||
// ---
|
||||
// apples: ok
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
// func TestWriteInvalidDocumentIndexCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename))
|
||||
// if result.Error == nil {
|
||||
// t.Error("Expected command to fail due to invalid path")
|
||||
// }
|
||||
// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
|
||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
// }
|
||||
|
||||
// func TestWriteBadDocumentIndexCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
|
||||
// if result.Error == nil {
|
||||
// t.Error("Expected command to fail due to invalid path")
|
||||
// }
|
||||
// expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
|
||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
// }
|
||||
// func TestWriteMultiAllCmd(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3
|
||||
// ---
|
||||
// apples: great
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: 3
|
||||
// apples: ok
|
||||
// ---
|
||||
// apples: ok`
|
||||
// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_EmptyArray(t *testing.T) {
|
||||
// content := `b: 3`
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s a []", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b: 3
|
||||
// a: []
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_Error(t *testing.T) {
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, "write")
|
||||
// if result.Error == nil {
|
||||
// t.Error("Expected command to fail due to missing arg")
|
||||
// }
|
||||
// expectedOutput := `Must provide <filename> <path_to_update> <value>`
|
||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_ErrorUnreadableFile(t *testing.T) {
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, "write fake-unknown a.b 3")
|
||||
// if result.Error == nil {
|
||||
// t.Error("Expected command to fail due to unknown file")
|
||||
// }
|
||||
// var expectedOutput string
|
||||
// if runtime.GOOS == "windows" {
|
||||
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
|
||||
// } else {
|
||||
// expectedOutput = `open fake-unknown: no such file or directory`
|
||||
// }
|
||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_Inplace(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: 3
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// gotOutput := test.ReadTempYamlFile(filename)
|
||||
// expectedOutput := `b:
|
||||
// c: 7`
|
||||
// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_InplaceError(t *testing.T) {
|
||||
// content := `b: cat
|
||||
// c: 3
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
|
||||
// if result.Error == nil {
|
||||
// t.Error("Expected Error to occur!")
|
||||
// }
|
||||
// gotOutput := test.ReadTempYamlFile(filename)
|
||||
// test.AssertResult(t, content, gotOutput)
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_Append(t *testing.T) {
|
||||
// content := `b:
|
||||
// - foo
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// - foo
|
||||
// - 7
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_AppendInline(t *testing.T) {
|
||||
// content := `b: [foo]`
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b: [foo, 7]
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_AppendInlinePretty(t *testing.T) {
|
||||
// content := `b: [foo]`
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -P b[+] 7", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// - foo
|
||||
// - 7
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_AppendEmptyArray(t *testing.T) {
|
||||
// content := `a: 2
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `a: 2
|
||||
// b:
|
||||
// - v
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_SplatArray(t *testing.T) {
|
||||
// content := `b:
|
||||
// - c: thing
|
||||
// - c: another thing
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// - c: new
|
||||
// - c: new
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_SplatMap(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: thing
|
||||
// d: another thing
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: new
|
||||
// d: new
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
|
||||
// func TestWriteCmd_SplatMapEmpty(t *testing.T) {
|
||||
// content := `b:
|
||||
// c: thing
|
||||
// d: another thing
|
||||
// `
|
||||
// filename := test.WriteTempYamlFile(content)
|
||||
// defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
// cmd := getRootCommand()
|
||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename))
|
||||
// if result.Error != nil {
|
||||
// t.Error(result.Error)
|
||||
// }
|
||||
// expectedOutput := `b:
|
||||
// c: {}
|
||||
// d: another thing
|
||||
// `
|
||||
// test.AssertResult(t, expectedOutput, result.Output)
|
||||
// }
|
||||
13
compare.sh
Executable file
13
compare.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo "${GREEN}---Old---${NC}"
|
||||
yq $@ > /tmp/yq-old-output
|
||||
cat /tmp/yq-old-output
|
||||
|
||||
echo "${GREEN}---New---${NC}"
|
||||
./yq $@ > /tmp/yq-new-output
|
||||
cat /tmp/yq-new-output
|
||||
|
||||
echo "${GREEN}---Diff---${NC}"
|
||||
colordiff /tmp/yq-old-output /tmp/yq-new-output
|
||||
76
debian/changelog
vendored
Normal file
76
debian/changelog
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
yq (3.3.2) focal; urgency=medium
|
||||
|
||||
* Bug fix: existStatus bug (#459)
|
||||
* Automatically makes a os temp directory if it does not exist (#461)
|
||||
|
||||
-- Roberto Mier Escandon <rmescandon@gmail.com> Fri, 07 Aug 2020 18:53:01 +0200
|
||||
|
||||
yq (3.3-0) focal; urgency=medium
|
||||
|
||||
* You can control string styles (quotes) using the new --style flag
|
||||
* String values now always have quotes when outputting to json
|
||||
* Negative array indices now traverse the array backwards
|
||||
* Added a --stripComments flag to print yaml without any comments
|
||||
* Bumped go to version 1.14
|
||||
|
||||
-- Roberto Mier Escandon <rmescandon@gmail.com> Thu, 30 Apr 2020 20:45:44 +0200
|
||||
|
||||
yq (3.1-2) eoan; urgency=medium
|
||||
|
||||
* Bug fix: yq 3 was removing empty inline-style objects and arrays (#355)
|
||||
* Bug fix: Merge option returned different output when switching order of
|
||||
merging files(#347)
|
||||
* Bug fix: Add new object to existing array object was failing in 3.1.1 (#361)
|
||||
* Bug fix: yq 3 empty keys did not allow merging of values (#356)
|
||||
* Bug fix: keys quoted during merge (#363)
|
||||
* Bug fix: Correct length with wc -l (#362)
|
||||
* Bug fix: Write to empty document removed path (#359)
|
||||
|
||||
-- Roberto Mier Escandon <rmescandon@gmail.com> Mon, 24 Feb 2020 20:31:58 +0100
|
||||
|
||||
yq (3.1-1) eoan; urgency=medium
|
||||
|
||||
* Keeps yaml comments and formatting, can specify yaml tags when updating.
|
||||
* Handles anchors
|
||||
* Can print out matching paths and values when splatting
|
||||
* JSON output works for all commands
|
||||
* Yaml files with multiple documents are printed out as one JSON
|
||||
document per line.
|
||||
* Deep splat (**) to match arbitrary paths
|
||||
* Update scripts file format has changed to be more powerful
|
||||
* Reading and splatting, matching results are printed once per line
|
||||
* Bugfixing
|
||||
|
||||
-- Roberto Mier Escandon <rmescandon@gmail.com> Tue, 11 Feb 2020 22:18:24 +0100
|
||||
|
||||
yq (2.2-1) bionic; urgency=medium
|
||||
|
||||
* Added Windows support for the "--inplace" command flag
|
||||
* Prefix now supports arrays
|
||||
* Add prefix command
|
||||
* Bump Alpine version to 3.8
|
||||
* Improved docker build process
|
||||
* Lint fixes
|
||||
* Build support for all linux architectures supported by gox
|
||||
|
||||
-- Roberto Mier Escandon <rmescandon@gmail.com> Sat, 19 Jan 2019 15:50:47 +0100
|
||||
|
||||
yq (2.1-0) bionic; urgency=medium
|
||||
|
||||
* Ability to read multiple documents in a single file
|
||||
* Ability to append list items instead of overwriting
|
||||
|
||||
-- Roberto Mier EscandĂłn <rmescandon@gmail.com> Tue, 10 Jul 2018 14:02:42 +0200
|
||||
|
||||
yq (2.0-0) bionic; urgency=medium
|
||||
|
||||
* Release 2.0.0
|
||||
|
||||
-- Roberto Mier EscandĂłn <rmescandon@gmail.com> Wed, 20 Jun 2018 10:29:53 +0200
|
||||
|
||||
yq (1.15-0) bionic; urgency=medium
|
||||
|
||||
* Release 1.15
|
||||
|
||||
-- Roberto Mier EscandĂłn <rmescandon@gmail.com> Wed, 06 Jun 2018 11:32:03 +0200
|
||||
|
||||
1
debian/compat
vendored
Normal file
1
debian/compat
vendored
Normal file
@@ -0,0 +1 @@
|
||||
10
|
||||
22
debian/control
vendored
Normal file
22
debian/control
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Source: yq
|
||||
Section: devel
|
||||
Priority: optional
|
||||
Maintainer: Roberto Mier EscandĂłn <rmescandon@gmail.com>
|
||||
Build-Depends: debhelper (>=10),
|
||||
dh-golang (>=1.34),
|
||||
golang-1.13,
|
||||
rsync
|
||||
Standards-Version: 4.1.4
|
||||
Homepage: https://github.com/mikefarah/yq.git
|
||||
Vcs-Browser: https://github.com/mikefarah/yq.git
|
||||
Vcs-Git: https://github.com/mikefarah/yq.git
|
||||
XS-Go-Import-Path: github.com/mikefarah/yq
|
||||
XSBC-Original-Maintainer: Roberto Mier EscandĂłn <rmescandon@gmail.com>
|
||||
|
||||
Package: yq
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Description: lightweight and portable command-line YAML processor
|
||||
.
|
||||
The aim of the project is to be the
|
||||
[jq](https://github.com/stedolan/jq) or sed of yaml files.
|
||||
24
debian/copyright
vendored
Normal file
24
debian/copyright
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: yq
|
||||
Source: https://github.com/mikefarah/yq.git
|
||||
|
||||
Files: *
|
||||
Copyright: 2017 Mike Farah
|
||||
License: Expat
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
2
debian/gbp.conf
vendored
Normal file
2
debian/gbp.conf
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[DEFAULT]
|
||||
pristine-tar = True
|
||||
57
debian/rules
vendored
Executable file
57
debian/rules
vendored
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/make -f
|
||||
#
|
||||
# Copyright (C) 2018 Roberto Mier EscandĂłn <rmescandon@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 3 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
PROJECT := yq
|
||||
OWNER := mikefarah
|
||||
REPO := github.com
|
||||
GOVERSION := 1.13
|
||||
|
||||
export DH_OPTIONS
|
||||
export DH_GOPKG := ${REPO}/${OWNER}/${PROJECT}
|
||||
export GOROOT := /usr/lib/go-${GOVERSION}
|
||||
export GOPATH := ${CURDIR}/_build
|
||||
export GOBIN := ${GOPATH}/bin
|
||||
export PATH := ${GOROOT}/bin:${GOBIN}:${PATH}
|
||||
export GOCACHE := /tmp/gocache
|
||||
export GOFLAGS := -mod=vendor
|
||||
|
||||
SRCDIR := ${GOPATH}/src/${DH_GOPKG}
|
||||
DESTDIR := ${CURDIR}/debian/${PROJECT}
|
||||
BINDIR := /usr/bin
|
||||
ASSETSDIR := /usr/share/${PROJECT}
|
||||
|
||||
%:
|
||||
dh $@ --builddirectory=${GOPATH} --buildsystem=golang
|
||||
|
||||
override_dh_auto_build:
|
||||
mkdir -p ${SRCDIR}
|
||||
mkdir -p ${GOBIN}
|
||||
# copy project to local srcdir to build from there
|
||||
rsync -avz --progress --exclude=_build --exclude=debian --exclude=tmp. --exclude=go.mod --exclude=docs . $(SRCDIR)
|
||||
# build go code
|
||||
(cd ${SRCDIR} && go install -buildmode=pie ./...)
|
||||
|
||||
override_dh_auto_test:
|
||||
(cd ${SRCDIR} && go test -v ./...)
|
||||
|
||||
override_dh_auto_install:
|
||||
cp ${GOBIN}/yq ${DESTDIR}/${BINDIR}
|
||||
cp -f ${SRCDIR}/LICENSE ${DESTDIR}/${ASSETSDIR}
|
||||
chmod a+x ${DESTDIR}/${BINDIR}/yq
|
||||
|
||||
override_dh_auto_clean:
|
||||
dh_clean
|
||||
rm -rf ${CURDIR}/_build
|
||||
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
3
debian/yq.dirs
vendored
Normal file
3
debian/yq.dirs
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
usr/bin
|
||||
usr/share/yq
|
||||
usr/share/man/man1
|
||||
11
examples/array.yaml
Normal file
11
examples/array.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- become: true
|
||||
gather_facts: false
|
||||
hosts: lalaland
|
||||
name: "Apply smth"
|
||||
roles:
|
||||
- lala
|
||||
- land
|
||||
serial: 1
|
||||
- become: false
|
||||
gather_facts: true
|
||||
8
examples/bad.yaml
Normal file
8
examples/bad.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
b:
|
||||
d: be gone
|
||||
c: 2
|
||||
e:
|
||||
- name: Billy Bob # comment over here
|
||||
|
||||
---
|
||||
[123123
|
||||
4
examples/data1-no-comments.yaml
Normal file
4
examples/data1-no-comments.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
a: simple
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
4
examples/data1.yaml
Normal file
4
examples/data1.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
7
examples/data2.yaml
Normal file
7
examples/data2.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
a: other # better than the original
|
||||
b: [3, 4]
|
||||
c:
|
||||
toast: leave
|
||||
test: 1
|
||||
tell: 1
|
||||
tasty.taco: cool
|
||||
4
examples/data3.yaml
Normal file
4
examples/data3.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
a: "simple" # just the best
|
||||
b: [1, 3]
|
||||
c:
|
||||
test: 1
|
||||
2
examples/empty.yaml
Normal file
2
examples/empty.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
# a: apple
|
||||
# b: cat
|
||||
7
examples/instruction_sample.yaml
Normal file
7
examples/instruction_sample.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
- command: update
|
||||
path: b.c
|
||||
value:
|
||||
#great
|
||||
things: frog # wow!
|
||||
- command: delete
|
||||
path: b.d
|
||||
19
examples/merge-anchor.yaml
Normal file
19
examples/merge-anchor.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
|
||||
bar: &bar
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
<<: [*foo,*bar]
|
||||
c: foobarList_c
|
||||
|
||||
foobar:
|
||||
c: foobar_c
|
||||
<<: *foo
|
||||
thing: foobar_thing
|
||||
5
examples/multiline-text.yaml
Normal file
5
examples/multiline-text.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
test: |
|
||||
abcdefg
|
||||
hijklmno
|
||||
|
||||
|
||||
18
examples/multiple_docs.yaml
Normal file
18
examples/multiple_docs.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
commonKey: first document
|
||||
a: Easy! as one two three
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
e:
|
||||
- name: fred
|
||||
value: 3
|
||||
- name: sam
|
||||
value: 4
|
||||
---
|
||||
commonKey: second document
|
||||
another:
|
||||
document: here
|
||||
---
|
||||
commonKey: third document
|
||||
wow:
|
||||
- here is another
|
||||
7
examples/multiple_docs_small.yaml
Normal file
7
examples/multiple_docs_small.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
a: Easy! as one two three
|
||||
---
|
||||
another:
|
||||
document: here
|
||||
---
|
||||
- 1
|
||||
- 2
|
||||
2
examples/numbered_keys.yml
Normal file
2
examples/numbered_keys.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
5:
|
||||
6: camel!
|
||||
2
examples/order.yaml
Normal file
2
examples/order.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
version: 3
|
||||
application: MyApp
|
||||
6
examples/order.yml
Normal file
6
examples/order.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
version: '2'
|
||||
services:
|
||||
test:
|
||||
image: ubuntu:14.04
|
||||
stdin_open: true
|
||||
tty: true
|
||||
1
examples/sample.json
Normal file
1
examples/sample.json
Normal file
@@ -0,0 +1 @@
|
||||
{"a":"Easy! as one two three","b":{"c":2,"d":[3,4],"e":[{"name":"fred","value":3},{"name":"sam","value":4}]},"ab":"must appear last"}
|
||||
@@ -1,9 +1,11 @@
|
||||
a: Easy! as one two three
|
||||
# Some doc
|
||||
|
||||
a: true
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
d: [3, 4, 5]
|
||||
e:
|
||||
- name: fred
|
||||
value: 3
|
||||
- name: sam
|
||||
value: 4
|
||||
value: 4
|
||||
1
examples/sample_array.yaml
Normal file
1
examples/sample_array.yaml
Normal file
@@ -0,0 +1 @@
|
||||
[1,2,3]
|
||||
2
examples/sample_array_2.yaml
Normal file
2
examples/sample_array_2.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- 4
|
||||
- 5
|
||||
1
examples/sample_text.yaml
Normal file
1
examples/sample_text.yaml
Normal file
@@ -0,0 +1 @@
|
||||
hi
|
||||
5
examples/simple-anchor-exploded.yaml
Normal file
5
examples/simple-anchor-exploded.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
foo:
|
||||
a: 1
|
||||
|
||||
foobar:
|
||||
a: 1
|
||||
5
examples/simple-anchor.yaml
Normal file
5
examples/simple-anchor.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
foo: &foo
|
||||
a: 1
|
||||
|
||||
foobar:
|
||||
<<: *foo
|
||||
5
github-action/Dockerfile
Normal file
5
github-action/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM mikefarah/yq:3
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
4
github-action/entrypoint.sh
Executable file
4
github-action/entrypoint.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh -l
|
||||
|
||||
echo "$1"
|
||||
eval $1
|
||||
19
go.mod
Normal file
19
go.mod
Normal file
@@ -0,0 +1,19 @@
|
||||
module github.com/mikefarah/yq/v4
|
||||
|
||||
require (
|
||||
github.com/elliotchance/orderedmap v1.3.0
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/goccy/go-yaml v1.8.1
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/mattn/go-colorable v0.1.7 // indirect
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/timtadh/data-structures v0.5.3 // indirect
|
||||
github.com/timtadh/lexmachine v0.2.2
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
)
|
||||
|
||||
go 1.15
|
||||
188
go.sum
Normal file
188
go.sum
Normal file
@@ -0,0 +1,188 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0=
|
||||
github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-yaml v1.8.1 h1:JuZRFlqLM5cWF6A+waL8AKVuCcqvKOuhJtUQI+L3ez0=
|
||||
github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ=
|
||||
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
|
||||
github.com/timtadh/lexmachine v0.2.2 h1:g55RnjdYazm5wnKv59pwFcBJHOyvTPfDEoz21s4PHmY=
|
||||
github.com/timtadh/lexmachine v0.2.2/go.mod h1:GBJvD5OAfRn/gnp92zb9KTgHLB7akKyxmVivoYCcjQI=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
7
mkdocs.yml
Normal file
7
mkdocs.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
docs_dir: mkdocs
|
||||
site_dir: docs
|
||||
site_name: Yq
|
||||
theme: 'material'
|
||||
repo_name: 'mikefarah/yq'
|
||||
repo_url: 'https://github.com/mikefarah/yq'
|
||||
|
||||
101
pkg/yqlib/candidate_node.go
Normal file
101
pkg/yqlib/candidate_node.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type CandidateNode struct {
|
||||
Node *yaml.Node // the actual node
|
||||
Path []interface{} /// the path we took to get to this node
|
||||
Document uint // the document index of this node
|
||||
Filename string
|
||||
}
|
||||
|
||||
func (n *CandidateNode) GetKey() string {
|
||||
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
||||
}
|
||||
|
||||
func (n *CandidateNode) Copy() (*CandidateNode, error) {
|
||||
clone := &CandidateNode{}
|
||||
err := copier.Copy(clone, n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clone, nil
|
||||
}
|
||||
|
||||
// updates this candidate from the given candidate node
|
||||
func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
|
||||
n.UpdateAttributesFrom(other)
|
||||
n.Node.Content = other.Node.Content
|
||||
n.Node.Value = other.Node.Value
|
||||
n.Node.Alias = other.Node.Alias
|
||||
}
|
||||
|
||||
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
||||
if n.Node.Kind != other.Node.Kind {
|
||||
// clear out the contents when switching to a different type
|
||||
// e.g. map to array
|
||||
n.Node.Content = make([]*yaml.Node, 0)
|
||||
n.Node.Value = ""
|
||||
}
|
||||
n.Node.Kind = other.Node.Kind
|
||||
n.Node.Tag = other.Node.Tag
|
||||
|
||||
// merge will pickup the style of the new thing
|
||||
// when autocreating nodes
|
||||
if n.Node.Style == 0 {
|
||||
n.Node.Style = other.Node.Style
|
||||
}
|
||||
n.Node.FootComment = n.Node.FootComment + other.Node.FootComment
|
||||
n.Node.HeadComment = n.Node.HeadComment + other.Node.HeadComment
|
||||
n.Node.LineComment = n.Node.LineComment + other.Node.LineComment
|
||||
}
|
||||
|
||||
func (n *CandidateNode) PathStackToString() string {
|
||||
return mergePathStackToString(n.Path)
|
||||
}
|
||||
|
||||
func mergePathStackToString(pathStack []interface{}) string {
|
||||
var sb strings.Builder
|
||||
for index, path := range pathStack {
|
||||
switch path.(type) {
|
||||
case int, int64:
|
||||
// if arrayMergeStrategy == AppendArrayMergeStrategy {
|
||||
// sb.WriteString("[+]")
|
||||
// } else {
|
||||
sb.WriteString(fmt.Sprintf("[%v]", path))
|
||||
// }
|
||||
|
||||
default:
|
||||
s := fmt.Sprintf("%v", path)
|
||||
var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint
|
||||
|
||||
hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"")
|
||||
hasDoubleQuotes := strings.Contains(s, "\"")
|
||||
wrappingCharacterStart := "\""
|
||||
wrappingCharacterEnd := "\""
|
||||
if hasDoubleQuotes {
|
||||
wrappingCharacterStart = "("
|
||||
wrappingCharacterEnd = ")"
|
||||
}
|
||||
if hasSpecial || errParsingInt == nil {
|
||||
sb.WriteString(wrappingCharacterStart)
|
||||
}
|
||||
sb.WriteString(s)
|
||||
if hasSpecial || errParsingInt == nil {
|
||||
sb.WriteString(wrappingCharacterEnd)
|
||||
}
|
||||
}
|
||||
|
||||
if index < len(pathStack)-1 {
|
||||
sb.WriteString(".")
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
61
pkg/yqlib/color_print.go
Normal file
61
pkg/yqlib/color_print.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/goccy/go-yaml/lexer"
|
||||
"github.com/goccy/go-yaml/printer"
|
||||
)
|
||||
|
||||
// Thanks @risentveber!
|
||||
|
||||
const escape = "\x1b"
|
||||
|
||||
func format(attr color.Attribute) string {
|
||||
return fmt.Sprintf("%s[%dm", escape, attr)
|
||||
}
|
||||
|
||||
func ColorizeAndPrint(bytes []byte, writer io.Writer) error {
|
||||
tokens := lexer.Tokenize(string(bytes))
|
||||
var p printer.Printer
|
||||
p.Bool = func() *printer.Property {
|
||||
return &printer.Property{
|
||||
Prefix: format(color.FgHiMagenta),
|
||||
Suffix: format(color.Reset),
|
||||
}
|
||||
}
|
||||
p.Number = func() *printer.Property {
|
||||
return &printer.Property{
|
||||
Prefix: format(color.FgHiMagenta),
|
||||
Suffix: format(color.Reset),
|
||||
}
|
||||
}
|
||||
p.MapKey = func() *printer.Property {
|
||||
return &printer.Property{
|
||||
Prefix: format(color.FgCyan),
|
||||
Suffix: format(color.Reset),
|
||||
}
|
||||
}
|
||||
p.Anchor = func() *printer.Property {
|
||||
return &printer.Property{
|
||||
Prefix: format(color.FgHiYellow),
|
||||
Suffix: format(color.Reset),
|
||||
}
|
||||
}
|
||||
p.Alias = func() *printer.Property {
|
||||
return &printer.Property{
|
||||
Prefix: format(color.FgHiYellow),
|
||||
Suffix: format(color.Reset),
|
||||
}
|
||||
}
|
||||
p.String = func() *printer.Property {
|
||||
return &printer.Property{
|
||||
Prefix: format(color.FgGreen),
|
||||
Suffix: format(color.Reset),
|
||||
}
|
||||
}
|
||||
_, err := writer.Write([]byte(p.PrintTokens(tokens) + "\n"))
|
||||
return err
|
||||
}
|
||||
5
pkg/yqlib/constants.go
Normal file
5
pkg/yqlib/constants.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package yqlib
|
||||
|
||||
import "gopkg.in/op/go-logging.v1"
|
||||
|
||||
var log = logging.MustGetLogger("yq-lib")
|
||||
48
pkg/yqlib/data_tree_navigator.go
Normal file
48
pkg/yqlib/data_tree_navigator.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
type dataTreeNavigator struct {
|
||||
navigationPrefs NavigationPrefs
|
||||
}
|
||||
|
||||
type NavigationPrefs struct {
|
||||
FollowAlias bool
|
||||
}
|
||||
|
||||
type DataTreeNavigator interface {
|
||||
// given a list of CandidateEntities and a pathNode,
|
||||
// this will process the list against the given pathNode and return
|
||||
// a new list of matching candidates
|
||||
GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
|
||||
}
|
||||
|
||||
func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator {
|
||||
return &dataTreeNavigator{navigationPrefs}
|
||||
}
|
||||
|
||||
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
if pathNode == nil {
|
||||
log.Debugf("getMatchingNodes - nothing to do")
|
||||
return matchingNodes, nil
|
||||
}
|
||||
log.Debugf("Processing Op: %v", pathNode.Operation.toString())
|
||||
if log.IsEnabledFor(logging.DEBUG) {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
log.Debug(NodeToString(el.Value.(*CandidateNode)))
|
||||
}
|
||||
}
|
||||
log.Debug(">>")
|
||||
handler := pathNode.Operation.OperationType.Handler
|
||||
if handler != nil {
|
||||
return handler(d, matchingNodes, pathNode)
|
||||
}
|
||||
return nil, fmt.Errorf("Unknown operator %v", pathNode.Operation.OperationType)
|
||||
|
||||
}
|
||||
664
pkg/yqlib/data_tree_navigator_test.go
Normal file
664
pkg/yqlib/data_tree_navigator_test.go
Normal file
@@ -0,0 +1,664 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
func resultsToString(results *list.List) []string {
|
||||
var pretty []string = make([]string, 0)
|
||||
for el := results.Front(); el != nil; el = el.Next() {
|
||||
n := el.Value.(*CandidateNode)
|
||||
pretty = append(pretty, NodeToString(n))
|
||||
}
|
||||
return pretty
|
||||
}
|
||||
|
||||
// func TestDataTreeNavigatorDeleteSimple(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// b: apple
|
||||
// c: camel`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("a .- b")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a]
|
||||
// Tag: !!map, Kind: MappingNode, Anchor:
|
||||
// c: camel
|
||||
// `
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorDeleteTwice(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// b: apple
|
||||
// c: camel
|
||||
// d: dingo`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("a .- b OR a .- c")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a]
|
||||
// Tag: !!map, Kind: MappingNode, Anchor:
|
||||
// d: dingo
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorDeleteWithUnion(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// b: apple
|
||||
// c: camel
|
||||
// d: dingo`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("a .- (b OR c)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a]
|
||||
// Tag: !!map, Kind: MappingNode, Anchor:
|
||||
// d: dingo
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorDeleteByIndex(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// - b: apple
|
||||
// - b: sdfsd
|
||||
// - b: apple`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("(a .- (0 or 1))")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a]
|
||||
// Tag: !!seq, Kind: SequenceNode, Anchor:
|
||||
// - b: apple
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorDeleteByFind(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// - b: apple
|
||||
// - b: sdfsd
|
||||
// - b: apple`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("(a .- (* == apple))")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a]
|
||||
// Tag: !!seq, Kind: SequenceNode, Anchor:
|
||||
// - b: sdfsd
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorDeleteArrayByFind(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// - apple
|
||||
// - sdfsd
|
||||
// - apple`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("(a .- (. == apple))")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a]
|
||||
// Tag: !!seq, Kind: SequenceNode, Anchor:
|
||||
// - sdfsd
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorDeleteViaSelf(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `- apple
|
||||
// - sdfsd
|
||||
// - apple`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath(". .- (. == apple)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: []
|
||||
// Tag: !!seq, Kind: SequenceNode, Anchor:
|
||||
// - sdfsd
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorFilterWithSplat(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `f:
|
||||
// a: frog
|
||||
// b: dally
|
||||
// c: log`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath(".f | .[] == \"frog\"")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [f]
|
||||
// Tag: !!int, Kind: ScalarNode, Anchor:
|
||||
// 2
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCountAndCollectWithFilterCmd(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `f:
|
||||
// a: frog
|
||||
// b: dally
|
||||
// c: log`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath(".f | .[] == *og ")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [f]
|
||||
// Tag: !!int, Kind: ScalarNode, Anchor:
|
||||
// 2
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCollectWithFilter(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `f:
|
||||
// a: frog
|
||||
// b: dally
|
||||
// c: log`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("f(collect(. == *og))")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [f]
|
||||
// Tag: , Kind: SequenceNode, Anchor:
|
||||
// - frog
|
||||
// - log
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCountWithFilter2(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `f:
|
||||
// a: frog
|
||||
// b: dally
|
||||
// c: log`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("count(f(. == *og))")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: []
|
||||
// Tag: !!int, Kind: ScalarNode, Anchor:
|
||||
// 2
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCollectWithFilter2(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `f:
|
||||
// a: frog
|
||||
// b: dally
|
||||
// c: log`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("collect(f(. == *og))")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: []
|
||||
// Tag: , Kind: SequenceNode, Anchor:
|
||||
// - frog
|
||||
// - log
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCountMultipleMatchesInside(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `f:
|
||||
// a: [1,2]
|
||||
// b: dally
|
||||
// c: [3,4,5]`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("f | count(a or c)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [f]
|
||||
// Tag: !!int, Kind: ScalarNode, Anchor:
|
||||
// 2
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCollectMultipleMatchesInside(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `f:
|
||||
// a: [1,2]
|
||||
// b: dally
|
||||
// c: [3,4,5]`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("f | collect(a or c)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [f]
|
||||
// Tag: , Kind: SequenceNode, Anchor:
|
||||
// - [1, 2]
|
||||
// - [3, 4, 5]
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCountMultipleMatchesInsideSplat(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `f:
|
||||
// a: [1,2,3]
|
||||
// b: [1,2,3,4]
|
||||
// c: [1,2,3,4,5]`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("f(count( (a or c)*))")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [f]
|
||||
// Tag: !!int, Kind: ScalarNode, Anchor:
|
||||
// 8
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCountMultipleMatchesOutside(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `f:
|
||||
// a: [1,2,3]
|
||||
// b: [1,2,3,4]
|
||||
// c: [1,2,3,4,5]`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("f(a or c)(count(*))")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [f a]
|
||||
// Tag: !!int, Kind: ScalarNode, Anchor:
|
||||
// 3
|
||||
// -- Node --
|
||||
// Document 0, path: [f c]
|
||||
// Tag: !!int, Kind: ScalarNode, Anchor:
|
||||
// 5
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCountOfResults(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `- apple
|
||||
// - sdfsd
|
||||
// - apple`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("count(*)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: []
|
||||
// Tag: !!int, Kind: ScalarNode, Anchor:
|
||||
// 3
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorCountNoMatches(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `- apple
|
||||
// - sdfsd
|
||||
// - apple`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("count(5)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: []
|
||||
// Tag: !!int, Kind: ScalarNode, Anchor:
|
||||
// 0
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorDeleteAndWrite(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// - b: apple
|
||||
// - b: sdfsd
|
||||
// - { b: apple, c: cat }`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("(a .- (0 or 1)) or (a[0].b := frog)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a]
|
||||
// Tag: !!seq, Kind: SequenceNode, Anchor:
|
||||
// - {b: frog, c: cat}
|
||||
|
||||
// -- Node --
|
||||
// Document 0, path: [a 0 b]
|
||||
// Tag: !!str, Kind: ScalarNode, Anchor:
|
||||
// frog
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorDeleteArray(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// - b: apple
|
||||
// - b: sdfsd
|
||||
// - b: apple`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("a .- (b == a*)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a]
|
||||
// Tag: !!seq, Kind: SequenceNode, Anchor:
|
||||
// - b: sdfsd
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorArraySimple(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `- b: apple`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("[0]")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [0]
|
||||
// Tag: !!map, Kind: MappingNode, Anchor:
|
||||
// b: apple
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorSimpleAssignByFind(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// b: apple`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("a(. == apple) := frog")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a b]
|
||||
// Tag: !!str, Kind: ScalarNode, Anchor:
|
||||
// frog
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorOrDeDupes(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// cat: apple
|
||||
// mad: things`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("a.(cat or cat)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a cat]
|
||||
// Tag: !!str, Kind: ScalarNode, Anchor:
|
||||
// apple
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
|
||||
// func TestDataTreeNavigatorAnd(t *testing.T) {
|
||||
|
||||
// nodes := readDoc(t, `a:
|
||||
// cat: apple
|
||||
// pat: apple
|
||||
// cow: apple
|
||||
// mad: things`)
|
||||
|
||||
// path, errPath := treeCreator.ParsePath("a.(*t and c*)")
|
||||
// if errPath != nil {
|
||||
// t.Error(errPath)
|
||||
// }
|
||||
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
|
||||
|
||||
// if errNav != nil {
|
||||
// t.Error(errNav)
|
||||
// }
|
||||
|
||||
// expected := `
|
||||
// -- Node --
|
||||
// Document 0, path: [a cat]
|
||||
// Tag: !!str, Kind: ScalarNode, Anchor:
|
||||
// apple
|
||||
// `
|
||||
|
||||
// test.AssertResult(t, expected, resultsToString(results))
|
||||
// }
|
||||
1
pkg/yqlib/doc/.gitignore
vendored
Normal file
1
pkg/yqlib/doc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.md
|
||||
20
pkg/yqlib/document_index_operator.go
Normal file
20
pkg/yqlib/document_index_operator.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func GetDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"}
|
||||
scalar := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(scalar)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
41
pkg/yqlib/document_index_operator_test.go
Normal file
41
pkg/yqlib/document_index_operator_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var documentIndexScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Retrieve a document index",
|
||||
document: "a: cat\n---\na: frog\n",
|
||||
expression: `.a | documentIndex`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!int)::0\n",
|
||||
"D1, P[a], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Filter by document index",
|
||||
document: "a: cat\n---\na: frog\n",
|
||||
expression: `select(. | documentIndex == 1)`,
|
||||
expected: []string{
|
||||
"D1, P[], (doc)::a: frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Print Document Index with matches",
|
||||
document: "a: cat\n---\na: frog\n",
|
||||
expression: `.a | ({"match": ., "doc": (. | documentIndex)})`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::match: cat\ndoc: 0\n",
|
||||
"D0, P[], (!!map)::match: frog\ndoc: 1\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDocumentIndexScenarios(t *testing.T) {
|
||||
for _, tt := range documentIndexScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Document Index Operator", documentIndexScenarios)
|
||||
}
|
||||
247
pkg/yqlib/encoder.go
Normal file
247
pkg/yqlib/encoder.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Encoder interface {
|
||||
Encode(node *yaml.Node) error
|
||||
}
|
||||
|
||||
type yamlEncoder struct {
|
||||
destination io.Writer
|
||||
indent int
|
||||
colorise bool
|
||||
firstDoc bool
|
||||
}
|
||||
|
||||
func NewYamlEncoder(destination io.Writer, indent int, colorise bool) Encoder {
|
||||
if indent < 0 {
|
||||
indent = 0
|
||||
}
|
||||
return &yamlEncoder{destination, indent, colorise, true}
|
||||
}
|
||||
|
||||
func (ye *yamlEncoder) Encode(node *yaml.Node) error {
|
||||
|
||||
destination := ye.destination
|
||||
tempBuffer := bytes.NewBuffer(nil)
|
||||
if ye.colorise {
|
||||
destination = tempBuffer
|
||||
}
|
||||
|
||||
var encoder = yaml.NewEncoder(destination)
|
||||
|
||||
encoder.SetIndent(ye.indent)
|
||||
// TODO: work out if the first doc had a separator or not.
|
||||
if ye.firstDoc {
|
||||
ye.firstDoc = false
|
||||
} else if _, err := destination.Write([]byte("---\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := encoder.Encode(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ye.colorise {
|
||||
return ColorizeAndPrint(tempBuffer.Bytes(), ye.destination)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type jsonEncoder struct {
|
||||
encoder *json.Encoder
|
||||
}
|
||||
|
||||
func mapKeysToStrings(node *yaml.Node) {
|
||||
|
||||
if node.Kind == yaml.MappingNode {
|
||||
for index, child := range node.Content {
|
||||
if index%2 == 0 { // its a map key
|
||||
child.Tag = "!!str"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, child := range node.Content {
|
||||
mapKeysToStrings(child)
|
||||
}
|
||||
}
|
||||
|
||||
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
|
||||
var encoder = json.NewEncoder(destination)
|
||||
var indentString = ""
|
||||
|
||||
for index := 0; index < indent; index++ {
|
||||
indentString = indentString + " "
|
||||
}
|
||||
encoder.SetIndent("", indentString)
|
||||
return &jsonEncoder{encoder}
|
||||
}
|
||||
|
||||
func (je *jsonEncoder) Encode(node *yaml.Node) error {
|
||||
var dataBucket orderedMap
|
||||
// firstly, convert all map keys to strings
|
||||
mapKeysToStrings(node)
|
||||
errorDecoding := node.Decode(&dataBucket)
|
||||
if errorDecoding != nil {
|
||||
return errorDecoding
|
||||
}
|
||||
return je.encoder.Encode(dataBucket)
|
||||
}
|
||||
|
||||
// orderedMap allows to marshal and unmarshal JSON and YAML values keeping the
|
||||
// order of keys and values in a map or an object.
|
||||
type orderedMap struct {
|
||||
// if this is an object, kv != nil. If this is not an object, kv == nil.
|
||||
kv []orderedMapKV
|
||||
altVal interface{}
|
||||
}
|
||||
|
||||
type orderedMapKV struct {
|
||||
K string
|
||||
V orderedMap
|
||||
}
|
||||
|
||||
func (o *orderedMap) UnmarshalJSON(data []byte) error {
|
||||
switch data[0] {
|
||||
case '{':
|
||||
// initialise so that even if the object is empty it is not nil
|
||||
o.kv = []orderedMapKV{}
|
||||
|
||||
// create decoder
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
_, err := dec.Token() // open object
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// cycle through k/v
|
||||
var tok json.Token
|
||||
for tok, err = dec.Token(); err != io.EOF; tok, err = dec.Token() {
|
||||
// we can expect two types: string or Delim. Delim automatically means
|
||||
// that it is the closing bracket of the object, whereas string means
|
||||
// that there is another key.
|
||||
if _, ok := tok.(json.Delim); ok {
|
||||
break
|
||||
}
|
||||
kv := orderedMapKV{
|
||||
K: tok.(string),
|
||||
}
|
||||
if err := dec.Decode(&kv.V); err != nil {
|
||||
return err
|
||||
}
|
||||
o.kv = append(o.kv, kv)
|
||||
}
|
||||
// unexpected error
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case '[':
|
||||
var arr []orderedMap
|
||||
return json.Unmarshal(data, &arr)
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, &o.altVal)
|
||||
}
|
||||
|
||||
func (o orderedMap) MarshalJSON() ([]byte, error) {
|
||||
if o.kv == nil {
|
||||
return json.Marshal(o.altVal)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
buf.WriteByte('{')
|
||||
for idx, el := range o.kv {
|
||||
if err := enc.Encode(el.K); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.WriteByte(':')
|
||||
if err := enc.Encode(el.V); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if idx != len(o.kv)-1 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (o *orderedMap) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
case yaml.DocumentNode:
|
||||
if len(node.Content) == 0 {
|
||||
return nil
|
||||
}
|
||||
return o.UnmarshalYAML(node.Content[0])
|
||||
case yaml.AliasNode:
|
||||
return o.UnmarshalYAML(node.Alias)
|
||||
case yaml.ScalarNode:
|
||||
return node.Decode(&o.altVal)
|
||||
case yaml.MappingNode:
|
||||
// set kv to non-nil
|
||||
o.kv = []orderedMapKV{}
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
var key string
|
||||
var val orderedMap
|
||||
if err := node.Content[i].Decode(&key); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := node.Content[i+1].Decode(&val); err != nil {
|
||||
return err
|
||||
}
|
||||
o.kv = append(o.kv, orderedMapKV{
|
||||
K: key,
|
||||
V: val,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
case yaml.SequenceNode:
|
||||
var res []orderedMap
|
||||
if err := node.Decode(&res); err != nil {
|
||||
return err
|
||||
}
|
||||
o.altVal = res
|
||||
o.kv = nil
|
||||
return nil
|
||||
case 0:
|
||||
// null
|
||||
o.kv = nil
|
||||
o.altVal = nil
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("orderedMap: invalid yaml node")
|
||||
}
|
||||
}
|
||||
|
||||
func (o *orderedMap) MarshalYAML() (interface{}, error) {
|
||||
// fast path: kv is nil, use altVal
|
||||
if o.kv == nil {
|
||||
return o.altVal, nil
|
||||
}
|
||||
content := make([]*yaml.Node, 0, len(o.kv)*2)
|
||||
for _, val := range o.kv {
|
||||
n := new(yaml.Node)
|
||||
if err := n.Encode(val.V); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content = append(content, &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: val.K,
|
||||
}, n)
|
||||
}
|
||||
return &yaml.Node{
|
||||
Kind: yaml.MappingNode,
|
||||
Tag: "!!map",
|
||||
Content: content,
|
||||
}, nil
|
||||
}
|
||||
178
pkg/yqlib/lib.go
Normal file
178
pkg/yqlib/lib.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type OperationType struct {
|
||||
Type string
|
||||
NumArgs uint // number of arguments to the op
|
||||
Precedence uint
|
||||
Handler OperatorHandler
|
||||
}
|
||||
|
||||
// operators TODO:
|
||||
// - generator doc from operator tests
|
||||
// - slurp - stdin, read in sequence, vs read all
|
||||
// - write in place
|
||||
// - get path operator (like doc index)
|
||||
// - get file index op (like doc index)
|
||||
// - get file name op (like doc index)
|
||||
// - mergeAppend (merges and appends arrays)
|
||||
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
||||
// - updateTag - not recursive
|
||||
// - get tag (tag)
|
||||
// - compare ??
|
||||
// - validate ??
|
||||
// - exists
|
||||
|
||||
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
||||
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
||||
|
||||
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
|
||||
|
||||
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
|
||||
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
|
||||
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
|
||||
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
|
||||
|
||||
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator}
|
||||
|
||||
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
||||
var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
|
||||
|
||||
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
|
||||
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
|
||||
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
|
||||
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
|
||||
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
|
||||
|
||||
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
||||
|
||||
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
||||
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||
|
||||
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
|
||||
var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator}
|
||||
var Not = &OperationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: NotOperator}
|
||||
var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
|
||||
|
||||
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
|
||||
|
||||
// not sure yet
|
||||
|
||||
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
|
||||
|
||||
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Handler: DeleteChildOperator}
|
||||
|
||||
// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35}
|
||||
// filters matches if they have the existing path
|
||||
|
||||
type Operation struct {
|
||||
OperationType *OperationType
|
||||
Value interface{}
|
||||
StringValue string
|
||||
CandidateNode *CandidateNode // used for Value Path elements
|
||||
Preferences interface{}
|
||||
}
|
||||
|
||||
func CreateValueOperation(value interface{}, stringValue string) *Operation {
|
||||
var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
|
||||
node.Value = stringValue
|
||||
|
||||
switch value.(type) {
|
||||
case float32, float64:
|
||||
node.Tag = "!!float"
|
||||
case int, int64, int32:
|
||||
node.Tag = "!!int"
|
||||
case bool:
|
||||
node.Tag = "!!bool"
|
||||
case string:
|
||||
node.Tag = "!!str"
|
||||
case nil:
|
||||
node.Tag = "!!null"
|
||||
}
|
||||
|
||||
return &Operation{
|
||||
OperationType: ValueOp,
|
||||
Value: value,
|
||||
StringValue: stringValue,
|
||||
CandidateNode: &CandidateNode{Node: &node},
|
||||
}
|
||||
}
|
||||
|
||||
// debugging purposes only
|
||||
func (p *Operation) toString() string {
|
||||
if p.OperationType == TraversePath {
|
||||
return fmt.Sprintf("%v", p.Value)
|
||||
} else if p.OperationType == DocumentFilter {
|
||||
return fmt.Sprintf("d%v", p.Value)
|
||||
} else if p.OperationType == SelfReference {
|
||||
return "SELF"
|
||||
} else if p.OperationType == ValueOp {
|
||||
return fmt.Sprintf("%v (%T)", p.Value, p.Value)
|
||||
} else {
|
||||
return fmt.Sprintf("%v", p.OperationType.Type)
|
||||
}
|
||||
}
|
||||
|
||||
//use for debugging only
|
||||
func NodesToString(collection *list.List) string {
|
||||
if !log.IsEnabledFor(logging.DEBUG) {
|
||||
return ""
|
||||
}
|
||||
|
||||
result := ""
|
||||
for el := collection.Front(); el != nil; el = el.Next() {
|
||||
result = result + "\n" + NodeToString(el.Value.(*CandidateNode))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func NodeToString(node *CandidateNode) string {
|
||||
if !log.IsEnabledFor(logging.DEBUG) {
|
||||
return ""
|
||||
}
|
||||
value := node.Node
|
||||
if value == nil {
|
||||
return "-- nil --"
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
encoder := yaml.NewEncoder(buf)
|
||||
errorEncoding := encoder.Encode(value)
|
||||
if errorEncoding != nil {
|
||||
log.Error("Error debugging node, %v", errorEncoding.Error())
|
||||
}
|
||||
encoder.Close()
|
||||
tag := value.Tag
|
||||
if value.Kind == yaml.DocumentNode {
|
||||
tag = "doc"
|
||||
} else if value.Kind == yaml.AliasNode {
|
||||
tag = "alias"
|
||||
}
|
||||
return fmt.Sprintf(`D%v, P%v, (%v)::%v`, node.Document, node.Path, tag, buf.String())
|
||||
}
|
||||
|
||||
func KindString(kind yaml.Kind) string {
|
||||
switch kind {
|
||||
case yaml.ScalarNode:
|
||||
return "ScalarNode"
|
||||
case yaml.SequenceNode:
|
||||
return "SequenceNode"
|
||||
case yaml.MappingNode:
|
||||
return "MappingNode"
|
||||
case yaml.DocumentNode:
|
||||
return "DocumentNode"
|
||||
case yaml.AliasNode:
|
||||
return "AliasNode"
|
||||
default:
|
||||
return "unknown!"
|
||||
}
|
||||
}
|
||||
34
pkg/yqlib/matchKeyString.go
Normal file
34
pkg/yqlib/matchKeyString.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package yqlib
|
||||
|
||||
func Match(name string, pattern string) (matched bool) {
|
||||
if pattern == "" {
|
||||
return name == pattern
|
||||
}
|
||||
log.Debug("pattern: %v", pattern)
|
||||
if pattern == "*" {
|
||||
log.Debug("wild!")
|
||||
return true
|
||||
}
|
||||
return deepMatch([]rune(name), []rune(pattern))
|
||||
}
|
||||
|
||||
func deepMatch(str, pattern []rune) bool {
|
||||
for len(pattern) > 0 {
|
||||
switch pattern[0] {
|
||||
default:
|
||||
if len(str) == 0 || str[0] != pattern[0] {
|
||||
return false
|
||||
}
|
||||
case '?':
|
||||
if len(str) == 0 {
|
||||
return false
|
||||
}
|
||||
case '*':
|
||||
return deepMatch(str, pattern[1:]) ||
|
||||
(len(str) > 0 && deepMatch(str[1:], pattern))
|
||||
}
|
||||
str = str[1:]
|
||||
pattern = pattern[1:]
|
||||
}
|
||||
return len(str) == 0 && len(pattern) == 0
|
||||
}
|
||||
52
pkg/yqlib/operator_assign_update.go
Normal file
52
pkg/yqlib/operator_assign_update.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// grab the first value
|
||||
first := rhs.Front()
|
||||
|
||||
if first != nil {
|
||||
candidate.UpdateFrom(first.Value.(*CandidateNode))
|
||||
}
|
||||
}
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
// does not update content or values
|
||||
func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// grab the first value
|
||||
first := rhs.Front()
|
||||
|
||||
if first != nil {
|
||||
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode))
|
||||
}
|
||||
}
|
||||
return matchingNodes, nil
|
||||
}
|
||||
84
pkg/yqlib/operator_assign_update_test.go
Normal file
84
pkg/yqlib/operator_assign_update_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `.a.b |= "frog"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: frog}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `.a.b | (. |= "frog")`,
|
||||
expected: []string{
|
||||
"D0, P[a b], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `.a.b |= 5`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: 5}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `.a.b |= 3.142`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: 3.142}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {b: {g: foof}}}`,
|
||||
expression: `.a |= .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {g: foof}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {b: apple, c: cactus}}`,
|
||||
expression: `.a[] | select(. == "apple") |= "frog"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `[candy, apple, sandy]`,
|
||||
expression: `.[] | select(. == "*andy") |= "bogs"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::[bogs, apple, bogs]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `.a.b |= "bogs"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: bogs}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `.a.b[0] |= "bogs"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: [bogs]}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `.a.b[1].c |= "bogs"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAssignOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range assignOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
||||
77
pkg/yqlib/operator_booleans.go
Normal file
77
pkg/yqlib/operator_booleans.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func isTruthy(c *CandidateNode) (bool, error) {
|
||||
node := UnwrapDoc(c.Node)
|
||||
value := true
|
||||
|
||||
if node.Tag == "!!null" {
|
||||
return false, nil
|
||||
}
|
||||
if node.Kind == yaml.ScalarNode && node.Tag == "!!bool" {
|
||||
errDecoding := node.Decode(&value)
|
||||
if errDecoding != nil {
|
||||
return false, errDecoding
|
||||
}
|
||||
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
type boolOp func(bool, bool) bool
|
||||
|
||||
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
|
||||
lhsCandidate := lhsChild.Value.(*CandidateNode)
|
||||
lhsTrue, errDecoding := isTruthy(lhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
|
||||
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
|
||||
rhsCandidate := rhsChild.Value.(*CandidateNode)
|
||||
rhsTrue, errDecoding := isTruthy(rhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
|
||||
|
||||
results.PushBack(boolResult)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func OrOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- orOp")
|
||||
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
||||
return b1 || b2
|
||||
})
|
||||
}
|
||||
|
||||
func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- AndOp")
|
||||
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
||||
return b1 && b2
|
||||
})
|
||||
}
|
||||
36
pkg/yqlib/operator_booleans_test.go
Normal file
36
pkg/yqlib/operator_booleans_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var booleanOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `true or false`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
}, {
|
||||
document: `{}`,
|
||||
expression: `false or false`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
}, {
|
||||
document: `{a: true, b: false}`,
|
||||
expression: `.[] or (false, true)`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
"D0, P[b], (!!bool)::false\n",
|
||||
"D0, P[b], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestBooleanOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range booleanOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
||||
33
pkg/yqlib/operator_collect.go
Normal file
33
pkg/yqlib/operator_collect.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- collectOperation")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
|
||||
var document uint = 0
|
||||
var path []interface{}
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Collecting %v", NodeToString(candidate))
|
||||
if path == nil && candidate.Path != nil && len(candidate.Path) > 1 {
|
||||
path = candidate.Path[:len(candidate.Path)-1]
|
||||
document = candidate.Document
|
||||
}
|
||||
node.Content = append(node.Content, candidate.Node)
|
||||
}
|
||||
|
||||
collectC := &CandidateNode{Node: node, Document: document, Path: path}
|
||||
results.PushBack(collectC)
|
||||
|
||||
return results, nil
|
||||
}
|
||||
102
pkg/yqlib/operator_collect_object.go
Normal file
102
pkg/yqlib/operator_collect_object.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
/*
|
||||
[Mike: cat, Bob: dog]
|
||||
[Thing: rabbit, peter: sam]
|
||||
|
||||
==> cross multiply
|
||||
|
||||
{Mike: cat, Thing: rabbit}
|
||||
{Mike: cat, peter: sam}
|
||||
...
|
||||
*/
|
||||
|
||||
func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- collectObjectOperation")
|
||||
|
||||
if matchMap.Len() == 0 {
|
||||
return list.New(), nil
|
||||
}
|
||||
first := matchMap.Front().Value.(*CandidateNode)
|
||||
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
|
||||
|
||||
for i := 0; i < len(first.Node.Content); i++ {
|
||||
rotated[i] = list.New()
|
||||
}
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidateNode := el.Value.(*CandidateNode)
|
||||
for i := 0; i < len(first.Node.Content); i++ {
|
||||
rotated[i].PushBack(createChildCandidate(candidateNode, i))
|
||||
}
|
||||
}
|
||||
|
||||
newObject := list.New()
|
||||
for i := 0; i < len(first.Node.Content); i++ {
|
||||
additions, err := collect(d, list.New(), rotated[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newObject.PushBackList(additions)
|
||||
}
|
||||
|
||||
return newObject, nil
|
||||
|
||||
}
|
||||
|
||||
func createChildCandidate(candidate *CandidateNode, index int) *CandidateNode {
|
||||
return &CandidateNode{
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
Filename: candidate.Filename,
|
||||
Node: candidate.Node.Content[index],
|
||||
}
|
||||
}
|
||||
|
||||
func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.List) (*list.List, error) {
|
||||
if remainingMatches.Len() == 0 {
|
||||
return aggregate, nil
|
||||
}
|
||||
|
||||
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
|
||||
splatted, err := Splat(d, nodeToMap(candidate))
|
||||
|
||||
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||
splatEl.Value.(*CandidateNode).Path = nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if aggregate.Len() == 0 {
|
||||
return collect(d, splatted, remainingMatches)
|
||||
}
|
||||
|
||||
newAgg := list.New()
|
||||
|
||||
for el := aggregate.Front(); el != nil; el = el.Next() {
|
||||
aggCandidate := el.Value.(*CandidateNode)
|
||||
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||
splatCandidate := splatEl.Value.(*CandidateNode)
|
||||
newCandidate, err := aggCandidate.Copy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newCandidate.Path = nil
|
||||
|
||||
newCandidate, err = multiply(d, newCandidate, splatCandidate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newAgg.PushBack(newCandidate)
|
||||
}
|
||||
}
|
||||
return collect(d, newAgg, remainingMatches)
|
||||
|
||||
}
|
||||
100
pkg/yqlib/operator_collect_object_test.go
Normal file
100
pkg/yqlib/operator_collect_object_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var collectObjectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: ``,
|
||||
expression: `{}`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
document: "{name: Mike}\n",
|
||||
expression: `{"wrap": .}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::wrap: {name: Mike}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: "{name: Mike}\n---\n{name: Bob}",
|
||||
expression: `{"wrap": .}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::wrap: {name: Mike}\n",
|
||||
"D0, P[], (!!map)::wrap: {name: Bob}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, age: 32}`,
|
||||
expression: `{.name: .age}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::Mike: 32\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: [cat, dog]}`,
|
||||
expression: `{.name: .pets[]}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::Mike: cat\n",
|
||||
"D0, P[], (!!map)::Mike: dog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}",
|
||||
expression: `{.name: .pets[]}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::Mike: cat\n",
|
||||
"D0, P[], (!!map)::Mike: dog\n",
|
||||
"D0, P[], (!!map)::Rosey: monkey\n",
|
||||
"D0, P[], (!!map)::Rosey: sheep\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
|
||||
expression: `{.name: .pets[], "f":.food[]}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::Mike: cat\nf: hotdog\n",
|
||||
"D0, P[], (!!map)::Mike: cat\nf: burger\n",
|
||||
"D0, P[], (!!map)::Mike: dog\nf: hotdog\n",
|
||||
"D0, P[], (!!map)::Mike: dog\nf: burger\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: {cows: [apl, bba]}}`,
|
||||
expression: `{"a":.name, "b":.pets}`,
|
||||
expected: []string{
|
||||
`D0, P[], (!!map)::a: Mike
|
||||
b: {cows: [apl, bba]}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
document: ``,
|
||||
expression: `{"wrap": "frog"}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::wrap: frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike}`,
|
||||
expression: `{"wrap": .}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::wrap: {name: Mike}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike}`,
|
||||
expression: `{"wrap": {"further": .}} | (.. style= "flow")`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{wrap: {further: {name: Mike}}}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestCollectObjectOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range collectObjectOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Collect into Object", collectObjectOperatorScenarios)
|
||||
}
|
||||
57
pkg/yqlib/operator_collect_test.go
Normal file
57
pkg/yqlib/operator_collect_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var collectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: ``,
|
||||
expression: `[]`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
document: ``,
|
||||
expression: `["cat"]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- cat\n",
|
||||
},
|
||||
}, {
|
||||
document: ``,
|
||||
expression: `[true]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- true\n",
|
||||
},
|
||||
}, {
|
||||
document: ``,
|
||||
expression: `["cat", "dog"]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- cat\n- dog\n",
|
||||
},
|
||||
}, {
|
||||
document: ``,
|
||||
expression: `1 | collect`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- 1\n",
|
||||
},
|
||||
}, {
|
||||
document: `[1,2,3]`,
|
||||
expression: `[.[]]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- 1\n- 2\n- 3\n",
|
||||
},
|
||||
}, {
|
||||
document: `a: {b: [1,2,3]}`,
|
||||
expression: `[.a.b[]]`,
|
||||
expected: []string{
|
||||
"D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestCollectOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range collectOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Collect into Array", collectOperatorScenarios)
|
||||
}
|
||||
76
pkg/yqlib/operator_comments.go
Normal file
76
pkg/yqlib/operator_comments.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type CommentOpPreferences struct {
|
||||
LineComment bool
|
||||
HeadComment bool
|
||||
FootComment bool
|
||||
}
|
||||
|
||||
func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("AssignComments operator!")
|
||||
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
comment := ""
|
||||
if rhs.Front() != nil {
|
||||
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
||||
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting comment of : %v", candidate.GetKey())
|
||||
if preferences.LineComment {
|
||||
candidate.Node.LineComment = comment
|
||||
}
|
||||
if preferences.HeadComment {
|
||||
candidate.Node.HeadComment = comment
|
||||
}
|
||||
if preferences.FootComment {
|
||||
candidate.Node.FootComment = comment
|
||||
}
|
||||
|
||||
}
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func GetCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
||||
log.Debugf("GetComments operator!")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
comment := ""
|
||||
if preferences.LineComment {
|
||||
comment = candidate.Node.LineComment
|
||||
} else if preferences.HeadComment {
|
||||
comment = candidate.Node.HeadComment
|
||||
} else if preferences.FootComment {
|
||||
comment = candidate.Node.FootComment
|
||||
}
|
||||
comment = strings.Replace(comment, "# ", "", 1)
|
||||
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
79
pkg/yqlib/operator_comments_test.go
Normal file
79
pkg/yqlib/operator_comments_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var commentOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Set line comment",
|
||||
document: `a: cat`,
|
||||
expression: `.a lineComment="single"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: cat # single\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set head comment",
|
||||
document: `a: cat`,
|
||||
expression: `. headComment="single"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::# single\n\na: cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set foot comment, using an expression",
|
||||
document: `a: cat`,
|
||||
expression: `. footComment=.a`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: cat\n\n# cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Remove comment",
|
||||
document: "a: cat # comment\nb: dog # leave this",
|
||||
expression: `.a lineComment=""`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: cat\nb: dog # leave this\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Remove all comments",
|
||||
document: "# hi\n\na: cat # comment\n\n# great\n",
|
||||
expression: `.. comments=""`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get line comment",
|
||||
document: "# welcome!\n\na: cat # meow\n\n# have a great day",
|
||||
expression: `.a | lineComment`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!str)::meow\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get head comment",
|
||||
document: "# welcome!\n\na: cat # meow\n\n# have a great day",
|
||||
expression: `. | headComment`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::welcome!\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get foot comment",
|
||||
document: "# welcome!\n\na: cat # meow\n\n# have a great day",
|
||||
expression: `. | footComment`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::have a great day\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestCommentOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range commentOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Comments Operator", commentOperatorScenarios)
|
||||
}
|
||||
85
pkg/yqlib/operator_create_map.go
Normal file
85
pkg/yqlib/operator_create_map.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- createMapOperation")
|
||||
|
||||
//each matchingNodes entry should turn into a sequence of keys to create.
|
||||
//then collect object should do a cross function of the same index sequence for all matches.
|
||||
|
||||
var path []interface{}
|
||||
|
||||
var document uint = 0
|
||||
|
||||
sequences := list.New()
|
||||
|
||||
if matchingNodes.Len() > 0 {
|
||||
|
||||
for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
|
||||
matchingNode := matchingNodeEl.Value.(*CandidateNode)
|
||||
sequenceNode, err := sequenceFor(d, matchingNode, pathNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequences.PushBack(sequenceNode)
|
||||
}
|
||||
} else {
|
||||
sequenceNode, err := sequenceFor(d, nil, pathNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequences.PushBack(sequenceNode)
|
||||
}
|
||||
|
||||
return nodeToMap(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
|
||||
|
||||
}
|
||||
|
||||
func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathTreeNode) (*CandidateNode, error) {
|
||||
var path []interface{}
|
||||
var document uint = 0
|
||||
var matches = list.New()
|
||||
|
||||
if matchingNode != nil {
|
||||
path = matchingNode.Path
|
||||
document = matchingNode.Document
|
||||
matches = nodeToMap(matchingNode)
|
||||
}
|
||||
|
||||
mapPairs, err := crossFunction(d, matches, pathNode,
|
||||
func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||
log.Debugf("LHS:", NodeToString(lhs))
|
||||
log.Debugf("RHS:", NodeToString(rhs))
|
||||
node.Content = []*yaml.Node{
|
||||
UnwrapDoc(lhs.Node),
|
||||
UnwrapDoc(rhs.Node),
|
||||
}
|
||||
|
||||
return &CandidateNode{Node: &node, Document: document, Path: path}, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
innerList := listToNodeSeq(mapPairs)
|
||||
innerList.Style = yaml.FlowStyle
|
||||
return &CandidateNode{Node: innerList, Document: document, Path: path}, nil
|
||||
}
|
||||
|
||||
//NOTE: here the document index gets dropped so we
|
||||
// no longer know where the node originates from.
|
||||
func listToNodeSeq(list *list.List) *yaml.Node {
|
||||
node := yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
for entry := list.Front(); entry != nil; entry = entry.Next() {
|
||||
entryCandidate := entry.Value.(*CandidateNode)
|
||||
log.Debugf("Collecting %v into sequence", NodeToString(entryCandidate))
|
||||
node.Content = append(node.Content, entryCandidate.Node)
|
||||
}
|
||||
return &node
|
||||
}
|
||||
81
pkg/yqlib/operator_create_map_test.go
Normal file
81
pkg/yqlib/operator_create_map_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var createMapOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: ``,
|
||||
expression: `"frog": "jumps"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{frog: jumps}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, age: 32}`,
|
||||
expression: `.name: .age`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: 32}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: [cat, dog]}`,
|
||||
expression: `.name: .pets[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
|
||||
expression: `.name: .pets[], "f":.food[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
||||
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: "{name: Mike, pets: [cat, dog], food: [hotdog, burger]}\n---\n{name: Fred, pets: [mouse], food: [pizza, onion, apple]}",
|
||||
expression: `.name: .pets[], "f":.food[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n- [{Fred: mouse}]\n",
|
||||
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n- [{f: pizza}, {f: onion}, {f: apple}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: {cows: [apl, bba]}}`,
|
||||
expression: `"a":.name, "b":.pets`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{a: Mike}]\n",
|
||||
"D0, P[], (!!seq)::- [{b: {cows: [apl, bba]}}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike}`,
|
||||
expression: `"wrap": .`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{wrap: {name: Mike}}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: "{name: Mike}\n---\n{name: Bob}",
|
||||
expression: `"wrap": .`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{wrap: {name: Mike}}]\n- [{wrap: {name: Bob}}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: "{name: Mike}\n---\n{name: Bob}",
|
||||
expression: `"wrap": ., .name: "great"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{wrap: {name: Mike}}]\n- [{wrap: {name: Bob}}]\n",
|
||||
"D0, P[], (!!seq)::- [{Mike: great}]\n- [{Bob: great}]\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestCreateMapOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range createMapOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
||||
88
pkg/yqlib/operator_delete.go
Normal file
88
pkg/yqlib/operator_delete.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// for each lhs, splat the node,
|
||||
// the intersect it against the rhs expression
|
||||
// recreate the contents using only the intersection result.
|
||||
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
elMap := list.New()
|
||||
elMap.PushBack(candidate)
|
||||
nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs)
|
||||
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if candidate.Node.Kind == yaml.SequenceNode {
|
||||
deleteFromArray(candidate, nodesToDelete)
|
||||
} else if candidate.Node.Kind == yaml.MappingNode {
|
||||
deleteFromMap(candidate, nodesToDelete)
|
||||
} else {
|
||||
log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate))
|
||||
}
|
||||
}
|
||||
return lhs, nil
|
||||
}
|
||||
|
||||
func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||
log.Debug("deleteFromMap")
|
||||
node := candidate.Node
|
||||
contents := node.Content
|
||||
newContents := make([]*yaml.Node, 0)
|
||||
|
||||
for index := 0; index < len(contents); index = index + 2 {
|
||||
key := contents[index]
|
||||
value := contents[index+1]
|
||||
|
||||
childCandidate := &CandidateNode{
|
||||
Node: value,
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, key.Value),
|
||||
}
|
||||
// _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
|
||||
shouldDelete := true
|
||||
|
||||
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
|
||||
|
||||
if !shouldDelete {
|
||||
newContents = append(newContents, key, value)
|
||||
}
|
||||
}
|
||||
node.Content = newContents
|
||||
}
|
||||
|
||||
func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||
log.Debug("deleteFromArray")
|
||||
node := candidate.Node
|
||||
contents := node.Content
|
||||
newContents := make([]*yaml.Node, 0)
|
||||
|
||||
for index := 0; index < len(contents); index = index + 1 {
|
||||
value := contents[index]
|
||||
|
||||
// childCandidate := &CandidateNode{
|
||||
// Node: value,
|
||||
// Document: candidate.Document,
|
||||
// Path: append(candidate.Path, index),
|
||||
// }
|
||||
|
||||
// _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
|
||||
shouldDelete := true
|
||||
if !shouldDelete {
|
||||
newContents = append(newContents, value)
|
||||
}
|
||||
}
|
||||
node.Content = newContents
|
||||
}
|
||||
22
pkg/yqlib/operator_equals.go
Normal file
22
pkg/yqlib/operator_equals.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
func EqualsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- equalsOperation")
|
||||
return crossFunction(d, matchingNodes, pathNode, isEquals)
|
||||
}
|
||||
|
||||
func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
value := false
|
||||
|
||||
if lhs.Node.Tag == "!!null" {
|
||||
value = (rhs.Node.Tag == "!!null")
|
||||
} else {
|
||||
value = Match(lhs.Node.Value, rhs.Node.Value)
|
||||
}
|
||||
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
|
||||
return createBooleanCandidate(lhs, value), nil
|
||||
}
|
||||
53
pkg/yqlib/operator_equals_test.go
Normal file
53
pkg/yqlib/operator_equals_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var equalsOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `[cat,goat,dog]`,
|
||||
expression: `.[] | (. == "*at")`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::true\n",
|
||||
"D0, P[1], (!!bool)::true\n",
|
||||
"D0, P[2], (!!bool)::false\n",
|
||||
},
|
||||
}, {
|
||||
document: `[3, 4, 5]`,
|
||||
expression: `.[] | (. == 4)`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::false\n",
|
||||
"D0, P[1], (!!bool)::true\n",
|
||||
"D0, P[2], (!!bool)::false\n",
|
||||
},
|
||||
}, {
|
||||
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
|
||||
expression: `.a | (.[].b == "apple")`,
|
||||
expected: []string{
|
||||
"D0, P[a cat b], (!!bool)::true\n",
|
||||
"D0, P[a pat b], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: ``,
|
||||
expression: `null == null`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: ``,
|
||||
expression: `null == ~`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEqualOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range equalsOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Equals Operator", equalsOperatorScenarios)
|
||||
}
|
||||
151
pkg/yqlib/operator_explode.go
Normal file
151
pkg/yqlib/operator_explode.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- ExplodeOperation")
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
|
||||
err = explodeNode(childEl.Value.(*CandidateNode).Node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return matchMap, nil
|
||||
}
|
||||
|
||||
func explodeNode(node *yaml.Node) error {
|
||||
node.Anchor = ""
|
||||
switch node.Kind {
|
||||
case yaml.SequenceNode, yaml.DocumentNode:
|
||||
for index, contentNode := range node.Content {
|
||||
log.Debugf("exploding index %v", index)
|
||||
errorInContent := explodeNode(contentNode)
|
||||
if errorInContent != nil {
|
||||
return errorInContent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case yaml.AliasNode:
|
||||
log.Debugf("its an alias!")
|
||||
if node.Alias != nil {
|
||||
node.Kind = node.Alias.Kind
|
||||
node.Style = node.Alias.Style
|
||||
node.Tag = node.Alias.Tag
|
||||
node.Content = node.Alias.Content
|
||||
node.Value = node.Alias.Value
|
||||
node.Alias = nil
|
||||
}
|
||||
return nil
|
||||
case yaml.MappingNode:
|
||||
var newContent = list.New()
|
||||
for index := 0; index < len(node.Content); index = index + 2 {
|
||||
keyNode := node.Content[index]
|
||||
valueNode := node.Content[index+1]
|
||||
log.Debugf("traversing %v", keyNode.Value)
|
||||
if keyNode.Value != "<<" {
|
||||
err := overrideEntry(node, keyNode, valueNode, index, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if valueNode.Kind == yaml.SequenceNode {
|
||||
log.Debugf("an alias merge list!")
|
||||
for index := 0; index < len(valueNode.Content); index = index + 1 {
|
||||
aliasNode := valueNode.Content[index]
|
||||
err := applyAlias(node, aliasNode.Alias, index, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debugf("an alias merge!")
|
||||
err := applyAlias(node, valueNode.Alias, index, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node.Content = make([]*yaml.Node, newContent.Len())
|
||||
index := 0
|
||||
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
|
||||
node.Content[index] = newEl.Value.(*yaml.Node)
|
||||
index++
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent *list.List) error {
|
||||
if alias == nil {
|
||||
return nil
|
||||
}
|
||||
for index := 0; index < len(alias.Content); index = index + 2 {
|
||||
keyNode := alias.Content[index]
|
||||
log.Debugf("applying alias key %v", keyNode.Value)
|
||||
valueNode := alias.Content[index+1]
|
||||
err := overrideEntry(node, keyNode, valueNode, aliasIndex, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent *list.List) error {
|
||||
|
||||
err := explodeNode(value)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
|
||||
valueEl := newEl.Next() // move forward twice
|
||||
keyNode := newEl.Value.(*yaml.Node)
|
||||
log.Debugf("checking new content %v:%v", keyNode.Value, valueEl.Value.(*yaml.Node).Value)
|
||||
if keyNode.Value == key.Value && keyNode.Alias == nil && key.Alias == nil {
|
||||
log.Debugf("overridign new content")
|
||||
valueEl.Value = value
|
||||
return nil
|
||||
}
|
||||
newEl = valueEl // move forward twice
|
||||
}
|
||||
|
||||
for index := startIndex + 2; index < len(node.Content); index = index + 2 {
|
||||
keyNode := node.Content[index]
|
||||
|
||||
if keyNode.Value == key.Value && keyNode.Alias == nil {
|
||||
log.Debugf("content will be overridden at index %v", index)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err = explodeNode(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("adding %v:%v", key.Value, value.Value)
|
||||
newContent.PushBack(key)
|
||||
newContent.PushBack(value)
|
||||
return nil
|
||||
}
|
||||
60
pkg/yqlib/operator_explode_test.go
Normal file
60
pkg/yqlib/operator_explode_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var explodeTest = []expressionScenario{
|
||||
{
|
||||
document: `{a: mike}`,
|
||||
expression: `explode(.a)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: mike}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{f : {a: &a cat, b: *a}}`,
|
||||
expression: `explode(.f)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{f: {a: cat, b: cat}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{f : {a: &a cat, *a: b}}`,
|
||||
expression: `explode(.f)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{f: {a: cat, cat: b}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foo* | explode(.) | (. style="flow")`,
|
||||
expected: []string{
|
||||
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
|
||||
"D0, P[foobarList], (!!map)::{b: bar_b, a: foo_a, thing: bar_thing, c: foobarList_c}\n",
|
||||
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
|
||||
expected: []string{
|
||||
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
|
||||
"D0, P[foobarList], (!!map)::{b: bar_b, a: foo_a, thing: bar_thing, c: foobarList_c}\n",
|
||||
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`,
|
||||
expression: `explode(.f)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{f: {a: cat, b: {f: cat}, cat: {f: cat}}}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestExplodeOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range explodeTest {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
||||
123
pkg/yqlib/operator_multilpy.go
Normal file
123
pkg/yqlib/operator_multilpy.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
|
||||
|
||||
func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
lhsCandidate := el.Value.(*CandidateNode)
|
||||
|
||||
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
|
||||
rhsCandidate := rightEl.Value.(*CandidateNode)
|
||||
resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results.PushBack(resultCandidate)
|
||||
}
|
||||
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- MultiplyOperator")
|
||||
return crossFunction(d, matchingNodes, pathNode, multiply)
|
||||
}
|
||||
|
||||
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
log.Debugf("Multipling LHS: %v", NodeToString(lhs))
|
||||
log.Debugf("- RHS: %v", NodeToString(rhs))
|
||||
|
||||
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
||||
|
||||
var newBlank = &CandidateNode{
|
||||
Path: lhs.Path,
|
||||
Document: lhs.Document,
|
||||
Filename: lhs.Filename,
|
||||
Node: &yaml.Node{},
|
||||
}
|
||||
var newThing, err = mergeObjects(d, newBlank, lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mergeObjects(d, newThing, rhs)
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("Cannot multiply %v with %v", NodeToString(lhs), NodeToString(rhs))
|
||||
}
|
||||
|
||||
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
var results = list.New()
|
||||
err := recursiveDecent(d, results, nodeToMap(rhs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pathIndexToStartFrom int = 0
|
||||
if results.Front() != nil {
|
||||
pathIndexToStartFrom = len(results.Front().Value.(*CandidateNode).Path)
|
||||
}
|
||||
|
||||
for el := results.Front(); el != nil; el = el.Next() {
|
||||
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return lhs, nil
|
||||
}
|
||||
|
||||
func createTraversalTree(path []interface{}) *PathTreeNode {
|
||||
if len(path) == 0 {
|
||||
return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
|
||||
} else if len(path) == 1 {
|
||||
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
|
||||
}
|
||||
return &PathTreeNode{
|
||||
Operation: &Operation{OperationType: Pipe},
|
||||
Lhs: createTraversalTree(path[0:1]),
|
||||
Rhs: createTraversalTree(path[1:])}
|
||||
|
||||
}
|
||||
|
||||
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode) error {
|
||||
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
||||
|
||||
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
||||
|
||||
assignmentOp := &Operation{OperationType: AssignAttributes}
|
||||
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
||||
assignmentOp.OperationType = Assign
|
||||
}
|
||||
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}
|
||||
|
||||
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{Operation: rhsOp}}
|
||||
|
||||
_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode)
|
||||
|
||||
return err
|
||||
}
|
||||
125
pkg/yqlib/operator_multiply_test.go
Normal file
125
pkg/yqlib/operator_multiply_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var multiplyOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {also: [1]}, b: {also: me}}`,
|
||||
expression: `. * {"a" : .b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {also: me}, b: {also: [1]}}`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge objects together",
|
||||
document: `{a: {also: me}, b: {also: {g: wizz}}}`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {also: {g: wizz}}, b: {also: me}}`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {also: {g: wizz}}, b: {also: [1]}}`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {also: [1]}, b: {also: [1]}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {also: [1]}, b: {also: {g: wizz}}}`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {things: great}, b: {also: me}}`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge keeps style of LHS",
|
||||
document: `a: {things: great}
|
||||
b:
|
||||
also: "me"
|
||||
`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
`D0, P[], (!!map)::a: {things: great, also: "me"}
|
||||
b:
|
||||
also: "me"
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge arrays",
|
||||
document: `{a: [1,2,3], b: [3,4,5]}`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge to prefix an element",
|
||||
document: `{a: cat, b: dog}`,
|
||||
expression: `. * {"a": {"c": .a}}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {c: cat}, b: dog}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge with simple aliases",
|
||||
document: `{a: &cat {c: frog}, b: {f: *cat}, c: {g: thongs}}`,
|
||||
expression: `.c * .b`,
|
||||
expected: []string{
|
||||
"D0, P[c], (!!map)::{g: thongs, f: *cat}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge does not copy anchor names",
|
||||
document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`,
|
||||
expression: `.c * .a`,
|
||||
expected: []string{
|
||||
"D0, P[c], (!!map)::{g: thongs, c: frog}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge with merge anchors",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar * .foobarList`,
|
||||
expected: []string{
|
||||
"D0, P[foobar], (!!map)::c: foobarList_c\n<<: [*foo, *bar]\nthing: foobar_thing\nb: foobarList_b\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestMultiplyOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range multiplyOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Mulitply Operator", multiplyOperatorScenarios)
|
||||
}
|
||||
20
pkg/yqlib/operator_not.go
Normal file
20
pkg/yqlib/operator_not.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- notOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debug("notOperation checking %v", candidate)
|
||||
truthy, errDecoding := isTruthy(candidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
result := createBooleanCandidate(candidate, !truthy)
|
||||
results.PushBack(result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
56
pkg/yqlib/operator_not_test.go
Normal file
56
pkg/yqlib/operator_not_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var notOperatorScenarios = []expressionScenario{
|
||||
// {
|
||||
// document: `cat`,
|
||||
// expression: `. | not`,
|
||||
// expected: []string{
|
||||
// "D0, P[], (!!bool)::false\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// document: `1`,
|
||||
// expression: `. | not`,
|
||||
// expected: []string{
|
||||
// "D0, P[], (!!bool)::false\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// document: `0`,
|
||||
// expression: `. | not`,
|
||||
// expected: []string{
|
||||
// "D0, P[], (!!bool)::false\n",
|
||||
// },
|
||||
// },
|
||||
{
|
||||
document: `~`,
|
||||
expression: `. | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
// {
|
||||
// document: `false`,
|
||||
// expression: `. | not`,
|
||||
// expected: []string{
|
||||
// "D0, P[], (!!bool)::true\n",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// document: `true`,
|
||||
// expression: `. | not`,
|
||||
// expected: []string{
|
||||
// "D0, P[], (!!bool)::false\n",
|
||||
// },
|
||||
// },
|
||||
}
|
||||
|
||||
func TestNotOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range notOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
||||
43
pkg/yqlib/operator_recursive_descent.go
Normal file
43
pkg/yqlib/operator_recursive_descent.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
err := recursiveDecent(d, results, matchMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error {
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
candidate.Node = UnwrapDoc(candidate.Node)
|
||||
|
||||
log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
|
||||
results.PushBack(candidate)
|
||||
|
||||
if candidate.Node.Kind != yaml.AliasNode {
|
||||
|
||||
children, err := Splat(d, nodeToMap(candidate))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = recursiveDecent(d, results, children)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
91
pkg/yqlib/operator_recursive_descent_test.go
Normal file
91
pkg/yqlib/operator_recursive_descent_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `cat`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: frog}`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: frog}\n",
|
||||
"D0, P[a], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {b: apple}}\n",
|
||||
"D0, P[a], (!!map)::{b: apple}\n",
|
||||
"D0, P[a b], (!!str)::apple\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `[1,2,3]`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[1, 2, 3]\n",
|
||||
"D0, P[0], (!!int)::1\n",
|
||||
"D0, P[1], (!!int)::2\n",
|
||||
"D0, P[2], (!!int)::3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `[{a: cat},2,true]`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[{a: cat}, 2, true]\n",
|
||||
"D0, P[0], (!!map)::{a: cat}\n",
|
||||
"D0, P[0 a], (!!str)::cat\n",
|
||||
"D0, P[1], (!!int)::2\n",
|
||||
"D0, P[2], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n",
|
||||
"D0, P[a], (!!map)::&cat {c: frog}\n",
|
||||
"D0, P[a c], (!!str)::frog\n",
|
||||
"D0, P[b], (alias)::*cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar | ..`,
|
||||
expected: []string{
|
||||
"D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n",
|
||||
"D0, P[foobar c], (!!str)::foobar_c\n",
|
||||
"D0, P[foobar <<], (alias)::*foo\n",
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList | ..`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n",
|
||||
"D0, P[foobarList b], (!!str)::foobarList_b\n",
|
||||
"D0, P[foobarList <<], (!!seq)::[*foo, *bar]\n",
|
||||
"D0, P[foobarList << 0], (alias)::*foo\n",
|
||||
"D0, P[foobarList << 1], (alias)::*bar\n",
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestRecursiveDescentOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range recursiveDescentOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
||||
37
pkg/yqlib/operator_select.go
Normal file
37
pkg/yqlib/operator_select.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
func SelectOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("-- selectOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// grab the first value
|
||||
first := rhs.Front()
|
||||
|
||||
if first != nil {
|
||||
result := first.Value.(*CandidateNode)
|
||||
includeResult, errDecoding := isTruthy(result)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
|
||||
if includeResult {
|
||||
results.PushBack(candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
51
pkg/yqlib/operator_select_test.go
Normal file
51
pkg/yqlib/operator_select_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var selectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `[cat,goat,dog]`,
|
||||
expression: `.[] | select(. == "*at")`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!str)::cat\n",
|
||||
"D0, P[1], (!!str)::goat\n",
|
||||
},
|
||||
}, {
|
||||
document: `[hot, fot, dog]`,
|
||||
expression: `.[] | select(. == "*at")`,
|
||||
expected: []string{},
|
||||
}, {
|
||||
document: `a: [cat,goat,dog]`,
|
||||
expression: `.a[] | select(. == "*at")`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::cat\n",
|
||||
"D0, P[a 1], (!!str)::goat\n"},
|
||||
}, {
|
||||
document: `a: { things: cat, bob: goat, horse: dog }`,
|
||||
expression: `.a[] | select(. == "*at")`,
|
||||
expected: []string{
|
||||
"D0, P[a things], (!!str)::cat\n",
|
||||
"D0, P[a bob], (!!str)::goat\n"},
|
||||
}, {
|
||||
document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`,
|
||||
expression: `.a[] | select(.include)`,
|
||||
expected: []string{
|
||||
"D0, P[a things], (!!map)::{include: true}\n",
|
||||
"D0, P[a andMe], (!!map)::{include: fold}\n",
|
||||
},
|
||||
}, {
|
||||
document: `[cat,~,dog]`,
|
||||
expression: `.[] | select(. == ~)`,
|
||||
expected: []string{
|
||||
"D0, P[1], (!!null)::~\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestSelectOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range selectOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
||||
7
pkg/yqlib/operator_self.go
Normal file
7
pkg/yqlib/operator_self.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
func SelfOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
return matchMap, nil
|
||||
}
|
||||
223
pkg/yqlib/operator_traverse_path.go
Normal file
223
pkg/yqlib/operator_traverse_path.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"container/list"
|
||||
|
||||
"github.com/elliotchance/orderedmap"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type TraversePreferences struct {
|
||||
DontFollowAlias bool
|
||||
}
|
||||
|
||||
func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) {
|
||||
preferences := &TraversePreferences{DontFollowAlias: true}
|
||||
splatOperation := &Operation{OperationType: TraversePath, Value: "[]", Preferences: preferences}
|
||||
splatTreeNode := &PathTreeNode{Operation: splatOperation}
|
||||
return TraversePathOperator(d, matches, splatTreeNode)
|
||||
}
|
||||
|
||||
func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- Traversing")
|
||||
var matchingNodeMap = list.New()
|
||||
var newNodes []*CandidateNode
|
||||
var err error
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
newNodes, err = traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, n := range newNodes {
|
||||
matchingNodeMap.PushBack(n)
|
||||
}
|
||||
}
|
||||
|
||||
return matchingNodeMap, nil
|
||||
}
|
||||
|
||||
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
|
||||
log.Debug("Traversing %v", NodeToString(matchingNode))
|
||||
value := matchingNode.Node
|
||||
|
||||
if value.Tag == "!!null" && operation.Value != "[]" {
|
||||
log.Debugf("Guessing kind")
|
||||
// we must ahve added this automatically, lets guess what it should be now
|
||||
switch operation.Value.(type) {
|
||||
case int, int64:
|
||||
log.Debugf("probably an array")
|
||||
value.Kind = yaml.SequenceNode
|
||||
default:
|
||||
log.Debugf("probably a map")
|
||||
value.Kind = yaml.MappingNode
|
||||
}
|
||||
value.Tag = ""
|
||||
}
|
||||
|
||||
switch value.Kind {
|
||||
case yaml.MappingNode:
|
||||
log.Debug("its a map with %v entries", len(value.Content)/2)
|
||||
var newMatches = orderedmap.NewOrderedMap()
|
||||
err := traverseMap(newMatches, matchingNode, operation)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if newMatches.Len() == 0 {
|
||||
//no matches, create one automagically
|
||||
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
|
||||
node := matchingNode.Node
|
||||
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: operation.StringValue}, valueNode)
|
||||
candidateNode := &CandidateNode{
|
||||
Node: valueNode,
|
||||
Path: append(matchingNode.Path, operation.StringValue),
|
||||
Document: matchingNode.Document,
|
||||
}
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
|
||||
}
|
||||
|
||||
arrayMatches := make([]*CandidateNode, newMatches.Len())
|
||||
i := 0
|
||||
for el := newMatches.Front(); el != nil; el = el.Next() {
|
||||
arrayMatches[i] = el.Value.(*CandidateNode)
|
||||
i++
|
||||
}
|
||||
return arrayMatches, nil
|
||||
|
||||
case yaml.SequenceNode:
|
||||
log.Debug("its a sequence of %v things!", len(value.Content))
|
||||
return traverseArray(matchingNode, operation)
|
||||
|
||||
case yaml.AliasNode:
|
||||
log.Debug("its an alias!")
|
||||
matchingNode.Node = matchingNode.Node.Alias
|
||||
return traverse(d, matchingNode, operation)
|
||||
case yaml.DocumentNode:
|
||||
log.Debug("digging into doc node")
|
||||
return traverse(d, &CandidateNode{
|
||||
Node: matchingNode.Node.Content[0],
|
||||
Document: matchingNode.Document}, operation)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func keyMatches(key *yaml.Node, pathNode *Operation) bool {
|
||||
return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue)
|
||||
}
|
||||
|
||||
func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, operation *Operation) error {
|
||||
// value.Content is a concatenated array of key, value,
|
||||
// so keys are in the even indexes, values in odd.
|
||||
// merge aliases are defined first, but we only want to traverse them
|
||||
// if we don't find a match directly on this node first.
|
||||
//TODO ALIASES, auto creation?
|
||||
|
||||
node := candidate.Node
|
||||
|
||||
followAlias := true
|
||||
|
||||
if operation.Preferences != nil {
|
||||
followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias
|
||||
}
|
||||
|
||||
var contents = node.Content
|
||||
for index := 0; index < len(contents); index = index + 2 {
|
||||
key := contents[index]
|
||||
value := contents[index+1]
|
||||
|
||||
log.Debug("checking %v (%v)", key.Value, key.Tag)
|
||||
//skip the 'merge' tag, find a direct match first
|
||||
if key.Tag == "!!merge" && followAlias {
|
||||
log.Debug("Merge anchor")
|
||||
err := traverseMergeAnchor(newMatches, candidate, value, operation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if keyMatches(key, operation) {
|
||||
log.Debug("MATCHED")
|
||||
candidateNode := &CandidateNode{
|
||||
Node: value,
|
||||
Path: append(candidate.Path, key.Value),
|
||||
Document: candidate.Document,
|
||||
}
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) error {
|
||||
switch value.Kind {
|
||||
case yaml.AliasNode:
|
||||
candidateNode := &CandidateNode{
|
||||
Node: value.Alias,
|
||||
Path: originalCandidate.Path,
|
||||
Document: originalCandidate.Document,
|
||||
}
|
||||
return traverseMap(newMatches, candidateNode, operation)
|
||||
case yaml.SequenceNode:
|
||||
for _, childValue := range value.Content {
|
||||
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, operation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func traverseArray(candidate *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
|
||||
log.Debug("operation Value %v", operation.Value)
|
||||
if operation.Value == "[]" {
|
||||
|
||||
var contents = candidate.Node.Content
|
||||
var newMatches = make([]*CandidateNode, len(contents))
|
||||
var index int64
|
||||
for index = 0; index < int64(len(contents)); index = index + 1 {
|
||||
newMatches[index] = &CandidateNode{
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
Node: contents[index],
|
||||
}
|
||||
}
|
||||
return newMatches, nil
|
||||
|
||||
}
|
||||
|
||||
switch operation.Value.(type) {
|
||||
case int64:
|
||||
index := operation.Value.(int64)
|
||||
indexToUse := index
|
||||
contentLength := int64(len(candidate.Node.Content))
|
||||
for contentLength <= index {
|
||||
candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
|
||||
contentLength = int64(len(candidate.Node.Content))
|
||||
}
|
||||
|
||||
if indexToUse < 0 {
|
||||
indexToUse = contentLength + indexToUse
|
||||
}
|
||||
|
||||
if indexToUse < 0 {
|
||||
return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
|
||||
}
|
||||
|
||||
return []*CandidateNode{&CandidateNode{
|
||||
Node: candidate.Node.Content[indexToUse],
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
}}, nil
|
||||
default:
|
||||
log.Debug("argument not an int (%v), no array matches", operation.Value)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
}
|
||||
237
pkg/yqlib/operator_traverse_path_test.go
Normal file
237
pkg/yqlib/operator_traverse_path_test.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var mergeDocSample = `
|
||||
foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
|
||||
bar: &bar
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
<<: [*foo,*bar]
|
||||
c: foobarList_c
|
||||
|
||||
foobar:
|
||||
c: foobar_c
|
||||
<<: *foo
|
||||
thing: foobar_thing
|
||||
`
|
||||
|
||||
var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `.a`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!map)::{b: apple}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `[{b: apple}, {c: banana}]`,
|
||||
expression: `.[]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!map)::{b: apple}\n",
|
||||
"D0, P[1], (!!map)::{c: banana}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `.a.b`,
|
||||
expected: []string{
|
||||
"D0, P[a b], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `.[1].a`,
|
||||
expected: []string{
|
||||
"D0, P[1 a], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `.a.[1]`,
|
||||
expected: []string{
|
||||
"D0, P[a 1], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {cat: apple, mad: things}}`,
|
||||
expression: `.a."*a*"`,
|
||||
expected: []string{
|
||||
"D0, P[a cat], (!!str)::apple\n",
|
||||
"D0, P[a mad], (!!str)::things\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {cat: {b: 3}, mad: {b: 4}, fad: {c: t}}}`,
|
||||
expression: `.a."*a*".b`,
|
||||
expected: []string{
|
||||
"D0, P[a cat b], (!!int)::3\n",
|
||||
"D0, P[a mad b], (!!int)::4\n",
|
||||
"D0, P[a fad b], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {cat: apple, mad: things}}`,
|
||||
expression: `.a | (.cat, .mad)`,
|
||||
expected: []string{
|
||||
"D0, P[a cat], (!!str)::apple\n",
|
||||
"D0, P[a mad], (!!str)::things\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {cat: apple, mad: things}}`,
|
||||
expression: `.a | (.cat, .mad, .fad)`,
|
||||
expected: []string{
|
||||
"D0, P[a cat], (!!str)::apple\n",
|
||||
"D0, P[a mad], (!!str)::things\n",
|
||||
"D0, P[a fad], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: {cat: apple, mad: things}}`,
|
||||
expression: `.a | (.cat, .mad, .fad) | select( (. == null) | not)`,
|
||||
expected: []string{
|
||||
"D0, P[a cat], (!!str)::apple\n",
|
||||
"D0, P[a mad], (!!str)::things\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
expression: `.b`,
|
||||
expected: []string{
|
||||
"D0, P[b], (alias)::*cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
expression: `.b.[]`,
|
||||
expected: []string{
|
||||
"D0, P[b c], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
expression: `.b.c`,
|
||||
expected: []string{
|
||||
"D0, P[b c], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `[1,2,3]`,
|
||||
expression: `.b`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
document: `[1,2,3]`,
|
||||
expression: `[0]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: `Maps can have numbers as keys, so this default to a non-exisiting key behaviour.`,
|
||||
document: `{a: b}`,
|
||||
expression: `[0]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar`,
|
||||
expected: []string{
|
||||
"D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar.a`,
|
||||
expected: []string{
|
||||
"D0, P[foobar a], (!!str)::foo_a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar.c`,
|
||||
expected: []string{
|
||||
"D0, P[foobar c], (!!str)::foo_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar.thing`,
|
||||
expected: []string{
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar.[]`,
|
||||
expected: []string{
|
||||
"D0, P[foobar c], (!!str)::foo_c\n",
|
||||
"D0, P[foobar a], (!!str)::foo_a\n",
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.a`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList a], (!!str)::foo_a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.thing`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList thing], (!!str)::bar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.c`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.b`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList b], (!!str)::bar_b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.[]`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList b], (!!str)::bar_b\n",
|
||||
"D0, P[foobarList a], (!!str)::foo_a\n",
|
||||
"D0, P[foobarList thing], (!!str)::bar_thing\n",
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestTraversePathOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range traversePathOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
||||
19
pkg/yqlib/operator_union.go
Normal file
19
pkg/yqlib/operator_union.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
func UnionOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for el := rhs.Front(); el != nil; el = el.Next() {
|
||||
node := el.Value.(*CandidateNode)
|
||||
lhs.PushBack(node)
|
||||
}
|
||||
return lhs, nil
|
||||
}
|
||||
31
pkg/yqlib/operator_union_test.go
Normal file
31
pkg/yqlib/operator_union_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var unionOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `"cat", "dog"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::cat\n",
|
||||
"D0, P[], (!!str)::dog\n",
|
||||
},
|
||||
}, {
|
||||
document: `{a: frog}`,
|
||||
expression: `1, true, "cat", .a`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::1\n",
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
"D0, P[], (!!str)::cat\n",
|
||||
"D0, P[a], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnionOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range unionOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user