├── .github └── FUNDING.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── main.rs └── options.rs └── tests └── integration_tests.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [matthiasdiener] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstyle" 16 | version = "1.0.4" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" 19 | 20 | [[package]] 21 | name = "assert_cmd" 22 | version = "2.0.12" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" 25 | dependencies = [ 26 | "anstyle", 27 | "bstr", 28 | "doc-comment", 29 | "predicates", 30 | "predicates-core", 31 | "predicates-tree", 32 | "wait-timeout", 33 | ] 34 | 35 | [[package]] 36 | name = "autocfg" 37 | version = "1.1.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 40 | 41 | [[package]] 42 | name = "bitflags" 43 | version = "2.4.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 46 | 47 | [[package]] 48 | name = "bstr" 49 | version = "1.7.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" 52 | dependencies = [ 53 | "memchr", 54 | "regex-automata", 55 | "serde", 56 | ] 57 | 58 | [[package]] 59 | name = "colored" 60 | version = "2.0.4" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" 63 | dependencies = [ 64 | "is-terminal", 65 | "lazy_static", 66 | "windows-sys", 67 | ] 68 | 69 | [[package]] 70 | name = "difflib" 71 | version = "0.4.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 74 | 75 | [[package]] 76 | name = "doc-comment" 77 | version = "0.3.3" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 80 | 81 | [[package]] 82 | name = "either" 83 | version = "1.9.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 86 | 87 | [[package]] 88 | name = "equivalent" 89 | version = "1.0.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 92 | 93 | [[package]] 94 | name = "errno" 95 | version = "0.3.5" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" 98 | dependencies = [ 99 | "libc", 100 | "windows-sys", 101 | ] 102 | 103 | [[package]] 104 | name = "getopts" 105 | version = "0.2.21" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 108 | dependencies = [ 109 | "unicode-width", 110 | ] 111 | 112 | [[package]] 113 | name = "hashbrown" 114 | version = "0.12.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 117 | 118 | [[package]] 119 | name = "hashbrown" 120 | version = "0.14.2" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" 123 | 124 | [[package]] 125 | name = "hermit-abi" 126 | version = "0.3.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" 129 | 130 | [[package]] 131 | name = "indexmap" 132 | version = "1.9.3" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 135 | dependencies = [ 136 | "autocfg", 137 | "hashbrown 0.12.3", 138 | "serde", 139 | ] 140 | 141 | [[package]] 142 | name = "indexmap" 143 | version = "2.0.2" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" 146 | dependencies = [ 147 | "equivalent", 148 | "hashbrown 0.14.2", 149 | ] 150 | 151 | [[package]] 152 | name = "is-terminal" 153 | version = "0.4.9" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" 156 | dependencies = [ 157 | "hermit-abi", 158 | "rustix", 159 | "windows-sys", 160 | ] 161 | 162 | [[package]] 163 | name = "itertools" 164 | version = "0.11.0" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" 167 | dependencies = [ 168 | "either", 169 | ] 170 | 171 | [[package]] 172 | name = "itoa" 173 | version = "1.0.9" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 176 | 177 | [[package]] 178 | name = "lazy_static" 179 | version = "1.4.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 182 | 183 | [[package]] 184 | name = "libc" 185 | version = "0.2.149" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" 188 | 189 | [[package]] 190 | name = "linux-raw-sys" 191 | version = "0.4.10" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" 194 | 195 | [[package]] 196 | name = "memchr" 197 | version = "2.6.4" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 200 | 201 | [[package]] 202 | name = "predicates" 203 | version = "3.0.4" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" 206 | dependencies = [ 207 | "anstyle", 208 | "difflib", 209 | "itertools", 210 | "predicates-core", 211 | ] 212 | 213 | [[package]] 214 | name = "predicates-core" 215 | version = "1.0.6" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" 218 | 219 | [[package]] 220 | name = "predicates-tree" 221 | version = "1.0.9" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" 224 | dependencies = [ 225 | "predicates-core", 226 | "termtree", 227 | ] 228 | 229 | [[package]] 230 | name = "proc-macro2" 231 | version = "1.0.69" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 234 | dependencies = [ 235 | "unicode-ident", 236 | ] 237 | 238 | [[package]] 239 | name = "quote" 240 | version = "1.0.33" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 243 | dependencies = [ 244 | "proc-macro2", 245 | ] 246 | 247 | [[package]] 248 | name = "regex" 249 | version = "1.10.2" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 252 | dependencies = [ 253 | "aho-corasick", 254 | "memchr", 255 | "regex-automata", 256 | "regex-syntax", 257 | ] 258 | 259 | [[package]] 260 | name = "regex-automata" 261 | version = "0.4.3" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 264 | dependencies = [ 265 | "aho-corasick", 266 | "memchr", 267 | "regex-syntax", 268 | ] 269 | 270 | [[package]] 271 | name = "regex-syntax" 272 | version = "0.8.2" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 275 | 276 | [[package]] 277 | name = "ripjson" 278 | version = "0.9.11" 279 | dependencies = [ 280 | "assert_cmd", 281 | "colored", 282 | "getopts", 283 | "indexmap 1.9.3", 284 | "regex", 285 | "serde", 286 | "serde_json", 287 | ] 288 | 289 | [[package]] 290 | name = "rustix" 291 | version = "0.38.20" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" 294 | dependencies = [ 295 | "bitflags", 296 | "errno", 297 | "libc", 298 | "linux-raw-sys", 299 | "windows-sys", 300 | ] 301 | 302 | [[package]] 303 | name = "ryu" 304 | version = "1.0.15" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 307 | 308 | [[package]] 309 | name = "serde" 310 | version = "1.0.190" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" 313 | dependencies = [ 314 | "serde_derive", 315 | ] 316 | 317 | [[package]] 318 | name = "serde_derive" 319 | version = "1.0.190" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" 322 | dependencies = [ 323 | "proc-macro2", 324 | "quote", 325 | "syn", 326 | ] 327 | 328 | [[package]] 329 | name = "serde_json" 330 | version = "1.0.107" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 333 | dependencies = [ 334 | "indexmap 2.0.2", 335 | "itoa", 336 | "ryu", 337 | "serde", 338 | ] 339 | 340 | [[package]] 341 | name = "syn" 342 | version = "2.0.38" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" 345 | dependencies = [ 346 | "proc-macro2", 347 | "quote", 348 | "unicode-ident", 349 | ] 350 | 351 | [[package]] 352 | name = "termtree" 353 | version = "0.4.1" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 356 | 357 | [[package]] 358 | name = "unicode-ident" 359 | version = "1.0.12" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 362 | 363 | [[package]] 364 | name = "unicode-width" 365 | version = "0.1.11" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" 368 | 369 | [[package]] 370 | name = "wait-timeout" 371 | version = "0.2.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 374 | dependencies = [ 375 | "libc", 376 | ] 377 | 378 | [[package]] 379 | name = "windows-sys" 380 | version = "0.48.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 383 | dependencies = [ 384 | "windows-targets", 385 | ] 386 | 387 | [[package]] 388 | name = "windows-targets" 389 | version = "0.48.5" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 392 | dependencies = [ 393 | "windows_aarch64_gnullvm", 394 | "windows_aarch64_msvc", 395 | "windows_i686_gnu", 396 | "windows_i686_msvc", 397 | "windows_x86_64_gnu", 398 | "windows_x86_64_gnullvm", 399 | "windows_x86_64_msvc", 400 | ] 401 | 402 | [[package]] 403 | name = "windows_aarch64_gnullvm" 404 | version = "0.48.5" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 407 | 408 | [[package]] 409 | name = "windows_aarch64_msvc" 410 | version = "0.48.5" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 413 | 414 | [[package]] 415 | name = "windows_i686_gnu" 416 | version = "0.48.5" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 419 | 420 | [[package]] 421 | name = "windows_i686_msvc" 422 | version = "0.48.5" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 425 | 426 | [[package]] 427 | name = "windows_x86_64_gnu" 428 | version = "0.48.5" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 431 | 432 | [[package]] 433 | name = "windows_x86_64_gnullvm" 434 | version = "0.48.5" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 437 | 438 | [[package]] 439 | name = "windows_x86_64_msvc" 440 | version = "0.48.5" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 443 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ripjson" 3 | version = "0.9.11" 4 | readme = "README.md" 5 | authors = ["Matthias Diener "] 6 | description = "A fast and lean way to grep in JSON files." 7 | license = "MIT" 8 | documentation = "https://github.com/matthiasdiener/ripjson" 9 | homepage = "https://github.com/matthiasdiener/ripjson" 10 | repository = "https://github.com/matthiasdiener/ripjson" 11 | edition = "2021" 12 | 13 | [dependencies] 14 | serde_json = {version="1.0", features = ["preserve_order"]} 15 | indexmap = { version = "1", features = ["serde-1"] } 16 | serde = "1.0" 17 | regex = "1" 18 | getopts = "0.2" 19 | colored = "2" 20 | assert_cmd = "2" 21 | 22 | [[bin]] 23 | bench = false 24 | path = "src/main.rs" 25 | name = "rj" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Matthias Diener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ripjson 2 | A fast and lean way to grep in JSON files. 3 | 4 | ## Installation 5 | 6 | ``` console 7 | $ cargo install ripjson 8 | ``` 9 | 10 | ## Usage 11 | 12 | Usage: rj [options] 13 | 14 | Options: 15 | -i, --ignore-case Search case insensitively. 16 | -s, --sensitive-case 17 | Search case sensitively [default]. 18 | -h, --help Print this help menu. 19 | -v, --version Print version. 20 | --color Color output. 21 | WHEN can be never, always, or auto [default]. 22 | 23 | Prints all JSON keys and values in `` whose keys match ``. 24 | 25 | `` specifies for which JSON keys to search for. Separate path elements 26 | with a `/`, e.g. `user/name`, similar to the JSON pointer syntax specfied in 27 | https://tools.ietf.org/html/rfc6901. 28 | 29 | ## Example 30 | 31 | ``` console 32 | $ cat test.json 33 | { 34 | "name": "John Doe", 35 | "age": 43, 36 | "address": { 37 | "street": "10 Downing Street", 38 | "city": "London" 39 | }, 40 | "phones": [ 41 | "+44 1234567", 42 | "+44 2345678" 43 | ] 44 | } 45 | 46 | $ rj '.*es.*' test.json 47 | address/street = "10 Downing Street" 48 | address/city = "London" 49 | phones = "+44 1234567" 50 | phones = "+44 2345678" 51 | 52 | $ rj '.*es.*/cit' test.json 53 | address/city = "London" 54 | ``` 55 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::options::parse_options; 2 | use colored::Colorize; 3 | use indexmap::IndexMap; 4 | use regex::Captures; 5 | use regex::Regex; 6 | use serde_json::Value; 7 | use std::fs; 8 | use std::io::Read; 9 | 10 | mod options; 11 | 12 | fn format_match(regex: &Regex, color_output: bool, path: &str, extra: &str, res: &mut String) { 13 | if !regex.is_match(&path) { 14 | return; 15 | } 16 | 17 | if color_output { 18 | let output = regex.replace_all(&path, |caps: &Captures| { 19 | format!("{}", &caps[0].red().bold()) 20 | }); 21 | res.push_str(&output); 22 | res.push_str(extra); 23 | res.push('\n'); 24 | } else { 25 | res.push_str(path); 26 | res.push_str(extra); 27 | res.push('\n'); 28 | } 29 | } 30 | 31 | fn visit_obj(regex: &Regex, color_output: bool, path: &str, value: &Value, res: &mut String) { 32 | let obj = value.as_object().unwrap(); 33 | 34 | if obj.is_empty() { 35 | format_match(regex, color_output, &path, " = {{}}", res); 36 | return; 37 | } 38 | 39 | for e in obj { 40 | let fullstring = format!("{}/{}", path, e.0); 41 | if e.1.is_array() { 42 | visit_array(regex, color_output, &fullstring, e.1, res); 43 | } else if e.1.is_object() { 44 | visit_obj(regex, color_output, &fullstring, e.1, res); 45 | } else { 46 | format_match( 47 | regex, 48 | color_output, 49 | &fullstring, 50 | &format!(" = {}", e.1), 51 | res, 52 | ); 53 | } 54 | } 55 | } 56 | 57 | fn visit_array(regex: &Regex, color_output: bool, path: &str, value: &Value, res: &mut String) { 58 | let a = value.as_array().unwrap(); 59 | 60 | if a.is_empty() { 61 | format_match(regex, color_output, &path, " = []", res); 62 | return; 63 | } 64 | 65 | for e in a { 66 | if e.is_array() { 67 | visit_array(regex, color_output, path, e, res); 68 | } else if e.is_object() { 69 | visit_obj(regex, color_output, path, e, res); 70 | } else { 71 | format_match(regex, color_output, &path, &format!(" = {}", e), res); 72 | } 73 | } 74 | } 75 | 76 | fn search_string(regex: &str, content: &str, color_output: bool) -> String { 77 | let values: IndexMap = serde_json::from_str(content).unwrap(); 78 | let regex = Regex::new(®ex).unwrap(); 79 | 80 | let mut res = "".to_string(); 81 | 82 | for v in values { 83 | if v.1.is_array() { 84 | visit_array(®ex, color_output, &v.0, &v.1, &mut res); 85 | } else if v.1.is_object() { 86 | visit_obj(®ex, color_output, &v.0, &v.1, &mut res); 87 | } else { 88 | format_match(®ex, color_output, &v.0, &format!(" = {}", v.1), &mut res); 89 | } 90 | } 91 | 92 | res 93 | } 94 | 95 | fn search_file(regex: &str, filename: &str, color_output: bool) -> std::io::Result<()> { 96 | let mut contents = String::new(); 97 | 98 | if filename == "-" { 99 | std::io::stdin().read_to_string(&mut contents)?; 100 | } else { 101 | contents = fs::read_to_string(filename).unwrap(); 102 | } 103 | 104 | print!("{}", search_string(regex, &contents, color_output)); 105 | Ok(()) 106 | } 107 | 108 | fn main() -> std::io::Result<()> { 109 | let (regex, files, color_output) = parse_options(); 110 | 111 | for filename in files { 112 | search_file(®ex, &filename, color_output)?; 113 | } 114 | 115 | Ok(()) 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | #[test] 121 | fn find_simple_string() { 122 | use crate::search_string; 123 | let mystr = "{\"name\": \"John Doe\",\"age\": 43,\"address\": 124 | {\"street\": \"10 Downing Street\",\"city\": \"London\" 125 | },\"phones\": [\"+44 1234567\",\"+44 2345678\"]}"; 126 | let regex = ".*es.*/cit"; 127 | assert_eq!( 128 | &search_string(regex, mystr, false), 129 | "address/city = \"London\"\n" 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/options.rs: -------------------------------------------------------------------------------- 1 | use getopts::Options; 2 | use std::env; 3 | use std::io::IsTerminal; 4 | 5 | fn print_usage(program: &str, opts: Options) { 6 | let brief = format!("Usage: {} [options]", program); 7 | print!("{}", opts.usage(&brief)); 8 | } 9 | 10 | fn print_version() { 11 | const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); 12 | println!("Ripjson v{}", VERSION.unwrap_or("unknown")); 13 | } 14 | 15 | fn add_options(opts: &mut Options) { 16 | opts.optflag("i", "ignore-case", "Search case insensitively."); 17 | opts.optflag("s", "sensitive-case", "Search case sensitively [default]."); 18 | opts.optflag("h", "help", "Print this help menu."); 19 | opts.optflag("v", "version", "Print version."); 20 | opts.optopt( 21 | "", 22 | "color", 23 | "Color output.\nWHEN can be never, always, or auto [default].", 24 | "", 25 | ); 26 | } 27 | 28 | pub fn parse_options() -> (String, Vec, bool) { 29 | let args: Vec = env::args().collect(); 30 | let program = args[0].clone(); 31 | let mut opts = Options::new(); 32 | add_options(&mut opts); 33 | 34 | let matches = match opts.parse(&args[1..]) { 35 | Ok(m) => m, 36 | Err(_f) => { 37 | print_usage(&program, opts); 38 | std::process::exit(1) 39 | } 40 | }; 41 | 42 | if matches.opt_present("h") { 43 | print_usage(&program, opts); 44 | std::process::exit(0); 45 | } 46 | 47 | if matches.opt_present("v") { 48 | print_version(); 49 | std::process::exit(0); 50 | } 51 | 52 | let color_output = if matches.opt_present("color") { 53 | match matches.opt_str("color").unwrap().as_ref() { 54 | "never" => false, 55 | "always" => true, 56 | "auto" => std::io::stdout().is_terminal(), 57 | _ => { 58 | println!( 59 | "Error: unknown color mode '{}' specified.\n", 60 | matches.opt_str("color").unwrap() 61 | ); 62 | print_usage(&program, opts); 63 | std::process::exit(1) 64 | } 65 | } 66 | } else { 67 | std::io::stdout().is_terminal() 68 | }; 69 | 70 | let ignore_case = matches.opt_present("i") && !matches.opt_present("s"); 71 | 72 | let regex = format!( 73 | "{}({})", 74 | if ignore_case { "(?i)" } else { "" }, 75 | if !matches.free.is_empty() { 76 | matches.free[0].clone() 77 | } else { 78 | println!("Error: no regex specified.\n"); 79 | print_usage(&program, opts); 80 | std::process::exit(1) 81 | } 82 | ); 83 | 84 | let files = if matches.free.len() > 1 { 85 | matches.free[1..].to_vec() 86 | } else { 87 | // Read from stdin if no file specified 88 | vec![String::from("-")] 89 | }; 90 | 91 | (regex, files, color_output) 92 | } 93 | -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | // use super::main::search_string; 4 | use assert_cmd::Command; 5 | use std::fs; 6 | use std::fs::File; 7 | use std::io::Write; 8 | 9 | #[test] 10 | fn calling_ripjson_without_arguments() { 11 | let mut cmd = Command::cargo_bin(env!("CARGO_BIN_EXE_rj") ).unwrap(); 12 | cmd.assert().failure(); 13 | } 14 | 15 | #[test] 16 | fn find_simple_file() { 17 | let mut file = File::create("test.json").unwrap(); 18 | file.write_all( 19 | b"{\"name\": \"John Doe\",\"age\": 43,\"address\": 20 | {\"street\": \"10 Downing Street\",\"city\": \"London\" 21 | },\"phones\": [\"+44 1234567\",\"+44 2345678\"]}", 22 | ) 23 | .unwrap(); 24 | let mut cmd = Command::cargo_bin(env!("CARGO_BIN_EXE_rj") ).unwrap(); 25 | let assert = cmd 26 | .arg(".*es.*/cit") 27 | .arg("test.json") 28 | .assert(); 29 | 30 | assert 31 | .success() 32 | .stdout("address/city = \"London\"\n"); 33 | fs::remove_file("test.json").unwrap(); 34 | } 35 | } 36 | --------------------------------------------------------------------------------