├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── TODO.txt ├── src └── bin │ ├── calc.rs │ ├── cur.rs │ ├── dmesg.rs │ ├── grep.rs │ ├── gunzip.rs │ ├── gzip.rs │ ├── info.rs │ ├── keymap.rs │ ├── less.rs │ ├── man.rs │ ├── mdless.rs │ ├── mtxt.rs │ ├── rem.rs │ ├── resize.rs │ ├── screenfetch.rs │ ├── tar.rs │ ├── unzip.rs │ └── watch.rs └── tests └── grep.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | sudo: false 5 | addons: 6 | apt: 7 | packages: 8 | - liblzma-dev 9 | notifications: 10 | email: false 11 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "adler32" 13 | version = "1.2.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "1.1.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 22 | dependencies = [ 23 | "memchr 2.7.4", 24 | ] 25 | 26 | [[package]] 27 | name = "arg_parser" 28 | version = "0.1.0" 29 | source = "git+https://gitlab.redox-os.org/redox-os/arg-parser.git#1c434b55f3e1a0375ebcca85b3e88db7378e82fa" 30 | 31 | [[package]] 32 | name = "assert_cmd" 33 | version = "1.0.8" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe" 36 | dependencies = [ 37 | "bstr", 38 | "doc-comment", 39 | "predicates 2.1.5", 40 | "predicates-core", 41 | "predicates-tree", 42 | "wait-timeout", 43 | ] 44 | 45 | [[package]] 46 | name = "autocfg" 47 | version = "1.4.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 50 | 51 | [[package]] 52 | name = "bitflags" 53 | version = "1.3.2" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 56 | 57 | [[package]] 58 | name = "bitflags" 59 | version = "2.9.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 62 | 63 | [[package]] 64 | name = "bstr" 65 | version = "0.2.17" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 68 | dependencies = [ 69 | "lazy_static", 70 | "memchr 2.7.4", 71 | "regex-automata 0.1.10", 72 | ] 73 | 74 | [[package]] 75 | name = "bzip2" 76 | version = "0.3.3" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" 79 | dependencies = [ 80 | "bzip2-sys", 81 | "libc", 82 | ] 83 | 84 | [[package]] 85 | name = "bzip2-sys" 86 | version = "0.1.13+1.0.8" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" 89 | dependencies = [ 90 | "cc", 91 | "pkg-config", 92 | ] 93 | 94 | [[package]] 95 | name = "cc" 96 | version = "1.1.22" 97 | source = "git+https://github.com/tea/cc-rs?branch=riscv-abi-arch-fix#588ceacb084af41415690c57688e338a32a1f1b4" 98 | dependencies = [ 99 | "shlex", 100 | ] 101 | 102 | [[package]] 103 | name = "cfg-if" 104 | version = "1.0.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 107 | 108 | [[package]] 109 | name = "crc32fast" 110 | version = "1.4.2" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 113 | dependencies = [ 114 | "cfg-if", 115 | ] 116 | 117 | [[package]] 118 | name = "difference" 119 | version = "2.0.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 122 | 123 | [[package]] 124 | name = "difflib" 125 | version = "0.4.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 128 | 129 | [[package]] 130 | name = "doc-comment" 131 | version = "0.3.3" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 134 | 135 | [[package]] 136 | name = "either" 137 | version = "1.15.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 140 | 141 | [[package]] 142 | name = "errno" 143 | version = "0.3.11" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 146 | dependencies = [ 147 | "libc", 148 | "windows-sys", 149 | ] 150 | 151 | [[package]] 152 | name = "extra" 153 | version = "0.1.0" 154 | source = "git+https://gitlab.redox-os.org/redox-os/libextra.git#cf213969493db8667052a591e32a1e26d43c4234" 155 | 156 | [[package]] 157 | name = "extrautils" 158 | version = "0.1.0" 159 | dependencies = [ 160 | "arg_parser", 161 | "assert_cmd", 162 | "bzip2", 163 | "extra", 164 | "filetime", 165 | "libflate", 166 | "libredox", 167 | "os-release", 168 | "pager", 169 | "predicates 1.0.8", 170 | "raw-cpuid", 171 | "rust-lzma", 172 | "tar", 173 | "tempfile", 174 | "termion 4.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 175 | "tree_magic", 176 | "zip", 177 | ] 178 | 179 | [[package]] 180 | name = "fastrand" 181 | version = "2.3.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 184 | 185 | [[package]] 186 | name = "filetime" 187 | version = "0.2.24" 188 | source = "git+https://github.com/jackpot51/filetime.git#186e19d3190ead16b05329400cb5b2350d8f44cf" 189 | dependencies = [ 190 | "cfg-if", 191 | "libc", 192 | "libredox", 193 | "windows-sys", 194 | ] 195 | 196 | [[package]] 197 | name = "fixedbitset" 198 | version = "0.2.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" 201 | 202 | [[package]] 203 | name = "flate2" 204 | version = "1.1.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" 207 | dependencies = [ 208 | "crc32fast", 209 | "miniz_oxide", 210 | ] 211 | 212 | [[package]] 213 | name = "float-cmp" 214 | version = "0.8.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" 217 | dependencies = [ 218 | "num-traits", 219 | ] 220 | 221 | [[package]] 222 | name = "fnv" 223 | version = "1.0.7" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 226 | 227 | [[package]] 228 | name = "getrandom" 229 | version = "0.3.2" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 232 | dependencies = [ 233 | "cfg-if", 234 | "libc", 235 | "r-efi", 236 | "wasi 0.14.2+wasi-0.2.4", 237 | ] 238 | 239 | [[package]] 240 | name = "hashbrown" 241 | version = "0.12.3" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 244 | 245 | [[package]] 246 | name = "indexmap" 247 | version = "1.9.3" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 250 | dependencies = [ 251 | "autocfg", 252 | "hashbrown", 253 | ] 254 | 255 | [[package]] 256 | name = "itertools" 257 | version = "0.10.5" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 260 | dependencies = [ 261 | "either", 262 | ] 263 | 264 | [[package]] 265 | name = "lazy_static" 266 | version = "1.5.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 269 | 270 | [[package]] 271 | name = "libc" 272 | version = "0.2.171" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 275 | 276 | [[package]] 277 | name = "libflate" 278 | version = "0.1.27" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" 281 | dependencies = [ 282 | "adler32", 283 | "crc32fast", 284 | "rle-decode-fast", 285 | "take_mut", 286 | ] 287 | 288 | [[package]] 289 | name = "libredox" 290 | version = "0.1.3" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 293 | dependencies = [ 294 | "bitflags 2.9.0", 295 | "libc", 296 | "redox_syscall", 297 | ] 298 | 299 | [[package]] 300 | name = "linux-raw-sys" 301 | version = "0.9.4" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 304 | 305 | [[package]] 306 | name = "memchr" 307 | version = "1.0.2" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" 310 | dependencies = [ 311 | "libc", 312 | ] 313 | 314 | [[package]] 315 | name = "memchr" 316 | version = "2.7.4" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 319 | 320 | [[package]] 321 | name = "miniz_oxide" 322 | version = "0.8.8" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 325 | dependencies = [ 326 | "adler2", 327 | ] 328 | 329 | [[package]] 330 | name = "msdos_time" 331 | version = "0.1.6" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "aad9dfe950c057b1bfe9c1f2aa51583a8468ef2a5baba2ebbe06d775efeb7729" 334 | dependencies = [ 335 | "time", 336 | "winapi", 337 | ] 338 | 339 | [[package]] 340 | name = "nom" 341 | version = "3.2.1" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" 344 | dependencies = [ 345 | "memchr 1.0.2", 346 | ] 347 | 348 | [[package]] 349 | name = "normalize-line-endings" 350 | version = "0.3.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 353 | 354 | [[package]] 355 | name = "num-traits" 356 | version = "0.2.19" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 359 | dependencies = [ 360 | "autocfg", 361 | ] 362 | 363 | [[package]] 364 | name = "numtoa" 365 | version = "0.2.4" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" 368 | 369 | [[package]] 370 | name = "once_cell" 371 | version = "1.21.3" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 374 | 375 | [[package]] 376 | name = "os-release" 377 | version = "0.1.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "82f29ae2f71b53ec19cc23385f8e4f3d90975195aa3d09171ba3bef7159bec27" 380 | dependencies = [ 381 | "lazy_static", 382 | ] 383 | 384 | [[package]] 385 | name = "pager" 386 | version = "0.1.0" 387 | source = "git+https://gitlab.redox-os.org/redox-os/libpager.git#d0ff9e28d1308bfa9c9cb8a0a4d461fc0c508b70" 388 | dependencies = [ 389 | "termion 4.0.5 (git+https://gitlab.redox-os.org/redox-os/termion.git)", 390 | ] 391 | 392 | [[package]] 393 | name = "petgraph" 394 | version = "0.5.1" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" 397 | dependencies = [ 398 | "fixedbitset", 399 | "indexmap", 400 | ] 401 | 402 | [[package]] 403 | name = "pkg-config" 404 | version = "0.3.32" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 407 | 408 | [[package]] 409 | name = "podio" 410 | version = "0.1.7" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "b18befed8bc2b61abc79a457295e7e838417326da1586050b919414073977f19" 413 | 414 | [[package]] 415 | name = "predicates" 416 | version = "1.0.8" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" 419 | dependencies = [ 420 | "difference", 421 | "float-cmp", 422 | "normalize-line-endings", 423 | "predicates-core", 424 | "regex", 425 | ] 426 | 427 | [[package]] 428 | name = "predicates" 429 | version = "2.1.5" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" 432 | dependencies = [ 433 | "difflib", 434 | "itertools", 435 | "predicates-core", 436 | ] 437 | 438 | [[package]] 439 | name = "predicates-core" 440 | version = "1.0.9" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" 443 | 444 | [[package]] 445 | name = "predicates-tree" 446 | version = "1.0.12" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" 449 | dependencies = [ 450 | "predicates-core", 451 | "termtree", 452 | ] 453 | 454 | [[package]] 455 | name = "r-efi" 456 | version = "5.2.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 459 | 460 | [[package]] 461 | name = "raw-cpuid" 462 | version = "10.7.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" 465 | dependencies = [ 466 | "bitflags 1.3.2", 467 | ] 468 | 469 | [[package]] 470 | name = "redox_syscall" 471 | version = "0.5.11" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 474 | dependencies = [ 475 | "bitflags 2.9.0", 476 | ] 477 | 478 | [[package]] 479 | name = "redox_termios" 480 | version = "0.1.3" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" 483 | 484 | [[package]] 485 | name = "regex" 486 | version = "1.11.1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 489 | dependencies = [ 490 | "aho-corasick", 491 | "memchr 2.7.4", 492 | "regex-automata 0.4.9", 493 | "regex-syntax", 494 | ] 495 | 496 | [[package]] 497 | name = "regex-automata" 498 | version = "0.1.10" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 501 | 502 | [[package]] 503 | name = "regex-automata" 504 | version = "0.4.9" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 507 | dependencies = [ 508 | "aho-corasick", 509 | "memchr 2.7.4", 510 | "regex-syntax", 511 | ] 512 | 513 | [[package]] 514 | name = "regex-syntax" 515 | version = "0.8.5" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 518 | 519 | [[package]] 520 | name = "rle-decode-fast" 521 | version = "1.0.3" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" 524 | 525 | [[package]] 526 | name = "rust-lzma" 527 | version = "0.6.0" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "7d62915608f6cee1d7f2fc00f28b4f058ff79d6e4ec3c2fe0006b09b52437c84" 530 | dependencies = [ 531 | "pkg-config", 532 | "vcpkg", 533 | ] 534 | 535 | [[package]] 536 | name = "rustix" 537 | version = "1.0.5" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 540 | dependencies = [ 541 | "bitflags 2.9.0", 542 | "errno", 543 | "libc", 544 | "linux-raw-sys", 545 | "windows-sys", 546 | ] 547 | 548 | [[package]] 549 | name = "shlex" 550 | version = "1.3.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 553 | 554 | [[package]] 555 | name = "take_mut" 556 | version = "0.2.2" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" 559 | 560 | [[package]] 561 | name = "tar" 562 | version = "0.4.44" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" 565 | dependencies = [ 566 | "filetime", 567 | "libc", 568 | ] 569 | 570 | [[package]] 571 | name = "tempfile" 572 | version = "3.19.1" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 575 | dependencies = [ 576 | "fastrand", 577 | "getrandom", 578 | "once_cell", 579 | "rustix", 580 | "windows-sys", 581 | ] 582 | 583 | [[package]] 584 | name = "termion" 585 | version = "4.0.5" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb" 588 | dependencies = [ 589 | "libc", 590 | "libredox", 591 | "numtoa", 592 | "redox_termios", 593 | ] 594 | 595 | [[package]] 596 | name = "termion" 597 | version = "4.0.5" 598 | source = "git+https://gitlab.redox-os.org/redox-os/termion.git#68f09b25b86281eb2238e86b6af2e32e26e96464" 599 | dependencies = [ 600 | "libc", 601 | "libredox", 602 | "numtoa", 603 | "redox_termios", 604 | ] 605 | 606 | [[package]] 607 | name = "termtree" 608 | version = "0.5.1" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 611 | 612 | [[package]] 613 | name = "time" 614 | version = "0.1.45" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 617 | dependencies = [ 618 | "libc", 619 | "wasi 0.10.0+wasi-snapshot-preview1", 620 | "winapi", 621 | ] 622 | 623 | [[package]] 624 | name = "tree_magic" 625 | version = "0.3.0" 626 | source = "git+https://github.com/aahancoc/tree_magic.git?rev=56fd014b4e4aefea7d1e37d4216595624511cefc#56fd014b4e4aefea7d1e37d4216595624511cefc" 627 | dependencies = [ 628 | "fnv", 629 | "lazy_static", 630 | "nom", 631 | "petgraph", 632 | ] 633 | 634 | [[package]] 635 | name = "vcpkg" 636 | version = "0.2.15" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 639 | 640 | [[package]] 641 | name = "wait-timeout" 642 | version = "0.2.1" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" 645 | dependencies = [ 646 | "libc", 647 | ] 648 | 649 | [[package]] 650 | name = "wasi" 651 | version = "0.10.0+wasi-snapshot-preview1" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 654 | 655 | [[package]] 656 | name = "wasi" 657 | version = "0.14.2+wasi-0.2.4" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 660 | dependencies = [ 661 | "wit-bindgen-rt", 662 | ] 663 | 664 | [[package]] 665 | name = "winapi" 666 | version = "0.3.9" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 669 | dependencies = [ 670 | "winapi-i686-pc-windows-gnu", 671 | "winapi-x86_64-pc-windows-gnu", 672 | ] 673 | 674 | [[package]] 675 | name = "winapi-i686-pc-windows-gnu" 676 | version = "0.4.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 679 | 680 | [[package]] 681 | name = "winapi-x86_64-pc-windows-gnu" 682 | version = "0.4.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 685 | 686 | [[package]] 687 | name = "windows-sys" 688 | version = "0.59.0" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 691 | dependencies = [ 692 | "windows-targets", 693 | ] 694 | 695 | [[package]] 696 | name = "windows-targets" 697 | version = "0.52.6" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 700 | dependencies = [ 701 | "windows_aarch64_gnullvm", 702 | "windows_aarch64_msvc", 703 | "windows_i686_gnu", 704 | "windows_i686_gnullvm", 705 | "windows_i686_msvc", 706 | "windows_x86_64_gnu", 707 | "windows_x86_64_gnullvm", 708 | "windows_x86_64_msvc", 709 | ] 710 | 711 | [[package]] 712 | name = "windows_aarch64_gnullvm" 713 | version = "0.52.6" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 716 | 717 | [[package]] 718 | name = "windows_aarch64_msvc" 719 | version = "0.52.6" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 722 | 723 | [[package]] 724 | name = "windows_i686_gnu" 725 | version = "0.52.6" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 728 | 729 | [[package]] 730 | name = "windows_i686_gnullvm" 731 | version = "0.52.6" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 734 | 735 | [[package]] 736 | name = "windows_i686_msvc" 737 | version = "0.52.6" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 740 | 741 | [[package]] 742 | name = "windows_x86_64_gnu" 743 | version = "0.52.6" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 746 | 747 | [[package]] 748 | name = "windows_x86_64_gnullvm" 749 | version = "0.52.6" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 752 | 753 | [[package]] 754 | name = "windows_x86_64_msvc" 755 | version = "0.52.6" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 758 | 759 | [[package]] 760 | name = "wit-bindgen-rt" 761 | version = "0.39.0" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 764 | dependencies = [ 765 | "bitflags 2.9.0", 766 | ] 767 | 768 | [[package]] 769 | name = "zip" 770 | version = "0.3.3" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "77ce0ceee93c995954a31f77903925a6a8bb094709445238e344f2107910e29e" 773 | dependencies = [ 774 | "bzip2", 775 | "flate2", 776 | "msdos_time", 777 | "podio", 778 | "time", 779 | ] 780 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "extrautils" 3 | version = "0.1.0" 4 | authors = ["Ticki "] 5 | 6 | [[bin]] 7 | name = "calc" 8 | path = "src/bin/calc.rs" 9 | 10 | [[bin]] 11 | name = "cur" 12 | path = "src/bin/cur.rs" 13 | 14 | [[bin]] 15 | name = "dmesg" 16 | path = "src/bin/dmesg.rs" 17 | 18 | [[bin]] 19 | name = "grep" 20 | path = "src/bin/grep.rs" 21 | 22 | [[bin]] 23 | name = "gunzip" 24 | path = "src/bin/gunzip.rs" 25 | 26 | [[bin]] 27 | name = "gzip" 28 | path = "src/bin/gzip.rs" 29 | 30 | [[bin]] 31 | name = "info" 32 | path = "src/bin/info.rs" 33 | 34 | [[bin]] 35 | name = "keymap" 36 | path = "src/bin/keymap.rs" 37 | 38 | [[bin]] 39 | name = "less" 40 | path = "src/bin/less.rs" 41 | 42 | [[bin]] 43 | name = "man" 44 | path = "src/bin/man.rs" 45 | 46 | [[bin]] 47 | name = "mdless" 48 | path = "src/bin/mdless.rs" 49 | 50 | [[bin]] 51 | name = "mtxt" 52 | path = "src/bin/mtxt.rs" 53 | 54 | [[bin]] 55 | name = "rem" 56 | path = "src/bin/rem.rs" 57 | 58 | [[bin]] 59 | name = "resize" 60 | path = "src/bin/resize.rs" 61 | 62 | [[bin]] 63 | name = "screenfetch" 64 | path = "src/bin/screenfetch.rs" 65 | 66 | [[bin]] 67 | name = "tar" 68 | path = "src/bin/tar.rs" 69 | 70 | [[bin]] 71 | name = "unzip" 72 | path = "src/bin/unzip.rs" 73 | 74 | [[bin]] 75 | name = "watch" 76 | path = "src/bin/watch.rs" 77 | 78 | [dependencies] 79 | arg_parser = { git = "https://gitlab.redox-os.org/redox-os/arg-parser.git" } 80 | extra = { git = "https://gitlab.redox-os.org/redox-os/libextra.git" } 81 | libflate = "0.1.4" 82 | os-release = "0.1.0" 83 | pager = { git = "https://gitlab.redox-os.org/redox-os/libpager.git" } 84 | libredox = "0.1" 85 | tar = { version = "0.4.27", default-features = false } 86 | filetime = { git = "https://github.com/jackpot51/filetime.git" } 87 | termion = "4" 88 | rust-lzma = {version = "0.6", features = ["static"]} 89 | tree_magic = { git = "https://github.com/aahancoc/tree_magic.git", rev = "56fd014b4e4aefea7d1e37d4216595624511cefc" } 90 | bzip2 = "0.3" 91 | zip = "0.3" 92 | 93 | [dev-dependencies] 94 | assert_cmd = "1" 95 | predicates = "1" 96 | tempfile = "3" 97 | 98 | [target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies] 99 | raw-cpuid = "10.2.0" 100 | 101 | [patch.crates-io] 102 | filetime = { git = "https://github.com/jackpot51/filetime.git" } 103 | cc-11 = { git = "https://github.com/tea/cc-rs", branch="riscv-abi-arch-fix", package = "cc" } 104 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Ticki 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redox OS extrautils 2 | 3 | This repository contains additional UNIX utilities for Redox OS. 4 | 5 | [![Travis Build Status](https://travis-ci.org/redox-os/extrautils.svg?branch=master)](https://travis-ci.org/redox-os/extrautils) 6 | 7 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | - [~] calc 2 | - [ ] cal 3 | - [ ] rd (less) 4 | - [~] sc (spellcheck) 5 | - [ ] shuf 6 | - [ ] rand 7 | - [x] djb2 8 | - [ ] rep (replace) 9 | - [x] cur (freely move cursor) 10 | - [ ] look 11 | - [ ] find 12 | - [ ] search (search) 13 | - [ ] unansi 14 | - [ ] mux (multiplex) 15 | - [ ] fail (fail if stderr is non-empty) 16 | - [x] mtxt (manipulate text) 17 | - [ ] rem (count-down/reminder) 18 | -------------------------------------------------------------------------------- /src/bin/calc.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | use extra::io::{fail, WriteExt}; 3 | use extra::option::OptionalExt; 4 | 5 | use std::env::args; 6 | use std::fmt; 7 | use std::io::{self, Write}; 8 | 9 | #[derive(Debug, Clone)] 10 | pub enum Token { 11 | Plus, 12 | Minus, 13 | Divide, 14 | Multiply, 15 | Exponent, 16 | OpenParen, 17 | CloseParen, 18 | Comma, 19 | Number(String), 20 | Identificator(String), 21 | } 22 | 23 | impl Token { 24 | pub fn to_str(&self) -> &'static str { 25 | match self { 26 | Token::Plus => "Plus", 27 | Token::Minus => "Minus", 28 | Token::Divide => "Divide", 29 | Token::Multiply => "Multiply", 30 | Token::Exponent => "Exponent", 31 | Token::OpenParen => "OpenParen", 32 | Token::CloseParen => "CloseParen", 33 | Token::Comma => "comma", 34 | Token::Number(_) => "Number", 35 | Token::Identificator(_) => "Identificator", 36 | } 37 | } 38 | } 39 | 40 | impl fmt::Display for Token { 41 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 42 | write!(f, "{}", self.to_str()) 43 | } 44 | } 45 | 46 | #[derive(Debug, Clone)] 47 | pub enum ParseError { 48 | InvalidNumber(String), 49 | UnrecognizedToken(String), 50 | UnexpectedToken(String, &'static str), 51 | UnexpectedEndOfInput, 52 | UnexpectedNumberOfArgs(usize, usize), 53 | OtherError(String), 54 | } 55 | 56 | #[derive(Clone, Debug)] 57 | pub struct IntermediateResult { 58 | value: f64, 59 | tokens_read: usize, 60 | } 61 | 62 | impl IntermediateResult { 63 | fn new(value: f64, tokens_read: usize) -> Self { 64 | IntermediateResult { value, tokens_read } 65 | } 66 | } 67 | 68 | pub trait OperatorFunctions { 69 | fn is_operator(self) -> bool; 70 | fn operator_type(self) -> Token; 71 | } 72 | 73 | impl OperatorFunctions for char { 74 | fn is_operator(self) -> bool { 75 | self == '+' 76 | || self == '-' 77 | || self == '*' 78 | || self == '/' 79 | || self == '^' 80 | || self == '(' 81 | || self == ')' 82 | } 83 | 84 | fn operator_type(self) -> Token { 85 | match self { 86 | '+' => Token::Plus, 87 | '-' => Token::Minus, 88 | '/' => Token::Divide, 89 | '*' => Token::Multiply, 90 | '^' => Token::Exponent, 91 | '(' => Token::OpenParen, 92 | ')' => Token::CloseParen, 93 | _ => fail("Invalid operator", &mut io::stderr()), 94 | } 95 | } 96 | } 97 | 98 | pub fn tokenize(input: &str) -> Result, ParseError> { 99 | let mut tokens = Vec::with_capacity(input.len()); 100 | 101 | // TODO: Not this. Modify to use iterator 102 | let chars: Vec = input.chars().collect(); 103 | 104 | let input_length = chars.len(); 105 | let mut current_pos = 0; 106 | while current_pos < input_length { 107 | let c = chars[current_pos]; 108 | if c.is_digit(10) || c == '.' { 109 | let token_string = consume_number(&chars[current_pos..]); 110 | current_pos += token_string.len(); 111 | tokens.push(Token::Number(token_string)); 112 | } else if c.is_operator() { 113 | tokens.push(c.operator_type()); 114 | current_pos += 1; 115 | } else if c.is_whitespace() { 116 | current_pos += 1; 117 | } else if c.is_alphabetic() { 118 | let token_string = consume_ident(&chars[current_pos..]); 119 | current_pos += token_string.len(); 120 | tokens.push(Token::Identificator(token_string)); 121 | } else if c == ',' { 122 | tokens.push(Token::Comma); 123 | current_pos += 1; 124 | } else { 125 | let token_string = consume_until_new_token(&chars[current_pos..]); 126 | return Err(ParseError::UnrecognizedToken(token_string)); 127 | } 128 | } 129 | Ok(tokens) 130 | } 131 | 132 | fn consume_number(input: &[char]) -> String { 133 | let mut number = String::with_capacity(input.len()); 134 | let mut has_decimal_point = false; 135 | for &c in input { 136 | if c == '.' { 137 | if has_decimal_point { 138 | break; 139 | } else { 140 | number.push(c); 141 | has_decimal_point = true; 142 | } 143 | } else if c.is_digit(10) { 144 | number.push(c); 145 | } else { 146 | break; 147 | } 148 | } 149 | number 150 | } 151 | 152 | fn consume_ident(input: &[char]) -> String { 153 | let mut ident = String::with_capacity(input.len()); 154 | for &c in input { 155 | if c.is_alphanumeric() { 156 | ident.push(c); 157 | } else { 158 | break; 159 | } 160 | } 161 | ident 162 | } 163 | 164 | fn consume_until_new_token(input: &[char]) -> String { 165 | input 166 | .iter() 167 | .take_while(|c| !(c.is_whitespace() || c.is_operator() || c.is_digit(10))) 168 | .copied() 169 | .collect() 170 | } 171 | 172 | // Addition and subtraction 173 | pub fn e_expr(token_list: &[Token]) -> Result { 174 | let mut t1 = t_expr(token_list)?; 175 | let mut index = t1.tokens_read; 176 | 177 | while index < token_list.len() { 178 | match token_list[index] { 179 | Token::Plus => { 180 | let t2 = t_expr(&token_list[index + 1..])?; 181 | t1.value += t2.value; 182 | t1.tokens_read += t2.tokens_read + 1; 183 | } 184 | Token::Minus => { 185 | let t2 = t_expr(&token_list[index + 1..])?; 186 | t1.value -= t2.value; 187 | t1.tokens_read += t2.tokens_read + 1; 188 | } 189 | Token::Number(ref n) => return Err(ParseError::UnexpectedToken(n.clone(), "operator")), 190 | _ => break, 191 | }; 192 | index = t1.tokens_read; 193 | } 194 | Ok(t1) 195 | } 196 | 197 | // Multiplication and division 198 | pub fn t_expr(token_list: &[Token]) -> Result { 199 | let mut f1 = f_expr(token_list)?; 200 | let mut index = f1.tokens_read; 201 | 202 | while index < token_list.len() { 203 | match token_list[index] { 204 | Token::Multiply => { 205 | let f2 = f_expr(&token_list[index + 1..])?; 206 | f1.value *= f2.value; 207 | f1.tokens_read += f2.tokens_read + 1; 208 | } 209 | Token::Divide => { 210 | let f2 = f_expr(&token_list[index + 1..])?; 211 | if f2.value == 0.0 { 212 | return Err(ParseError::OtherError("Divide by zero error".to_owned())); 213 | } else { 214 | f1.value /= f2.value; 215 | f1.tokens_read += f2.tokens_read + 1; 216 | } 217 | } 218 | Token::Number(ref n) => return Err(ParseError::UnexpectedToken(n.clone(), "operator")), 219 | _ => break, 220 | } 221 | index = f1.tokens_read; 222 | } 223 | Ok(f1) 224 | } 225 | 226 | // Exponentiation 227 | pub fn f_expr(token_list: &[Token]) -> Result { 228 | let mut fn1 = i_expr(token_list)?; 229 | let mut index = fn1.tokens_read; 230 | let token_len = token_list.len(); 231 | while index < token_len { 232 | match token_list[index] { 233 | Token::Exponent => { 234 | let f = f_expr(&token_list[index + 1..])?; 235 | fn1.value = fn1.value.powf(f.value); 236 | fn1.tokens_read += f.tokens_read + 1; 237 | } 238 | Token::Number(ref n) => return Err(ParseError::UnexpectedToken(n.clone(), "operator")), 239 | _ => break, 240 | } 241 | index = fn1.tokens_read; 242 | } 243 | Ok(fn1) 244 | } 245 | 246 | // Functions and variables(TODO) 247 | pub fn i_expr(token_list: &[Token]) -> Result { 248 | if token_list.is_empty() { 249 | return Err(ParseError::UnexpectedEndOfInput); 250 | } 251 | match token_list[0] { 252 | Token::Identificator(ref ident) => { 253 | match token_list.get(1) { 254 | Some(Token::OpenParen) => {} 255 | _ => { 256 | return Err(ParseError::UnexpectedToken( 257 | token_list[0].to_string(), 258 | "parenthesis", 259 | )); 260 | } 261 | } 262 | let mut i = 2; 263 | let mut close_parent = false; 264 | let mut args = Vec::new(); 265 | while i < token_list.len() { 266 | if let Token::CloseParen = token_list[i] { 267 | close_parent = true; 268 | i += 1; 269 | break; 270 | } 271 | if i != 2 { 272 | if let Token::Comma = token_list[i] { 273 | i += 1; 274 | } else { 275 | return Err(ParseError::UnexpectedToken( 276 | token_list[i].to_string(), 277 | "comma", 278 | )); 279 | } 280 | } 281 | let expr = e_expr(&token_list[i..])?; 282 | i += expr.tokens_read; 283 | args.push(expr.value); 284 | } 285 | if !close_parent { 286 | return Err(ParseError::OtherError( 287 | "no matching close parenthesis found.".to_owned(), 288 | )); 289 | } 290 | macro_rules! functions_processor { 291 | ($($ident: expr ; $args_count: expr => $proc: expr),*) => ({ 292 | match &ident[..] { 293 | $( 294 | $ident => { 295 | if args.len() != $args_count { 296 | return Err(ParseError::UnexpectedNumberOfArgs($args_count, args.len())); 297 | } 298 | $proc(args) 299 | } 300 | )* 301 | f => { 302 | return Err(ParseError::OtherError(format!("the function \"{}\" is unsupported.", f))); 303 | } 304 | } 305 | }); 306 | } 307 | type A = Vec; 308 | let result = functions_processor!( 309 | "abs";1 => |n:A| n[0].abs(), 310 | "acos";1 => |n:A| n[0].acos(), 311 | "acosh";1 => |n:A| n[0].acosh(), 312 | "asin";1 => |n:A| n[0].asin(), 313 | "asinh";1 => |n:A| n[0].asinh(), 314 | "atan";1 => |n:A| n[0].atan(), 315 | "atan2";2 => |n:A| f64::atan2(n[0], n[1]), 316 | "atanh";1 => |n:A| n[0].atanh(), 317 | "cbrt";1 => |n:A| n[0].cbrt(), 318 | "ceil";1 => |n:A| n[0].ceil(), 319 | "clamp";3 => |n:A| n[0].max(n[1]).min(n[2]), 320 | "copysign";2 => |n:A| f64::copysign(n[0], n[1]), 321 | "cos";1 => |n:A| n[0].cos(), 322 | "cosh";1 => |n:A| n[0].cosh(), 323 | "floor";1 => |n:A| n[0].floor(), 324 | "fract";1 => |n:A| n[0].fract(), 325 | "hypot";2 => |n:A| f64::hypot(n[0], n[1]), 326 | "ln";1 => |n:A| n[0].ln(), 327 | "ln_1p";1 => |n:A| n[0].ln_1p(), 328 | "log";2 => |n:A| f64::log(n[0], n[1]), 329 | "log10";1 => |n:A| n[0].log10(), 330 | "log2";1 => |n:A| n[0].log2(), 331 | "max";2 => |n:A| f64::max(n[0], n[1]), 332 | "min";2 => |n:A| f64::min(n[0], n[1]), 333 | "mul_add";3 => |n:A| f64::mul_add(n[0], n[1], n[2]), 334 | "recip";1 => |n:A| n[0].recip(), 335 | "round";1 => |n:A| n[0].round(), 336 | "signum";1 => |n:A| n[0].signum(), 337 | "sin";1 => |n:A| n[0].sin(), 338 | "sinh";1 => |n:A| n[0].sinh(), 339 | "sqrt";1 => |n:A| n[0].sqrt(), 340 | "tan";1 => |n:A| n[0].tan(), 341 | "tanh";1 => |n:A| n[0].tanh(), 342 | "trunc";1 => |n:A| n[0].trunc()); 343 | Ok(IntermediateResult::new(result, i)) 344 | } 345 | _ => g_expr(token_list), 346 | } 347 | } 348 | 349 | // Numbers and parenthesized expressions 350 | pub fn g_expr(token_list: &[Token]) -> Result { 351 | if !token_list.is_empty() { 352 | match token_list[0] { 353 | Token::Number(ref n) => n 354 | .parse::() 355 | .map_err(|_| ParseError::InvalidNumber(n.clone())) 356 | .map(|num| IntermediateResult::new(num, 1)), 357 | Token::Minus => { 358 | if token_list.len() > 1 { 359 | if let Token::Number(ref n) = token_list[1] { 360 | n.parse::() 361 | .map_err(|_| ParseError::InvalidNumber(n.clone())) 362 | .map(|num| IntermediateResult::new(-1.0 * num, 2)) 363 | } else { 364 | Err(ParseError::UnexpectedToken( 365 | token_list[1].to_string(), 366 | "number", 367 | )) 368 | } 369 | } else { 370 | Err(ParseError::UnexpectedEndOfInput) 371 | } 372 | } 373 | Token::OpenParen => { 374 | let expr = e_expr(&token_list[1..]); 375 | match expr { 376 | Ok(ir) => { 377 | let close_paren = ir.tokens_read + 1; 378 | if close_paren < token_list.len() { 379 | match token_list[close_paren] { 380 | Token::CloseParen => { 381 | Ok(IntermediateResult::new(ir.value, close_paren + 1)) 382 | } 383 | _ => Err(ParseError::UnexpectedToken( 384 | token_list[close_paren].to_string(), 385 | ")", 386 | )), 387 | } 388 | } else { 389 | Err(ParseError::OtherError( 390 | "no matching close parenthesis found.".to_owned(), 391 | )) 392 | } 393 | } 394 | Err(e) => Err(e), 395 | } 396 | } 397 | _ => Err(ParseError::UnexpectedToken( 398 | token_list[0].to_string(), 399 | "number", 400 | )), 401 | } 402 | } else { 403 | Err(ParseError::UnexpectedEndOfInput) 404 | } 405 | } 406 | 407 | pub fn parse(tokens: Vec) -> Result { 408 | e_expr(&tokens).map(|answer| answer.value.to_string()) 409 | } 410 | 411 | #[cfg(test)] 412 | mod test { 413 | use super::*; 414 | 415 | #[test] 416 | fn simple_addition() { 417 | assert_eq!(tokenize("12+3").and_then(parse).unwrap(), "15"); 418 | } 419 | 420 | #[test] 421 | fn addition() { 422 | assert_eq!(tokenize("12+3+5").and_then(parse).unwrap(), "20"); 423 | } 424 | 425 | #[test] 426 | fn simple_subtraction() { 427 | assert_eq!(tokenize("12-3").and_then(parse).unwrap(), "9"); 428 | } 429 | 430 | #[test] 431 | fn subtraction() { 432 | assert_eq!(tokenize("12-3-4").and_then(parse).unwrap(), "5"); 433 | } 434 | 435 | #[test] 436 | fn mixed_addition_and_subtraction() { 437 | assert_eq!(tokenize("12+3-4+8-2-3").and_then(parse).unwrap(), "14"); 438 | } 439 | 440 | #[test] 441 | fn simple_parentheses() { 442 | assert_eq!(tokenize("((3))").and_then(parse).unwrap(), "3"); 443 | assert_eq!(tokenize("(12+(2+3))").and_then(parse).unwrap(), "17"); 444 | assert_eq!(tokenize("12+(2+3)").and_then(parse).unwrap(), "17"); 445 | } 446 | 447 | #[test] 448 | fn parentheses() { 449 | assert_eq!( 450 | tokenize("12+(2+(3+5))+4+(((6)))").and_then(parse).unwrap(), 451 | "32" 452 | ); 453 | } 454 | 455 | #[test] 456 | fn multiplication() { 457 | assert_eq!(tokenize("3*3").and_then(parse).unwrap(), "9"); 458 | assert_eq!(tokenize("3*5").and_then(parse).unwrap(), "15"); 459 | assert_eq!(tokenize("0*5").and_then(parse).unwrap(), "0"); 460 | assert_eq!(tokenize("5*4*3*2*1").and_then(parse).unwrap(), "120"); 461 | assert_eq!(tokenize("(5*4)*3*(2*1)").and_then(parse).unwrap(), "120"); 462 | } 463 | 464 | #[test] 465 | fn division() { 466 | assert_eq!(tokenize("12/4").and_then(parse).unwrap(), "3"); 467 | assert_eq!(tokenize("12/3").and_then(parse).unwrap(), "4"); 468 | assert_eq!(tokenize("5/2").and_then(parse).unwrap(), "2.5"); 469 | assert_eq!(tokenize("120/5/4/3/2").and_then(parse).unwrap(), "1"); 470 | assert_eq!(tokenize("(120/5)/4/(3/2)").and_then(parse).unwrap(), "4"); 471 | } 472 | 473 | #[test] 474 | fn exponentiation() { 475 | assert_eq!(tokenize("3^2").and_then(parse).unwrap(), "9"); 476 | assert_eq!(tokenize("2^3^2").and_then(parse).unwrap(), "512"); 477 | assert_eq!(tokenize("2^(2+1)^2").and_then(parse).unwrap(), "512"); 478 | } 479 | 480 | #[test] 481 | fn functions() { 482 | assert_eq!(tokenize("signum(-42)").and_then(parse).unwrap(), "-1"); 483 | assert_eq!( 484 | tokenize("copysign(-123, 3)").and_then(parse).unwrap(), 485 | "123" 486 | ); 487 | assert_eq!( 488 | tokenize("min(5, max(3, 9))+trunc(9.1)") 489 | .and_then(parse) 490 | .unwrap(), 491 | "14" 492 | ); 493 | } 494 | } 495 | 496 | fn eval(input: &str) -> String { 497 | match tokenize(input).and_then(parse) { 498 | Ok(s) => s, 499 | Err(e) => match e { 500 | ParseError::InvalidNumber(s) => ["Error: Invalid number: ", s.as_str()].concat(), 501 | ParseError::UnrecognizedToken(s) => { 502 | ["Error: Unrecognized token: ", s.as_str()].concat() 503 | } 504 | ParseError::UnexpectedToken(found, expected) => [ 505 | "Error: Unexpected token: expected [", 506 | expected, 507 | "] but found '", 508 | found.as_str(), 509 | "'", 510 | ] 511 | .concat(), 512 | ParseError::UnexpectedEndOfInput => "Error: Unexpected end of input.".to_owned(), 513 | ParseError::UnexpectedNumberOfArgs(expected, passed) => { 514 | format!("Error: Expected {} arguments, found {}", expected, passed) 515 | } 516 | ParseError::OtherError(s) => s, 517 | }, 518 | } 519 | } 520 | 521 | fn main() { 522 | let args = args(); 523 | let stdout = io::stdout(); 524 | let mut stdout = stdout.lock(); 525 | let mut stderr = io::stderr(); 526 | if args.len() > 1 { 527 | let input: Vec = args.skip(1).collect(); 528 | stdout 529 | .writeln(eval(&input.join("")).as_bytes()) 530 | .try(&mut stderr); 531 | } else { 532 | loop { 533 | print!("[]> "); 534 | stdout.flush().try(&mut stderr); 535 | let mut input = String::new(); 536 | io::stdin().read_line(&mut input).try(&mut stderr); 537 | if input.is_empty() { 538 | break; 539 | } else { 540 | match input.trim() { 541 | "" => (), 542 | "exit" => break, 543 | s => { 544 | stdout.writeln(eval(s).as_bytes()).try(&mut stderr); 545 | stdout.flush().try(&mut stderr); 546 | } 547 | } 548 | } 549 | } 550 | } 551 | } 552 | -------------------------------------------------------------------------------- /src/bin/cur.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | 3 | use std::env::args; 4 | use std::io::{self, Read, Write}; 5 | 6 | use extra::io::fail; 7 | 8 | static MAN_PAGE: &str = /* @MANSTART{cur} */ r#" 9 | NAME 10 | cur - freely move you cursor using H, J, K, and L (Vi-bindings). 11 | 12 | SYNOPSIS 13 | cur [-h | --help] 14 | 15 | DESCRIPTION 16 | This utility will let you navigate the terminal cursor using standard Vi bindings (h, j, k, and 17 | l) by using ANSI escape codes. 18 | 19 | In combination with other tools, this can be used as a very simple pager. 20 | 21 | OPTIONS 22 | -h 23 | --help 24 | Print this manual page. 25 | 26 | AUTHOR 27 | This program was written by Ticki for Redox OS. Bugs, issues, or feature requests should be 28 | reported in the Gitlab repository, 'redox-os/extrautils'. 29 | 30 | COPYRIGHT 31 | Copyright (c) 2016 Ticki 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 34 | and associated documentation files (the "Software"), to deal in the Software without 35 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 36 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 37 | Software is furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in all copies or 40 | substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 43 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 44 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 45 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 47 | "#; /* @MANEND */ 48 | 49 | fn main() { 50 | let args = args().skip(1); 51 | let stdin = io::stdin(); 52 | let mut stdin = stdin.lock(); 53 | let stdout = io::stdout(); 54 | let mut stdout = stdout.lock(); 55 | let mut stderr = io::stderr(); 56 | 57 | for i in args { 58 | match i.as_str() { 59 | // Print the help page. 60 | "-h" | "--help" => { 61 | print!("{}", MAN_PAGE); 62 | } 63 | // This argument is unknown. 64 | _ => fail("unknown argument.", &mut stderr), 65 | } 66 | } 67 | 68 | loop { 69 | // We read one byte at a time from stdin. 70 | let mut input = [0]; 71 | let _ = stdin.read(&mut input); 72 | 73 | // Output the right escape code to stdout. 74 | match input[0] { 75 | b'k' => print!("\x1b[A"), 76 | b'j' => print!("\x1b[B"), 77 | b'l' => print!("\x1b[C"), 78 | b'h' => print!("\x1b[D"), 79 | b'q' => break, 80 | _ => {} 81 | } 82 | 83 | // Flush it. 84 | let _ = stdout.flush(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/bin/dmesg.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | 3 | use std::env; 4 | use std::io::{stderr, stdout, Write}; 5 | use std::process::{exit, Command}; 6 | 7 | use extra::option::OptionalExt; 8 | 9 | const MAN_PAGE: &str = /* @MANSTART{dmesg} */ r#" 10 | NAME 11 | dmesg - display the system message buffer 12 | 13 | SYNOPSIS 14 | dmesg [ -h | --help] 15 | 16 | DESCRIPTION 17 | Displays the contents of the system message buffer. 18 | 19 | OPTIONS 20 | -h 21 | --help 22 | display this help and exit 23 | "#; /* @MANEND */ 24 | 25 | fn main() { 26 | let stdout = stdout(); 27 | let mut stdout = stdout.lock(); 28 | let mut stderr = stderr(); 29 | 30 | for arg in env::args().skip(1) { 31 | if arg.as_str() == "-h" || arg.as_str() == "--help" { 32 | print!("{}", MAN_PAGE); 33 | stdout.flush().try(&mut stderr); 34 | exit(0); 35 | } 36 | } 37 | 38 | Command::new("less") 39 | .arg("/scheme/sys/log") 40 | .spawn() 41 | .unwrap() 42 | .wait() 43 | .unwrap(); 44 | } 45 | -------------------------------------------------------------------------------- /src/bin/grep.rs: -------------------------------------------------------------------------------- 1 | extern crate arg_parser; 2 | extern crate extra; 3 | 4 | use arg_parser::ArgParser; 5 | use std::env; 6 | use std::fs::File; 7 | use std::io; 8 | use std::io::{BufRead, BufReader}; 9 | use std::path::Path; 10 | use std::process::exit; 11 | 12 | static MAN_PAGE: &str = /* @MANSTART{grep} */ r#" 13 | NAME 14 | grep - print lines matching a pattern 15 | 16 | SYNOPSIS 17 | grep [--help] [-chHinqv] [-m NUM] PATTERN [FILE...] 18 | 19 | DESCRIPTION 20 | grep searches the named input FILEs for lines containing a match to the given PATTERN. If no 21 | files are specified, grep searches the standard input. grep prints the matching lines. 22 | 23 | OPTIONS 24 | -c 25 | --count 26 | Print count of matching lines, instead of those lines. 27 | 28 | -H 29 | --with-filename 30 | Include filename header with each match (default for multiple files). 31 | 32 | -h 33 | --no-filename 34 | Never include filename header (default for single files or stdin). 35 | 36 | --help 37 | Print this manual page. 38 | 39 | -i 40 | --ignore-case 41 | Make matching case insensitive. 42 | 43 | -m NUM 44 | --max-count=NUM 45 | Stop searching after NUM matches. 46 | 47 | -n 48 | --line-number 49 | Prefix each line of output with the line number of the match. 50 | 51 | -q 52 | --quiet 53 | Suppress normal output and stop searching as soon as a match is found. 54 | 55 | -v 56 | --invert-match 57 | Invert matching. 58 | "#; /* @MANEND */ 59 | 60 | #[derive(Copy, Clone)] 61 | struct Flags { 62 | count: bool, 63 | ignore_case: bool, 64 | invert_match: bool, 65 | line_numbers: bool, 66 | quiet: bool, 67 | with_filenames: bool, 68 | without_filenames: bool, 69 | max_count: Option, 70 | } 71 | 72 | impl Flags { 73 | fn new() -> Flags { 74 | Flags { 75 | count: false, 76 | ignore_case: false, 77 | invert_match: false, 78 | line_numbers: false, 79 | quiet: false, 80 | with_filenames: false, 81 | without_filenames: false, 82 | max_count: None, 83 | } 84 | } 85 | } 86 | 87 | fn main() { 88 | let stdin = io::stdin(); 89 | let stdin = stdin.lock(); 90 | 91 | let mut flags = Flags::new(); 92 | let mut parser = ArgParser::new(8) 93 | .add_flag(&["help"]) 94 | .add_flag(&["c", "count"]) 95 | .add_flag(&["H", "with-filename"]) 96 | .add_flag(&["h", "no-filename"]) 97 | .add_flag(&["i", "ignore-case"]) 98 | .add_flag(&["n", "line-number"]) 99 | .add_flag(&["q", "quiet"]) 100 | .add_flag(&["v", "invert-match"]) 101 | .add_opt("m", "max-count"); 102 | parser.parse(env::args()); 103 | 104 | if parser.found("help") { 105 | print!("{}", MAN_PAGE); 106 | exit(0); 107 | } 108 | flags.count |= parser.found("count"); 109 | flags.with_filenames |= parser.found("with-filename"); 110 | flags.without_filenames |= parser.found("no-filename"); 111 | flags.ignore_case |= parser.found("ignore-case"); 112 | flags.line_numbers |= parser.found("line-number"); 113 | flags.quiet |= parser.found("quiet"); 114 | flags.invert_match |= parser.found("invert-match"); 115 | 116 | if let Some(mstr) = parser.get_opt("max-count") { 117 | flags.max_count = match mstr.parse::() { 118 | Ok(0) => exit(1), 119 | Ok(m) => Some(m), 120 | Err(e) => { 121 | eprintln!("Invalid max count {}: {}", mstr, e); 122 | exit(2); 123 | } 124 | }; 125 | } 126 | 127 | if let Err(e) = parser.found_invalid() { 128 | eprint!("{}", e); 129 | exit(2); 130 | } 131 | if parser.args.is_empty() { 132 | eprintln!("You must provide a pattern"); 133 | exit(2); 134 | } 135 | 136 | let mut pattern = parser.args[0].clone(); 137 | let files = &parser.args[1..]; 138 | 139 | if !flags.without_filenames && files.len() > 1 { 140 | flags.with_filenames = true; 141 | } else if flags.with_filenames && flags.without_filenames { 142 | // FIXME: Unfortunately, with ArgParser we don't have a way to tell 143 | // which order flags were received in. If a user has, say, an alias 144 | // like `grep -H` but then runs `grep -h` at the command line, we see 145 | // `grep -H -h` but can't distinguish it from `grep -h -H`. The last 146 | // flag should win, since that's clearly the user's intent. 147 | eprintln!("WARNING: filename flag overrides not yet supported"); 148 | } 149 | if flags.ignore_case { 150 | pattern = pattern.to_lowercase(); 151 | } 152 | 153 | let mut found = false; 154 | let mut error = false; 155 | if files.is_empty() { 156 | found = do_simple_search(BufReader::new(stdin), "(standard input)", &pattern, flags); 157 | } else { 158 | for path in files { 159 | match File::open(&Path::new(&path)) { 160 | Ok(f) => { 161 | found |= do_simple_search(BufReader::new(f), &path, &pattern, flags); 162 | } 163 | Err(err) => { 164 | eprintln!("Error opening {}: {}", path, err); 165 | error = true; 166 | } 167 | } 168 | } 169 | } 170 | if error { 171 | exit(2); 172 | } 173 | if !found { 174 | exit(1); 175 | } 176 | } 177 | 178 | fn do_simple_search(reader: T, path: &str, pattern: &str, flags: Flags) -> bool { 179 | let mut count = 0; 180 | for (line_num, result) in reader.lines().enumerate() { 181 | if let Ok(line) = result { 182 | let mut is_match = if flags.ignore_case { 183 | line.to_lowercase().contains(pattern) 184 | } else { 185 | line.contains(pattern) 186 | }; 187 | if flags.invert_match { 188 | is_match = !is_match 189 | } 190 | if is_match { 191 | if flags.quiet { 192 | return true; 193 | } 194 | count += 1; 195 | if !flags.count { 196 | if flags.with_filenames { 197 | print!("{}:", path); 198 | } 199 | if flags.line_numbers { 200 | print!("{}:", line_num + 1); 201 | } 202 | println!("{}", line); 203 | } 204 | if let Some(m) = flags.max_count { 205 | if count >= m { 206 | break; 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | if flags.count && !flags.quiet { 214 | if flags.with_filenames { 215 | print!("{}:", path); 216 | } 217 | println!("{}", count); 218 | } 219 | 220 | count > 0 221 | } 222 | -------------------------------------------------------------------------------- /src/bin/gunzip.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | extern crate libflate; 3 | 4 | use extra::option::OptionalExt; 5 | use libflate::gzip::Decoder; 6 | use std::io::Write; 7 | use std::{env, fs, io, process}; 8 | 9 | fn main() { 10 | let mut stderr = io::stderr(); 11 | 12 | let mut keep = false; 13 | let mut files = Vec::new(); 14 | for arg in env::args().skip(1) { 15 | if arg == "-k" { 16 | keep = true; 17 | } else { 18 | files.push(arg) 19 | } 20 | } 21 | 22 | if files.is_empty() { 23 | writeln!(stderr, "gunzip: no files provided").unwrap(); 24 | process::exit(1); 25 | } 26 | 27 | for arg in files { 28 | if arg.ends_with(".gz") { 29 | { 30 | let input = fs::File::open(&arg).try(&mut stderr); 31 | let mut decoder = Decoder::new(input).try(&mut stderr); 32 | 33 | let mut output = fs::File::create(&arg.trim_end_matches(".gz")).try(&mut stderr); 34 | io::copy(&mut decoder, &mut output).try(&mut stderr); 35 | 36 | output.flush().try(&mut stderr); 37 | } 38 | if !keep { 39 | fs::remove_file(&arg).try(&mut stderr); 40 | } 41 | } else { 42 | writeln!(stderr, "gunzip: {}: unknown suffix", arg).unwrap(); 43 | process::exit(2); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/bin/gzip.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | extern crate libflate; 3 | 4 | use extra::option::OptionalExt; 5 | use libflate::gzip::Encoder; 6 | use std::io::Write; 7 | use std::{env, fs, io, process}; 8 | 9 | fn main() { 10 | let mut stderr = io::stderr(); 11 | 12 | let mut keep = false; 13 | let mut files = Vec::new(); 14 | for arg in env::args().skip(1) { 15 | if arg == "-k" { 16 | keep = true; 17 | } else { 18 | files.push(arg) 19 | } 20 | } 21 | 22 | if files.is_empty() { 23 | eprintln!("gzip: no files provided"); 24 | process::exit(1); 25 | } 26 | 27 | for arg in files { 28 | { 29 | let output = fs::File::create(&format!("{}.gz", &arg)).try(&mut stderr); 30 | let mut encoder = Encoder::new(output).try(&mut stderr); 31 | 32 | let mut input = fs::File::open(&arg).try(&mut stderr); 33 | io::copy(&mut input, &mut encoder).try(&mut stderr); 34 | 35 | let mut encoded = encoder.finish().into_result().try(&mut stderr); 36 | encoded.flush().try(&mut stderr); 37 | } 38 | if !keep { 39 | fs::remove_file(&arg).try(&mut stderr); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/bin/info.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process::Command; 3 | 4 | static MAN_PAGE: &str = /* @MANSTART{info} */ r#" 5 | NAME 6 | info - view an info page. 7 | 8 | SYNOPSIS 9 | info [-h | --help] [page] 10 | 11 | DESCRIPTION 12 | This utility launches mdless with an info file. 13 | 14 | OPTIONS 15 | --help, -h 16 | Print this manual page. 17 | 18 | AUTHOR 19 | This program was written by Jeremy Soller for Redox OS. Bugs, issues, or feature requests 20 | should be reported in the Gitlab repository, 'redox-os/extrautils'. 21 | 22 | COPYRIGHT 23 | Copyright (c) 2016 Jeremy Soller 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 26 | and associated documentation files (the "Software"), to deal in the Software without 27 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 28 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 29 | Software is furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in all copies or 32 | substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 35 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 36 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 37 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 39 | "#; /* @MANEND */ 40 | 41 | fn main() { 42 | if let Some(arg) = env::args().nth(1) { 43 | match arg.as_str() { 44 | "--help" | "-h" => { 45 | print!("{}", MAN_PAGE); 46 | } 47 | page => { 48 | Command::new("mdless") 49 | .arg(&("/info/".to_owned() + page)) 50 | .spawn() 51 | .unwrap() 52 | .wait() 53 | .unwrap(); 54 | } 55 | } 56 | } else { 57 | Command::new("mdless") 58 | .arg(&("/info/index.md".to_owned())) 59 | .spawn() 60 | .unwrap() 61 | .wait() 62 | .unwrap(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/bin/keymap.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | 3 | use std::fs::File; 4 | use std::io::Write; 5 | use std::process::exit; 6 | use std::{env, str}; 7 | 8 | static MAN_PAGE: &str = /* @MANSTART{keymap} */ r#" 9 | NAME 10 | keymap - change the keymap 11 | 12 | SYNOPSIS 13 | keymap [-h | --help] [-l --list] NAME 14 | 15 | DESCRIPTION 16 | Changes the keymap. 17 | OPTIONS 18 | -h 19 | --help 20 | Print this manual page. 21 | 22 | -l 23 | --list 24 | List available keymaps. 25 | "#; /* @MANEND */ 26 | 27 | fn main() { 28 | let mut args = env::args().skip(1); 29 | 30 | let arg = match args.next() { 31 | Some(arg) => arg, 32 | None => { 33 | eprintln!("Must specify keymap name."); 34 | exit(1); 35 | } 36 | }; 37 | let path = if arg.starts_with('-') { 38 | match arg.as_str() { 39 | "-h" | "--help" => { 40 | print!("{}", MAN_PAGE); 41 | } 42 | "-l" | "--list" => { 43 | // TODO list keymaps 44 | } 45 | _ => { 46 | eprintln!("Unknown option: {}", arg); 47 | exit(1); 48 | } 49 | } 50 | exit(0); 51 | } else { 52 | arg 53 | }; 54 | 55 | match File::open("/scheme/display/keymap") { 56 | Ok(mut file) => { 57 | if let Err(e) = file.write(path.as_bytes()) { 58 | eprintln!("keymap: could not change keymap: {}", e); 59 | exit(1); 60 | } 61 | } 62 | Err(err) => { 63 | eprintln!("keymap: failed to open display: {}", err); 64 | exit(1); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/bin/less.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | extern crate pager; 3 | extern crate termion; 4 | 5 | use std::env::args; 6 | use std::fs::File; 7 | use std::io::{self, Read}; 8 | use std::path::Path; 9 | 10 | use extra::option::OptionalExt; 11 | use termion::raw::IntoRawMode; 12 | 13 | static MAN_PAGE: &str = /* @MANSTART{less} */ r#" 14 | NAME 15 | less - view a text file. 16 | 17 | SYNOPSIS 18 | less [-h | --help] [input] 19 | 20 | DESCRIPTION 21 | This utility views text files. If no input file is specified as an argument, standard input is 22 | used. 23 | 24 | OPTIONS 25 | --help, -h 26 | Print this manual page. 27 | 28 | AUTHOR 29 | This program was written by MovingtoMars and Ticki for Redox OS. Bugs, issues, or feature 30 | requests should be reported in the Gitlab repository, 'redox-os/extrautils'. 31 | 32 | COPYRIGHT 33 | Copyright (c) 2016 MovingtoMars 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 36 | and associated documentation files (the "Software"), to deal in the Software without 37 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 38 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 39 | Software is furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in all copies or 42 | substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 45 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 46 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 47 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 49 | "#; /* @MANEND */ 50 | 51 | fn main() { 52 | let mut args = args().skip(1).peekable(); 53 | let stdin = io::stdin(); 54 | let mut stdin = stdin.lock(); 55 | let mut stderr = io::stderr(); 56 | 57 | if let Some(x) = args.peek() { 58 | if x == "--help" || x == "-h" { 59 | print!("{}", MAN_PAGE); 60 | return; 61 | } 62 | } else { 63 | let mut terminal = termion::get_tty().try(&mut stderr); 64 | run("-", &mut stdin, &mut terminal, io::stdout()).try(&mut stderr); 65 | }; 66 | 67 | for filename in args { 68 | let file = File::open(Path::new(filename.as_str())); 69 | match file { 70 | Ok(mut open_file) => { 71 | if let Err(err) = run(filename.as_str(), &mut open_file, &mut stdin, io::stdout()) { 72 | eprintln!("{}: {}", filename, err); 73 | } 74 | } 75 | Err(err) => { 76 | eprintln!("{}: {}", filename, err); 77 | } 78 | } 79 | } 80 | } 81 | 82 | // Run pager on a single file. 83 | fn run( 84 | path: &str, 85 | file: &mut dyn Read, 86 | controls: &mut dyn Read, 87 | stdout: W, 88 | ) -> std::io::Result<()> { 89 | let mut string = String::new(); 90 | file.read_to_string(&mut string)?; 91 | let string2 = string.replace('\x1B', "?"); 92 | 93 | pager::start(controls, stdout, path, &string2) 94 | } 95 | -------------------------------------------------------------------------------- /src/bin/man.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process::{exit, Command}; 3 | 4 | static MAN_PAGE: &str = /* @MANSTART{man} */ r#" 5 | NAME 6 | man - view a man page. 7 | 8 | SYNOPSIS 9 | man [-h | --help] [page] 10 | 11 | DESCRIPTION 12 | This utility launches less with a manual file. 13 | 14 | OPTIONS 15 | --help, -h 16 | Print this manual page. 17 | 18 | AUTHOR 19 | This program was written by Jeremy Soller for Redox OS. Bugs, issues, or feature requests 20 | should be reported in the Gitlab repository, 'redox-os/extrautils'. 21 | 22 | COPYRIGHT 23 | Copyright (c) 2016 Jeremy Soller 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 26 | and associated documentation files (the "Software"), to deal in the Software without 27 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 28 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 29 | Software is furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in all copies or 32 | substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 35 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 36 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 37 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 39 | "#; /* @MANEND */ 40 | 41 | fn main() { 42 | if let Some(arg) = env::args().nth(1) { 43 | match arg.as_str() { 44 | "--help" | "-h" => { 45 | // Print help. 46 | eprint!("{}", MAN_PAGE); 47 | exit(0); 48 | } 49 | page => { 50 | Command::new("less") 51 | .arg(&("/ref/".to_owned() + page)) 52 | .spawn() 53 | .unwrap() 54 | .wait() 55 | .unwrap(); 56 | } 57 | } 58 | } else { 59 | eprintln!("Which manual page do you want?"); 60 | exit(1); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/bin/mdless.rs: -------------------------------------------------------------------------------- 1 | // TODO support reading from standard input 2 | 3 | extern crate extra; 4 | extern crate termion; 5 | 6 | use std::env::args; 7 | use std::fs::File; 8 | use std::io::{self, Read, Write}; 9 | use std::path::{Path, PathBuf}; 10 | use std::str::Chars; 11 | 12 | use extra::option::OptionalExt; 13 | 14 | use termion::event::Key; 15 | use termion::input::TermRead; 16 | use termion::raw::{IntoRawMode, RawTerminal}; 17 | use termion::{clear, color, cursor, style, terminal_size}; 18 | 19 | static MAN_PAGE: &str = /* @MANSTART{mdless} */ r#" 20 | NAME 21 | mdless- view a markdown file. 22 | 23 | SYNOPSIS 24 | mdless [-h | --help] [input] 25 | 26 | DESCRIPTION 27 | This utility views md files. If no input file is specified as an argument, standard input is 28 | used. 29 | 30 | OPTIONS 31 | --help, -h 32 | Print this manual page. 33 | 34 | AUTHOR 35 | This program was written by MovingtoMars and Jeremy Soller for Redox OS. Bugs, issues, or feature requests should 36 | be reported in the Gitlab repository, 'redox-os/extrautils'. 37 | 38 | COPYRIGHT 39 | Copyright (c) 2016 MovingtoMars, Jeremy Soller 40 | 41 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 42 | and associated documentation files (the "Software"), to deal in the Software without 43 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 44 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 45 | Software is furnished to do so, subject to the following conditions: 46 | 47 | The above copyright notice and this permission notice shall be included in all copies or 48 | substantial portions of the Software. 49 | 50 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 51 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 52 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 53 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 55 | "#; /* @MANEND */ 56 | 57 | #[cfg(target_os = "redox")] 58 | fn terminal_path() -> String { 59 | use std::env; 60 | env::var("TTY").unwrap() 61 | } 62 | 63 | #[cfg(not(target_os = "redox"))] 64 | fn terminal_path() -> String { 65 | "/dev/tty".to_string() 66 | } 67 | 68 | fn main() { 69 | let mut args = args().skip(1).peekable(); 70 | let mut stdout = io::stdout(); 71 | let stdin = io::stdin(); 72 | let mut stdin = stdin.lock(); 73 | let mut stderr = io::stderr(); 74 | 75 | if let Some(x) = args.peek() { 76 | if x == "--help" || x == "-h" { 77 | // Print help. 78 | print!("{}", MAN_PAGE); 79 | return; 80 | } 81 | } else { 82 | let mut terminal = File::open(terminal_path()).try(&mut stderr); 83 | run(PathBuf::from("-"), &mut stdin, &mut terminal, &mut stdout).try(&mut stderr); 84 | }; 85 | 86 | for filename in args { 87 | let filepath = PathBuf::from(filename.as_str()); 88 | let file = File::open(&filepath); 89 | match file { 90 | Ok(mut open_file) => { 91 | if let Err(err) = run(filepath, &mut open_file, &mut stdin, &mut io::stdout()) { 92 | eprintln!("{}: {}", &filename, err); 93 | } 94 | } 95 | Err(err) => { 96 | eprintln!("{}: {}", &filename, err); 97 | } 98 | } 99 | } 100 | } 101 | 102 | //TODO: String in Text 103 | enum Block { 104 | Text(char), 105 | Bold(Vec), 106 | Italic(Vec), 107 | Code(Vec), 108 | Link(Vec, String), 109 | } 110 | 111 | impl Block { 112 | fn parse_bold(s: &mut Chars) -> Block { 113 | let mut blocks = Vec::new(); 114 | 115 | while let Some(c) = s.next() { 116 | if c == '*' && s.as_str().starts_with('*') { 117 | let _ = s.next(); 118 | break; 119 | } else { 120 | blocks.push(Block::Text(c)) 121 | } 122 | } 123 | 124 | Block::Bold(blocks) 125 | } 126 | 127 | fn parse_italic(s: &mut Chars) -> Block { 128 | let mut blocks = Vec::new(); 129 | 130 | for c in s { 131 | if c == '*' { 132 | break; 133 | } else { 134 | blocks.push(Block::Text(c)) 135 | } 136 | } 137 | 138 | Block::Italic(blocks) 139 | } 140 | 141 | fn parse_code(s: &mut Chars) -> Block { 142 | let mut blocks = Vec::new(); 143 | 144 | for c in s { 145 | if c == '`' { 146 | break; 147 | } else { 148 | blocks.push(Block::Text(c)) 149 | } 150 | } 151 | 152 | Block::Code(blocks) 153 | } 154 | 155 | #[allow(clippy::while_let_on_iterator)] 156 | fn parse_link(s: &mut Chars) -> Block { 157 | let mut blocks = Vec::new(); 158 | let mut link = String::new(); 159 | 160 | while let Some(c) = s.next() { 161 | match c { 162 | ']' => break, 163 | _ => blocks.push(Block::Text(c)), 164 | } 165 | } 166 | 167 | if s.as_str().starts_with('(') { 168 | while let Some(c) = s.next() { 169 | match c { 170 | '(' => (), 171 | ')' => break, 172 | _ => link.push(c), 173 | } 174 | } 175 | } 176 | 177 | Block::Link(blocks, link) 178 | } 179 | 180 | fn parse(s: &mut Chars) -> Vec { 181 | let mut blocks = Vec::new(); 182 | 183 | while let Some(c) = s.next() { 184 | match c { 185 | '*' => { 186 | if s.as_str().starts_with('*') { 187 | let _ = s.next(); 188 | blocks.push(Block::parse_bold(s)); 189 | } else { 190 | blocks.push(Block::parse_italic(s)); 191 | } 192 | } 193 | '`' => blocks.push(Block::parse_code(s)), 194 | '[' => blocks.push(Block::parse_link(s)), 195 | _ => blocks.push(Block::Text(c)), 196 | } 197 | } 198 | 199 | blocks 200 | } 201 | 202 | fn draw( 203 | &self, 204 | to: &mut RawTerminal, 205 | path: &PathBuf, 206 | next: &mut Vec, 207 | next_i: usize, 208 | ) -> std::io::Result { 209 | let mut count = 0; 210 | 211 | match *self { 212 | Block::Text(c) => { 213 | count += to.write(&[c as u8])?; 214 | } 215 | Block::Bold(ref blocks) => { 216 | write!(to, "{}", style::Bold)?; 217 | for block in blocks.iter() { 218 | count += block.draw(to, path, next, next_i)?; 219 | } 220 | write!(to, "{}", style::NoBold)?; 221 | } 222 | Block::Italic(ref blocks) => { 223 | write!(to, "{}", style::Italic)?; 224 | for block in blocks.iter() { 225 | count += block.draw(to, path, next, next_i)?; 226 | } 227 | write!(to, "{}", style::NoItalic)?; 228 | } 229 | Block::Code(ref blocks) => { 230 | write!(to, "{}", color::Bg(color::AnsiValue::grayscale(6)))?; 231 | for block in blocks.iter() { 232 | count += block.draw(to, path, next, next_i)?; 233 | } 234 | write!(to, "{}", color::Bg(color::Reset))?; 235 | } 236 | Block::Link(ref blocks, ref link) => { 237 | let highlight = next.len() == next_i; 238 | 239 | if link.starts_with('/') { 240 | next.push(PathBuf::from(link)); 241 | } else { 242 | let mut next_path = path.clone(); 243 | next_path.pop(); 244 | 245 | for part in Path::new(&link).iter() { 246 | match part.to_str().unwrap() { 247 | "." => {} 248 | ".." => { 249 | while next_path.ends_with(".") && next_path.pop() {} 250 | if next_path.ends_with("..") || !next_path.pop() { 251 | next_path.push(part); 252 | } 253 | } 254 | _ => next_path.push(part), 255 | } 256 | } 257 | 258 | next.push(next_path); 259 | } 260 | 261 | write!(to, "{}", style::Underline)?; 262 | if highlight { 263 | write!(to, "{}", style::Invert)?; 264 | } 265 | for block in blocks.iter() { 266 | count += block.draw(to, path, next, next_i)?; 267 | } 268 | write!(to, "{}", style::NoUnderline)?; 269 | if highlight { 270 | write!(to, "{}", style::NoInvert)?; 271 | } 272 | } 273 | } 274 | 275 | Ok(count) 276 | } 277 | } 278 | 279 | struct Buffer { 280 | lines: Vec>, 281 | y_off: u16, 282 | } 283 | 284 | impl Buffer { 285 | fn new() -> Buffer { 286 | Buffer { 287 | lines: Vec::new(), 288 | y_off: 0, 289 | } 290 | } 291 | 292 | fn read(&mut self, from: &mut dyn Read) -> std::io::Result { 293 | let mut tmp = String::new(); 294 | let res = from.read_to_string(&mut tmp)?; 295 | 296 | self.lines.append( 297 | &mut tmp 298 | .as_str() 299 | .split('\n') 300 | .map(|x| Block::parse(&mut x.chars())) 301 | .collect(), 302 | ); 303 | 304 | Ok(res) 305 | } 306 | 307 | #[allow(clippy::explicit_counter_loop)] 308 | fn draw( 309 | &self, 310 | to: &mut RawTerminal, 311 | path: &PathBuf, 312 | next: &mut Vec, 313 | next_i: usize, 314 | w: u16, 315 | h: u16, 316 | ) -> std::io::Result { 317 | write!(to, "{}{}{}", clear::All, style::Reset, cursor::Goto(1, 1))?; 318 | 319 | let mut y = 0; 320 | let mut count = 0; 321 | 322 | let lines = if self.lines.len() <= h as usize { 323 | &self.lines 324 | } else if self.y_off as usize >= self.lines.len() { 325 | &self.lines[0..0] 326 | } else { 327 | &self.lines[self.y_off as usize..] 328 | }; 329 | 330 | for line in lines { 331 | for block in line.iter() { 332 | let x = block.draw(to, path, next, next_i)?; 333 | count += x; 334 | if x >= w as usize { 335 | break; 336 | } 337 | } 338 | 339 | y += 1; 340 | write!(to, "{}", cursor::Goto(1, y + 1))?; 341 | if y >= h { 342 | break; 343 | } 344 | } 345 | 346 | Ok(count) 347 | } 348 | 349 | fn scroll_up(&mut self) { 350 | if self.y_off > 0 { 351 | self.y_off -= 1; 352 | } 353 | } 354 | 355 | fn scroll_down(&mut self, height: u16) { 356 | if ((self.y_off + height) as usize) <= self.lines.len() { 357 | self.y_off += 1; 358 | } 359 | } 360 | } 361 | 362 | fn run( 363 | mut path: PathBuf, 364 | file: &mut dyn Read, 365 | controls: &mut dyn Read, 366 | stdout: &mut W, 367 | ) -> std::io::Result<()> { 368 | let mut stdout = stdout.into_raw_mode()?; 369 | 370 | let (w, h) = { 371 | let (w, h) = terminal_size()?; 372 | (w as u16, h as u16) 373 | }; 374 | 375 | let mut next = Vec::new(); 376 | let mut next_i = 0; 377 | 378 | let mut buffer = Buffer::new(); 379 | buffer.read(file)?; 380 | 381 | buffer.draw(&mut stdout, &path, &mut next, next_i, w, h - 1)?; 382 | 383 | write!( 384 | stdout, 385 | "{}{}{} Press q to exit.{}", 386 | cursor::Goto(1, h), 387 | style::Invert, 388 | path.display(), 389 | style::NoInvert 390 | )?; 391 | 392 | stdout.flush()?; 393 | 394 | for c in controls.keys() { 395 | match c.unwrap() { 396 | Key::Char('q') => { 397 | write!( 398 | stdout, 399 | "{}{}{}", 400 | clear::All, 401 | style::Reset, 402 | cursor::Goto(1, 1) 403 | )?; 404 | break; 405 | } 406 | Key::Char('b') => { 407 | for _i in 1..h { 408 | buffer.scroll_up() 409 | } 410 | } 411 | Key::Char(' ') => { 412 | for _i in 1..h { 413 | buffer.scroll_down(h) 414 | } 415 | } 416 | Key::Up | Key::Char('k') => buffer.scroll_up(), 417 | Key::Down | Key::Char('j') => buffer.scroll_down(h), 418 | Key::Char('\t') => { 419 | next_i += 1; 420 | if next_i >= next.len() { 421 | next_i = 0; 422 | } 423 | } 424 | Key::Char('\r') | Key::Char('\n') => { 425 | if let Some(next_path) = next.get(next_i) { 426 | if let Ok(mut next_file) = File::open(&next_path) { 427 | path = next_path.clone(); 428 | buffer = Buffer::new(); 429 | buffer.read(&mut next_file)?; 430 | next_i = 0; 431 | } 432 | } 433 | } 434 | _ => {} 435 | } 436 | 437 | next = Vec::new(); 438 | 439 | buffer.draw(&mut stdout, &path, &mut next, next_i, w, h - 1)?; 440 | 441 | write!( 442 | stdout, 443 | "{}{}{} Press q to exit.{}", 444 | cursor::Goto(1, h), 445 | style::Invert, 446 | path.display(), 447 | style::NoInvert 448 | )?; 449 | 450 | stdout.flush()?; 451 | } 452 | 453 | Ok(()) 454 | } 455 | -------------------------------------------------------------------------------- /src/bin/mtxt.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | 3 | use std::env::args; 4 | use std::io::BufRead; 5 | use std::io::{self}; 6 | use std::process::exit; 7 | 8 | static MAN_PAGE: &str = /* @MANSTART{mtxt} */ r#" 9 | NAME 10 | mtxt - a simple tool to manipulate text from standard input. 11 | 12 | SYNOPSIS 13 | mtxt [-h | --help | -u | --to-uppercase-w | -l | --to-lowercase | -a | --strip-non-ascii] 14 | 15 | DESCRIPTION 16 | This utility will manipulate UTF-8 encoded text from the standard input. Unicode is supported. 17 | The flags are composable, unless otherwise stated. 18 | 19 | OPTIONS 20 | -h 21 | --help 22 | Print this manual page. 23 | 24 | -u 25 | --to-uppercase 26 | Convert the input to UPPERCASE. Works correctly with Unicode. Incompatible with '-l'. 27 | 28 | -l 29 | --lowercase 30 | Convert the input to lowercase. Works correctly with Unicode. 31 | 32 | -a 33 | --strip-non-ascii 34 | Strip the input for non-ASCII bytes, outputting a valid ASCII string. 35 | 36 | EXAMPLES 37 | $ echo Δ | mtxt -l 38 | > δ 39 | $ echo we got deltas Δ right | mtxt -a 40 | > we got deltas right 41 | $ echo Japanese scripts do not have case thus 山will stay unchanged | mtxt -u 42 | > JAPANESE SCRIPTS DO NOT HAVE CASE THUS 山WILL STAY UNCHANGED 43 | 44 | AUTHOR 45 | This program was written by Ticki for Redox OS. Bugs, issues, or feature requests should be 46 | reported in the Gitlab repository, 'redox-os/extrautils'. 47 | 48 | COPYRIGHT 49 | Copyright (c) 2016 Ticki 50 | 51 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 52 | and associated documentation files (the "Software"), to deal in the Software without 53 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 54 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 55 | Software is furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in all copies or 58 | substantial portions of the Software. 59 | 60 | Do you actually read the license? wat? 61 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 62 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 63 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 64 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 66 | "#; /* @MANEND */ 67 | 68 | fn main() { 69 | let stdin = io::stdin(); 70 | let stdin = stdin.lock(); 71 | 72 | // These are the options. 73 | let mut to_uppercase = false; 74 | let mut to_lowercase = false; 75 | let mut strip_non_ascii = false; 76 | 77 | for arg in args().skip(1) { 78 | match arg.as_str() { 79 | "-u" | "--to-uppercase" => to_uppercase = true, 80 | "-l" | "--to-lowercase" => to_lowercase = true, 81 | "-a" | "--strip-non-ascii" => strip_non_ascii = true, 82 | "-h" | "--help" => { 83 | print!("{}", MAN_PAGE); 84 | } 85 | a => { 86 | eprintln!("Error: unknown argument: {}", a); 87 | exit(1); 88 | } 89 | } 90 | } 91 | 92 | if to_lowercase && to_uppercase { 93 | eprintln!("-u and -l are incompatible. Aborting."); 94 | exit(1); 95 | } 96 | 97 | for u8_line in stdin.lines() { 98 | match u8_line { 99 | Err(err) => { 100 | eprintln!("{}", err); 101 | exit(1); 102 | } 103 | Ok(line) => { 104 | for c in line.chars() { 105 | if !strip_non_ascii || c.is_ascii() { 106 | if to_uppercase { 107 | print!("{}", c.to_uppercase().to_string()); 108 | } else if to_lowercase { 109 | print!("{}", c.to_lowercase().to_string()); 110 | } else { 111 | print!("{}", c); 112 | } 113 | } 114 | } 115 | println!(); 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/bin/rem.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | 3 | use std::env::args; 4 | use std::io::{self, Write}; 5 | use std::process::exit; 6 | use std::thread::sleep; 7 | use std::time::Duration; 8 | 9 | use extra::option::OptionalExt; 10 | 11 | static MAN_PAGE: &str = /* @MANSTART{rem} */ r#" 12 | NAME 13 | rem - set a count-down. 14 | 15 | SYNOPSIS 16 | rem [-h | --help] [-m N | --minutes N] [-H N | --hours N] [-s N | --seconds N] 17 | [-M N | --milliseconds N] [-n | --len] [-b | --blink] 18 | 19 | DESCRIPTION 20 | This utility lets you set a count-down with a progress bar. The options can be given in 21 | combination, adding together the durations given. 22 | 23 | OPTIONS 24 | --help 25 | Print this manual page. 26 | 27 | -h 28 | Print short help page. 29 | 30 | -m N 31 | --minutes N 32 | Wait N minutes. 33 | 34 | -H N 35 | --hours N 36 | Wait N hours. 37 | 38 | -s N 39 | --seconds N 40 | Wait N seconds. 41 | 42 | -M N 43 | --milliseconds N 44 | Wait N milliseconds. 45 | 46 | -n N 47 | --len N 48 | Set the length of the progress bar to N. 49 | 50 | -b 51 | --blink 52 | Blink with a red banner when done. 53 | 54 | AUTHOR 55 | This program was written by Ticki for Redox OS. Bugs, issues, or feature requests should be 56 | reported in the Gitlab repository, 'redox-os/extrautils'. 57 | 58 | COPYRIGHT 59 | Copyright (c) 2016 Ticki 60 | 61 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 62 | and associated documentation files (the "Software"), to deal in the Software without 63 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 64 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 65 | Software is furnished to do so, subject to the following conditions: 66 | 67 | The above copyright notice and this permission notice shall be included in all copies or 68 | substantial portions of the Software. 69 | 70 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 71 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 72 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 73 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 74 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 75 | "#; /* @MANEND */ 76 | 77 | static SHORT_HELP: &str = r#" 78 | rem - set a count-down. 79 | 80 | Options (use --help for extended list): 81 | -m N => Wait N minutes. 82 | -H N => Wait N hours. 83 | -s N => Wait N seconds. 84 | -M N => Wait N milliseconds. 85 | -n N => N character progress bar. 86 | -b => Blink when done. 87 | "#; 88 | 89 | fn main() { 90 | let mut args = args().skip(1); 91 | let stdout = io::stdout(); 92 | let mut stdout = stdout.lock(); 93 | let mut stderr = io::stderr(); 94 | 95 | let mut ms = 0u64; 96 | let mut len = 20; 97 | let mut blink = false; 98 | 99 | // Loop over the arguments. 100 | while let Some(arg) = args.next() { 101 | match arg.as_str() { 102 | "--help" => { 103 | print!("{}", MAN_PAGE); 104 | return; 105 | } 106 | "-h" => { 107 | print!("{}", SHORT_HELP); 108 | return; 109 | } 110 | "-n" | "--len" => { 111 | len = args 112 | .next() 113 | .fail("no number after -n.", &mut stderr) 114 | .parse() 115 | .try(&mut stderr) 116 | } 117 | "-b" | "--blink" => blink = true, 118 | t => { 119 | // Find number input. 120 | let num: u64 = args.next().unwrap_or_else(|| { 121 | eprintln!("error: incorrectly formatted number. Please input a positive integer."); 122 | exit(1); 123 | }).parse().try(&mut stderr); 124 | ms += num 125 | * match t { 126 | "-m" | "--minutes" => 1000 * 60, 127 | "-H" | "--hours" => 1000 * 60 * 60, 128 | "-s" | "--seconds" => 1000, 129 | "-M" | "--milliseconds" => 1, 130 | _ => { 131 | eprintln!("Error: unknown argument, {}", t); 132 | exit(1); 133 | } 134 | }; 135 | } 136 | } 137 | } 138 | 139 | // Default to help page. 140 | if ms == 0 { 141 | print!("{}", SHORT_HELP); 142 | return; 143 | } 144 | 145 | // Hide the cursor. 146 | print!("\x1b[?25l"); 147 | // Draw the empty progress bar. 148 | for _ in 0..len + 1 { 149 | print!(" "); 150 | } 151 | print!("]\r["); 152 | 153 | // As time goes, update the progress bar. 154 | for _ in 0..len { 155 | print!("#"); 156 | stdout.flush().try(&mut stderr); 157 | // Sleep. 158 | sleep(Duration::from_millis(ms / len)); 159 | } 160 | 161 | if blink { 162 | // This will print a blinking red banner. 163 | for _ in 0..13 { 164 | print!("\x1b[41m\x1b[2K\r"); // Clear the current line, rendering the background red. 165 | for _ in 0..len + 2 { 166 | print!(" "); 167 | } 168 | stdout.flush().try(&mut stderr); 169 | sleep(Duration::from_millis(200)); 170 | 171 | print!("\x1b[0m\x1b[2K\r"); // Clear the drawing mode and background. 172 | for _ in 0..len + 2 { 173 | print!(" "); 174 | } 175 | 176 | stdout.flush().try(&mut stderr); 177 | sleep(Duration::from_millis(200)); 178 | } 179 | } 180 | 181 | // Show the cursor again. 182 | println!("\x1b[?25h"); 183 | } 184 | -------------------------------------------------------------------------------- /src/bin/resize.rs: -------------------------------------------------------------------------------- 1 | extern crate extra; 2 | extern crate termion; 3 | 4 | use extra::option::OptionalExt; 5 | use std::io::{self, Write}; 6 | use termion::cursor::{self, DetectCursorPos}; 7 | use termion::raw::IntoRawMode; 8 | 9 | fn main() { 10 | let mut stderr = io::stderr(); 11 | 12 | let terminal = termion::get_tty().try(&mut stderr); 13 | let mut terminal = terminal.into_raw_mode().try(&mut stderr); 14 | 15 | write!(terminal, "{}", cursor::Save).unwrap(); 16 | terminal.flush().unwrap(); 17 | 18 | write!(terminal, "{}", cursor::Goto(999, 999)).unwrap(); 19 | terminal.flush().unwrap(); 20 | 21 | let (w, h) = terminal.cursor_pos().unwrap(); 22 | 23 | write!(terminal, "{}", cursor::Restore).unwrap(); 24 | terminal.flush().unwrap(); 25 | 26 | drop(terminal); 27 | 28 | println!("export COLUMNS={};", w); 29 | println!("export LINES={};", h); 30 | } 31 | -------------------------------------------------------------------------------- /src/bin/screenfetch.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 2 | extern crate raw_cpuid; 3 | extern crate libredox; 4 | 5 | use std::env; 6 | use std::fs::{self, File}; 7 | use std::io::Read; 8 | use std::path::Path; 9 | 10 | // std::fmt::Write conflicts with std::io::Write, hence the alias 11 | use std::fmt::Write as FmtWrite; 12 | 13 | use libredox::Fd; 14 | 15 | const SECONDS_PER_MINUTE: i64 = 60; 16 | const SECONDS_PER_HOUR: i64 = 3600; 17 | const SECONDS_PER_DAY: i64 = 86400; 18 | 19 | const KIB: u64 = 1024; 20 | const MIB: u64 = 1024 * KIB; 21 | const GIB: u64 = 1024 * MIB; 22 | const TIB: u64 = 1024 * GIB; 23 | 24 | 25 | fn format_size(size: u64) -> String { 26 | if size >= 4 * TIB { 27 | format!("{:.1} TiB", size as f64 / TIB as f64) 28 | } else if size >= GIB { 29 | format!("{:.1} GiB", size as f64 / GIB as f64) 30 | } else if size >= MIB { 31 | format!("{:.1} MiB", size as f64 / MIB as f64) 32 | } else if size >= KIB { 33 | format!("{:.1} KiB", size as f64 / KIB as f64) 34 | } else { 35 | format!("{} B", size) 36 | } 37 | } 38 | 39 | fn main() { 40 | let user = env::var("USER").unwrap_or_default(); 41 | let mut hostname = String::new(); 42 | if let Ok(mut file) = File::open("/etc/hostname") { 43 | let _ = file.read_to_string(&mut hostname); 44 | } 45 | 46 | let os_pretty_name = match os_release::OsRelease::new() { 47 | Ok(ok) => ok.pretty_name, 48 | Err(err) => { 49 | eprintln!("error: failed to read /etc/os-release: {}", err); 50 | "(unknown release)".to_string() 51 | } 52 | }; 53 | 54 | let mut kernel = String::new(); 55 | match fs::read_to_string("/scheme/sys/uname") { 56 | Ok(uname) => for line in uname.lines() { 57 | if line.is_empty() { 58 | continue; 59 | } 60 | 61 | if ! kernel.is_empty() { 62 | kernel.push(' '); 63 | } 64 | 65 | kernel.push_str(line); 66 | }, 67 | Err(err) => { 68 | eprintln!("error: failed to read /scheme/sys/uname: {}", err); 69 | } 70 | } 71 | 72 | let mut uptime_str = String::new(); 73 | 74 | if let Ok(ts) = libredox::call::clock_gettime(libredox::flag::CLOCK_MONOTONIC) { 75 | let uptime = ts.tv_sec; 76 | let uptime_secs = uptime % 60; 77 | let uptime_mins = (uptime / SECONDS_PER_MINUTE) % 60; 78 | let uptime_hours = (uptime / SECONDS_PER_HOUR) % 24; 79 | let uptime_days = (uptime / SECONDS_PER_DAY) % 365; 80 | 81 | let fmt_result; 82 | if uptime_days > 0 { 83 | fmt_result = write!( 84 | &mut uptime_str, 85 | "{}d {}h {}m {}s", 86 | uptime_days, uptime_hours, uptime_mins, uptime_secs 87 | ); 88 | } else if uptime_hours > 0 { 89 | fmt_result = write!( 90 | &mut uptime_str, 91 | "{}h {}m {}s", 92 | uptime_hours, uptime_mins, uptime_secs 93 | ); 94 | } else if uptime_mins > 0 { 95 | fmt_result = write!(&mut uptime_str, "{}m {}s", uptime_mins, uptime_secs); 96 | } else { 97 | fmt_result = write!(&mut uptime_str, "{}s", uptime_secs); 98 | } 99 | 100 | if let Err(err) = fmt_result { 101 | eprintln!("error: couldn't parse uptime, {}", err); 102 | } 103 | } 104 | 105 | let mut shell = String::new(); 106 | { 107 | if let Ok(shell_path) = env::var("SHELL") { 108 | if let Some(file_name) = Path::new(&shell_path).file_name() { 109 | shell = file_name.to_str().unwrap_or("").to_string(); 110 | } 111 | } 112 | } 113 | 114 | let mut width = 0; 115 | let mut height = 0; 116 | if let Ok(display_name) = env::var("DISPLAY") { 117 | if let Ok(display) = Fd::open(&display_name, libredox::flag::O_PATH, 0) { 118 | let mut buf: [u8; 4096] = [0; 4096]; 119 | if let Ok(count) = display.fpath(&mut buf) { 120 | let path = unsafe { String::from_utf8_unchecked(Vec::from(&buf[..count])) }; 121 | let res = path.split(':').nth(1).unwrap_or(""); 122 | width = res 123 | .split('/') 124 | .nth(1) 125 | .unwrap_or("") 126 | .parse::() 127 | .unwrap_or(0); 128 | height = res 129 | .split('/') 130 | .nth(2) 131 | .unwrap_or("") 132 | .parse::() 133 | .unwrap_or(0); 134 | } 135 | } 136 | } 137 | 138 | let mut cpu = String::new(); 139 | #[cfg(target_arch = "x86")] 140 | { 141 | let cpuid = raw_cpuid::CpuId::with_cpuid_fn(|a, c| { 142 | let result = unsafe { core::arch::x86::__cpuid_count(a, c) }; 143 | raw_cpuid::CpuIdResult { 144 | eax: result.eax, 145 | ebx: result.ebx, 146 | ecx: result.ecx, 147 | edx: result.edx, 148 | } 149 | }); 150 | if let Some(brand) = cpuid.get_processor_brand_string() { 151 | cpu = brand.as_str().to_string(); 152 | } 153 | } 154 | #[cfg(target_arch = "x86_64")] 155 | { 156 | let cpuid = raw_cpuid::CpuId::new(); 157 | if let Some(brand) = cpuid.get_processor_brand_string() { 158 | cpu = brand.as_str().to_string(); 159 | } 160 | } 161 | 162 | let mut ram = String::new(); 163 | { 164 | if let Ok(fd) = Fd::open("/scheme/memory", libredox::flag::O_PATH, 0) { 165 | if let Ok(stat) = fd.statvfs() { 166 | let size = stat.f_blocks as u64 * stat.f_bsize as u64; 167 | let used = (stat.f_blocks as u64 - stat.f_bfree as u64) * stat.f_bsize as u64; 168 | 169 | ram = format!( 170 | "{} / {} ({}%)", 171 | format_size(used), 172 | format_size(size), 173 | used * 100 / size 174 | ); 175 | } 176 | } 177 | } 178 | 179 | let mut disk = String::new(); 180 | { 181 | if let Ok(fd) = Fd::open("/", libredox::flag::O_PATH, 0) { 182 | if let Ok(stat) = fd.statvfs() { 183 | let size = stat.f_blocks as u64 * stat.f_bsize as u64; 184 | let used = (stat.f_blocks as u64 - stat.f_bfree as u64) * stat.f_bsize as u64; 185 | 186 | disk = format!( 187 | "{} / {} ({}%)", 188 | format_size(used), 189 | format_size(size), 190 | used * 100 / size 191 | ); 192 | } 193 | } 194 | } 195 | 196 | let left = [ 197 | " :+yMMMMy+: ", 198 | " .+dddNMMMMMMMMNddd+. ", 199 | " sydMMMMo/sMMMMs/oMMMMdys ", 200 | " .oMMMdso osdMMMo. ", 201 | " .+MMd/` -:::::::` `/dMM+. ", 202 | " +dMMN. NMMNNNMMdyo` .NMMd+ ", 203 | " yMMN NM+ oomMMN NMMy ", 204 | " hNMd. NM+ `oMN .dMNh ", 205 | " dMMh NM+ `oMN hMMd ", 206 | " dMMh NM+-oooodMMN hMMd ", 207 | " dMMh NM+/MMMMdhs` hMMd ", 208 | " hNMd. NM+`/mMMm+ .dMNh ", 209 | " yMMN NM+ oNMd+ NMMy ", 210 | " +dMMN. NM+ omMMm .NMMd+ ", 211 | " .+MMd/` --. .--- `/dMM+. ", 212 | " .oMMMdso osdMMMo. ", 213 | " sydMMMMo////////oMMMMdys ", 214 | " .+dddNMMMMMMMMNddd+. ", 215 | " :++++++++: ", 216 | ]; 217 | 218 | const S: &str = "\x1B[1;38;5;75m"; // blue start 219 | const E: &str = "\x1B[0m"; // end 220 | let right = [ 221 | format!("{}{}{}@{}{}{}", S, user, E, S, hostname.trim(), E), 222 | format!("{}OS: {}{}", S, E, os_pretty_name), 223 | format!("{}Kernel: {}{}", S, E, kernel), 224 | format!("{}Uptime: {}{}", S, E, uptime_str), 225 | format!("{}Shell: {}{}", S, E, shell), 226 | format!("{}Resolution: {}{}x{}", S, E, width, height), 227 | format!("{}DE: {}orbital", S, E), 228 | format!("{}WM: {}orbital", S, E), 229 | format!("{}CPU: {}{}", S, E, cpu), 230 | format!("{}RAM: {}{}", S, E, ram), 231 | format!("{}Disk: {}{}", S, E, disk), 232 | ]; 233 | 234 | for (i, line) in left.iter().enumerate() { 235 | print!("\x1B[1;38;5;75m{} \x1B[0m", line); 236 | if let Some(r) = right.get(i) { 237 | print!("{}", r); 238 | } 239 | println!(); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/bin/tar.rs: -------------------------------------------------------------------------------- 1 | extern crate bzip2; 2 | extern crate filetime; 3 | extern crate libflate; 4 | extern crate lzma; 5 | extern crate tar; 6 | extern crate tree_magic; 7 | 8 | use std::fs::{self, File}; 9 | use std::io::{copy, stdin, stdout, BufReader, Error, ErrorKind, Read, Result, Write}; 10 | use std::os::unix::fs::{symlink, OpenOptionsExt}; 11 | use std::path::{Path, PathBuf}; 12 | use std::str::FromStr; 13 | use std::{env, process}; 14 | 15 | use bzip2::read::BzDecoder; 16 | use filetime::FileTime; 17 | use libflate::gzip::Decoder as GzipDecoder; 18 | use lzma::LzmaReader; 19 | use tar::{Archive, Builder, EntryType}; 20 | 21 | fn create_inner(input: &str, ar: &mut Builder) -> Result<()> { 22 | if fs::metadata(input)?.is_dir() { 23 | for entry_result in fs::read_dir(input)? { 24 | let entry = entry_result?; 25 | if fs::metadata(entry.path())?.is_dir() { 26 | create_inner(entry.path().to_str().unwrap(), ar)?; 27 | } else { 28 | println!("{}", entry.path().display()); 29 | ar.append_path(entry.path())?; 30 | } 31 | } 32 | } else { 33 | println!("{}", input); 34 | ar.append_path(input)?; 35 | } 36 | 37 | Ok(()) 38 | } 39 | 40 | fn create(input: &str, tar: &str) -> Result<()> { 41 | if tar == "-" { 42 | create_inner(input, &mut Builder::new(stdout())) 43 | } else { 44 | create_inner(input, &mut Builder::new(File::create(tar)?)) 45 | } 46 | } 47 | 48 | fn list_inner(ar: &mut Archive) -> Result<()> { 49 | for entry_result in ar.entries()? { 50 | let entry = entry_result?; 51 | let path = entry.path()?; 52 | println!("{}", path.display()); 53 | } 54 | 55 | Ok(()) 56 | } 57 | 58 | fn list(tar: &str) -> Result<()> { 59 | if tar == "-" { 60 | list_inner(&mut Archive::new(stdin())) 61 | } else { 62 | list_inner(&mut Archive::new(File::open(tar)?)) 63 | } 64 | } 65 | 66 | fn create_symlink(link: PathBuf, target: &Path) -> Result<()> { 67 | //delete existing file to make way for symlink 68 | if link.exists() { 69 | fs::remove_file(link.clone()) 70 | .unwrap_or_else(|e| panic!("could not overwrite: {:?}, {:?}", link, e)); 71 | } 72 | symlink(target, link) 73 | } 74 | 75 | fn extract_inner(ar: &mut Archive, verbose: bool, strip: usize) -> Result<()> { 76 | for entry_result in ar.entries()? { 77 | let mut entry = entry_result?; 78 | 79 | let path = { 80 | let path = entry.path()?; 81 | let mut components = path.components(); 82 | for _ in 0..strip { 83 | components.next(); 84 | } 85 | components.as_path().to_path_buf() 86 | }; 87 | 88 | if path == Path::new("") { 89 | continue; 90 | } 91 | 92 | match entry.header().entry_type() { 93 | EntryType::Regular => { 94 | { 95 | let mut file = { 96 | if let Some(parent) = path.parent() { 97 | fs::create_dir_all(parent)?; 98 | } 99 | fs::OpenOptions::new() 100 | .read(true) 101 | .write(true) 102 | .truncate(true) 103 | .create(true) 104 | .mode(entry.header().mode().unwrap_or(0o644)) 105 | .open(&path)? 106 | }; 107 | copy(&mut entry, &mut file)?; 108 | } 109 | if let Ok(mtime) = entry.header().mtime() { 110 | let mtime = FileTime::from_unix_time(mtime as i64, 0); 111 | filetime::set_file_times(&path, mtime, mtime)?; 112 | } 113 | } 114 | EntryType::Directory => { 115 | fs::create_dir_all(&path)?; 116 | } 117 | EntryType::Symlink => { 118 | if let Some(target) = entry.link_name().unwrap_or_else(|e| { 119 | panic!("Can't parse symlink target for: {:?}, {:?}", path, e) 120 | }) { 121 | create_symlink(path, &target)? 122 | } 123 | } 124 | other => { 125 | panic!("Unsupported entry type {:?}", other); 126 | } 127 | } 128 | 129 | if verbose { 130 | println!("{}", entry.path()?.display()); 131 | } 132 | } 133 | 134 | Ok(()) 135 | } 136 | 137 | fn extract(tar: &Path, verbose: bool, strip: usize) -> Result<()> { 138 | if tar == Path::new("-") { 139 | extract_inner(&mut Archive::new(stdin()), verbose, strip) 140 | } else { 141 | let mime = tree_magic::from_filepath(Path::new(&tar)); 142 | let file = BufReader::new(File::open(tar)?); 143 | if mime == Some("application/x-xz".to_string()) { 144 | extract_inner( 145 | &mut Archive::new( 146 | LzmaReader::new_decompressor(file) 147 | .map_err(|e| Error::new(ErrorKind::Other, e))?, 148 | ), 149 | verbose, 150 | strip, 151 | ) 152 | } else if mime == Some("application/gzip".to_string()) { 153 | extract_inner( 154 | &mut Archive::new( 155 | GzipDecoder::new(file).map_err(|e| Error::new(ErrorKind::Other, e))?, 156 | ), 157 | verbose, 158 | strip, 159 | ) 160 | } else if mime == Some("application/x-bzip".to_string()) { 161 | extract_inner(&mut Archive::new(BzDecoder::new(file)), verbose, strip) 162 | } else { 163 | extract_inner(&mut Archive::new(file), verbose, strip) 164 | } 165 | } 166 | } 167 | 168 | fn main() { 169 | let mut args = env::args().skip(1); 170 | if let Some(op) = args.next() { 171 | match op.as_str() { 172 | "c" => { 173 | if let Some(input) = args.next() { 174 | if let Err(err) = create(&input, "-") { 175 | eprintln!("tar: create: failed: {}", err); 176 | process::exit(1); 177 | } 178 | } else { 179 | eprintln!("tar: create: no input specified: {}", op); 180 | process::exit(1); 181 | } 182 | } 183 | "cf" => { 184 | if let Some(tar) = args.next() { 185 | if let Some(input) = args.next() { 186 | if let Err(err) = create(&input, &tar) { 187 | eprintln!("tar: create: failed: {}", err); 188 | process::exit(1); 189 | } 190 | } else { 191 | eprintln!("tar: create: no input specified: {}", op); 192 | process::exit(1); 193 | } 194 | } else { 195 | eprintln!("tar: create: no tarfile specified: {}", op); 196 | process::exit(1); 197 | } 198 | } 199 | "t" | "tf" => { 200 | let tar = args.next().unwrap_or_else(|| "-".to_string()); 201 | if let Err(err) = list(&tar) { 202 | eprintln!("tar: list: failed: {}", err); 203 | process::exit(1); 204 | } 205 | } 206 | "x" | "xf" | "xvf" => { 207 | let mut tar = None; 208 | let mut strip = 0; 209 | while let Some(arg) = args.next() { 210 | if arg == "-C" || arg == "--directory" { 211 | env::set_current_dir( 212 | args.next() 213 | .unwrap_or_else(|| panic!("{} requires path", arg)), 214 | ) 215 | .unwrap(); 216 | } else if arg.starts_with("--directory=") { 217 | env::set_current_dir(&arg[12..]).unwrap(); 218 | } else if arg.starts_with("--strip-components") { 219 | let num = args.next().expect("--strip-components requires an integer"); 220 | strip = 221 | usize::from_str(&num).expect("--strip-components requires an integer"); 222 | } else if arg.starts_with("--strip-components=") { 223 | strip = usize::from_str(&arg[19..]) 224 | .expect("--strip-components requires an integer"); 225 | } else if tar.is_none() { 226 | let mut path = env::current_dir().unwrap(); 227 | path.push(arg); 228 | tar = Some(path); 229 | } 230 | } 231 | let tar = tar.unwrap_or_else(|| PathBuf::from("-")); 232 | 233 | let verbose = op.contains('v'); 234 | if let Err(err) = extract(&tar, verbose, strip) { 235 | eprintln!("tar: extract: failed: {}", err); 236 | process::exit(1); 237 | } 238 | } 239 | _ => { 240 | eprintln!("tar: {}: unknown operation\n", op); 241 | eprintln!("tar: need to specify c[f] (create), t[f] (list), or x[f] (extract)"); 242 | process::exit(1); 243 | } 244 | } 245 | } else { 246 | eprintln!("tar: no operation"); 247 | eprintln!("tar: need to specify cf (create), tf (list), or xf (extract)"); 248 | process::exit(1); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/bin/unzip.rs: -------------------------------------------------------------------------------- 1 | // From https://raw.githubusercontent.com/mvdnes/zip-rs/master/examples/extract.rs 2 | 3 | extern crate zip; 4 | 5 | use std::fs; 6 | use std::io; 7 | 8 | fn main() { 9 | std::process::exit(real_main()); 10 | } 11 | 12 | fn real_main() -> i32 { 13 | let args: Vec<_> = std::env::args().collect(); 14 | if args.len() < 2 { 15 | println!("Usage: {} ", args[0]); 16 | return 1; 17 | } 18 | let fname = std::path::Path::new(&*args[1]); 19 | let file = fs::File::open(&fname).unwrap(); 20 | 21 | let mut archive = zip::ZipArchive::new(file).unwrap(); 22 | 23 | for i in 0..archive.len() { 24 | let mut file = archive.by_index(i).unwrap(); 25 | let outpath = file.sanitized_name(); 26 | 27 | { 28 | let comment = file.comment(); 29 | if !comment.is_empty() { 30 | println!("File {} comment: {}", i, comment); 31 | } 32 | } 33 | 34 | if (&*file.name()).ends_with('/') { 35 | println!( 36 | "File {} extracted to \"{}\"", 37 | i, 38 | outpath.as_path().display() 39 | ); 40 | fs::create_dir_all(&outpath).unwrap(); 41 | } else { 42 | println!( 43 | "File {} extracted to \"{}\" ({} bytes)", 44 | i, 45 | outpath.as_path().display(), 46 | file.size() 47 | ); 48 | if let Some(p) = outpath.parent() { 49 | if !p.exists() { 50 | fs::create_dir_all(&p).unwrap(); 51 | } 52 | } 53 | let mut outfile = fs::File::create(&outpath).unwrap(); 54 | io::copy(&mut file, &mut outfile).unwrap(); 55 | } 56 | 57 | // Get and Set permissions 58 | #[cfg(any(unix, target_os = "redox"))] 59 | { 60 | use std::os::unix::fs::PermissionsExt; 61 | 62 | if let Some(mode) = file.unix_mode() { 63 | fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap(); 64 | } 65 | } 66 | } 67 | 0 68 | } 69 | -------------------------------------------------------------------------------- /src/bin/watch.rs: -------------------------------------------------------------------------------- 1 | // TODO support reading from standard input 2 | 3 | extern crate extra; 4 | extern crate termion; 5 | 6 | use std::env::{self, args}; 7 | use std::io::{self, Read, Write}; 8 | use std::process::{self, Command, Stdio}; 9 | use std::time::Duration; 10 | use std::{cmp, str, thread}; 11 | 12 | use extra::option::OptionalExt; 13 | 14 | use termion::raw::IntoRawMode; 15 | use termion::{async_stdin, clear, cursor, style, terminal_size}; 16 | 17 | static MAN_PAGE: &str = /* @MANSTART{watch} */ r#" 18 | NAME 19 | watch - execute a program periodically, showing output fullscreen 20 | 21 | SYNOPSIS 22 | watch [-h | --help] [-n N | --interval N] command 23 | 24 | DESCRIPTION 25 | Runs command repeatedly, displaying its output and errors. This allows you to watch the program 26 | output change over time. By default, the program is run every 2 seconds. By default, watch will 27 | run until interrupted. 28 | 29 | OPTIONS 30 | --help, -h 31 | Print this manual page. 32 | 33 | AUTHOR 34 | This program was written by Jeremy Soller for Redox OS. Bugs, issues, or feature requests 35 | should be reported in the Gitlab repository, 'redox-os/extrautils'. 36 | 37 | COPYRIGHT 38 | Copyright (c) 2016 Jeremy Soller 39 | 40 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 41 | and associated documentation files (the "Software"), to deal in the Software without 42 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 43 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 44 | Software is furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in all copies or 47 | substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 50 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 51 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 52 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 54 | "#; /* @MANEND */ 55 | 56 | fn main() { 57 | let mut args = args().skip(1); 58 | let mut stdout = io::stdout(); 59 | let mut stderr = io::stderr(); 60 | 61 | let mut command = String::new(); 62 | let mut interval = 2; 63 | 64 | while let Some(x) = args.next() { 65 | match x.as_str() { 66 | "--help" | "-h" => { 67 | // Print help. 68 | stdout.write(MAN_PAGE.as_bytes()).try(&mut stderr); 69 | return; 70 | } 71 | "--interval" | "-n" => { 72 | if let Some(interval_str) = args.next() { 73 | if let Ok(interval_num) = interval_str.parse::() { 74 | interval = cmp::max(1, interval_num); 75 | } else { 76 | eprintln!("watch: interval argument not specified"); 77 | process::exit(1); 78 | } 79 | } else { 80 | eprintln!("watch: interval argument not specified"); 81 | process::exit(1); 82 | } 83 | } 84 | arg => { 85 | if !command.is_empty() { 86 | command.push(' '); 87 | } 88 | command.push_str(arg); 89 | } 90 | } 91 | } 92 | 93 | if command.is_empty() { 94 | eprintln!("watch: command argument not specified"); 95 | process::exit(1); 96 | } 97 | 98 | run(command, interval, stdout).try(&mut stderr); 99 | } 100 | 101 | fn run(command: String, interval: u64, stdout: W) -> std::io::Result<()> { 102 | let title = format!("Every {}s: {}", interval, command); 103 | 104 | let shell = env::var("SHELL").unwrap_or_else(|_| String::from("sh")); 105 | 106 | let mut stdout = stdout.into_raw_mode()?; 107 | 108 | let (w, h) = terminal_size()?; 109 | 110 | let mut stdin = async_stdin(); 111 | 112 | 'watching: loop { 113 | write!( 114 | stdout, 115 | "{}{}{}", 116 | clear::All, 117 | style::Reset, 118 | cursor::Goto(1, 1) 119 | )?; 120 | 121 | let child = Command::new(&shell) 122 | .arg("-c") 123 | .arg(&command) 124 | .stdin(Stdio::null()) 125 | .stdout(Stdio::piped()) 126 | .stderr(Stdio::piped()) 127 | .output()?; 128 | 129 | let output = String::from_utf8_lossy(&child.stdout); 130 | 131 | for (y, line) in output.lines().enumerate() { 132 | write!(stdout, "{}", cursor::Goto(1, (y + 1) as u16))?; 133 | 134 | if line.len() > w as usize { 135 | stdout.write_all(line[..w as usize].as_bytes())?; 136 | } else { 137 | stdout.write_all(line.as_bytes())?; 138 | } 139 | 140 | if (y + 1) as u16 >= h { 141 | break; 142 | } 143 | } 144 | 145 | write!( 146 | stdout, 147 | "{}{}{}{}", 148 | cursor::Goto(1, h), 149 | style::Invert, 150 | title, 151 | style::NoInvert 152 | )?; 153 | 154 | stdout.flush()?; 155 | 156 | for _second in 0..interval * 10 { 157 | for b in (&mut stdin).bytes() { 158 | if b? == b'q' { 159 | break 'watching; 160 | } 161 | } 162 | 163 | thread::sleep(Duration::new(0, 100000000)); 164 | } 165 | } 166 | 167 | write!( 168 | stdout, 169 | "{}{}{}", 170 | clear::All, 171 | style::Reset, 172 | cursor::Goto(1, 1) 173 | )?; 174 | 175 | Ok(()) 176 | } 177 | -------------------------------------------------------------------------------- /tests/grep.rs: -------------------------------------------------------------------------------- 1 | extern crate assert_cmd; 2 | extern crate predicates; 3 | extern crate tempfile; 4 | 5 | use assert_cmd::Command; 6 | use predicates::prelude::*; 7 | use std::io::Write; 8 | use tempfile::NamedTempFile; 9 | 10 | static SAMPLE_FILE: &str = r#" 11 | We need some test data to search for stuff, so here goes... 12 | 13 | somestring 14 | somestring 15 | someotherstring 16 | SoMEsTriNGWIthObNoXiouSCaps 17 | SOMESTRINGINALLCAPS 18 | repeat repeated repeated 19 | repeat repeated 20 | 21 | Unicode is fun! 🦀 22 | Hello, 世界! 23 | "#; 24 | 25 | #[test] 26 | fn no_args() { 27 | Command::cargo_bin("grep") 28 | .expect("found binary") 29 | .assert() 30 | .code(2) 31 | .stderr(predicate::str::is_empty().not()) 32 | .stdout(predicate::str::is_empty()); 33 | } 34 | 35 | #[test] 36 | fn invalid_flag() { 37 | Command::cargo_bin("grep") 38 | .expect("found binary") 39 | .args(&["--does-not-exist", "validpattern"]) 40 | .assert() 41 | .code(2) 42 | .stderr(predicate::str::is_empty().not()) 43 | .stdout(predicate::str::is_empty()); 44 | } 45 | 46 | #[test] 47 | fn help() { 48 | Command::cargo_bin("grep") 49 | .expect("found binary") 50 | .arg("--help") 51 | .assert() 52 | .success() 53 | .stderr(predicate::str::is_empty()) 54 | .stdout(predicate::str::contains("OPTIONS")); 55 | } 56 | 57 | #[test] 58 | fn empty_input() { 59 | Command::cargo_bin("grep") 60 | .expect("found binary") 61 | .args(&["somepattern", "/dev/null"]) 62 | .assert() 63 | .code(1) 64 | .stderr(predicate::str::is_empty()) 65 | .stdout(predicate::str::is_empty()); 66 | } 67 | 68 | #[test] 69 | fn simple_match() { 70 | Command::cargo_bin("grep") 71 | .expect("found binary") 72 | .arg("foo") 73 | .write_stdin("foo\n") 74 | .assert() 75 | .success() 76 | .stderr(predicate::str::is_empty()) 77 | .stdout(predicate::str::similar("foo\n")); 78 | } 79 | 80 | #[test] 81 | fn count_empty_input() { 82 | Command::cargo_bin("grep") 83 | .expect("found binary") 84 | .args(&["-c", "somepattern", "/dev/null"]) 85 | .assert() 86 | .code(1) 87 | .stderr(predicate::str::is_empty()) 88 | .stdout(predicate::str::similar("0\n")); 89 | } 90 | 91 | #[test] 92 | fn simple_count_from_sample() { 93 | Command::cargo_bin("grep") 94 | .expect("found binary") 95 | .args(&["-c", "repeat"]) 96 | .write_stdin(SAMPLE_FILE) 97 | .assert() 98 | .success() 99 | .stderr(predicate::str::is_empty()) 100 | .stdout(predicate::str::similar("2\n")); 101 | } 102 | 103 | #[test] 104 | fn unicode_match_from_sample() { 105 | Command::cargo_bin("grep") 106 | .expect("found binary") 107 | .args(&["🦀"]) 108 | .write_stdin(SAMPLE_FILE) 109 | .assert() 110 | .success() 111 | .stderr(predicate::str::is_empty()) 112 | .stdout(predicate::str::similar("Unicode is fun! 🦀\n")); 113 | } 114 | 115 | #[test] 116 | fn simple_match_from_file() { 117 | let mut file = NamedTempFile::new().expect("temp file"); 118 | write!(file, "{}", SAMPLE_FILE).expect("wrote temp file"); 119 | Command::cargo_bin("grep") 120 | .expect("found binary") 121 | .args(&["somestring", file.path().to_str().unwrap()]) 122 | .assert() 123 | .success() 124 | .stderr(predicate::str::is_empty()) 125 | .stdout(predicate::str::similar("somestring\n somestring\n")); 126 | } 127 | 128 | #[test] 129 | fn single_file_with_headers() { 130 | let mut file = NamedTempFile::new().expect("temp file"); 131 | write!(file, "{}", SAMPLE_FILE).expect("wrote temp file"); 132 | let filename = file.path().to_str().unwrap(); 133 | Command::cargo_bin("grep") 134 | .expect("found binary") 135 | .args(&["-H", "someother", &filename]) 136 | .assert() 137 | .success() 138 | .stderr(predicate::str::is_empty()) 139 | .stdout(predicate::str::starts_with(format!("{}:", &filename))); 140 | } 141 | 142 | #[test] 143 | fn multiple_files() { 144 | let mut file1 = NamedTempFile::new().expect("temp file"); 145 | write!(file1, "{}", "nothing interesting").expect("wrote temp file"); 146 | let filename1 = file1.path().to_str().unwrap(); 147 | 148 | let mut file2 = NamedTempFile::new().expect("temp file"); 149 | write!(file2, "{}", SAMPLE_FILE).expect("wrote temp file"); 150 | let filename2 = file2.path().to_str().unwrap(); 151 | 152 | Command::cargo_bin("grep") 153 | .expect("found binary") 154 | .args(&["someother", &filename1, &filename2]) 155 | .assert() 156 | .success() 157 | .stderr(predicate::str::is_empty()) 158 | .stdout(predicate::str::starts_with(format!("{}:", &filename2))); 159 | } 160 | 161 | #[test] 162 | fn multiple_files_without_headers() { 163 | let mut file1 = NamedTempFile::new().expect("temp file"); 164 | write!(file1, "{}", "nothing interesting").expect("wrote temp file"); 165 | let filename1 = file1.path().to_str().unwrap(); 166 | 167 | let mut file2 = NamedTempFile::new().expect("temp file"); 168 | write!(file2, "{}", SAMPLE_FILE).expect("wrote temp file"); 169 | let filename2 = file2.path().to_str().unwrap(); 170 | 171 | Command::cargo_bin("grep") 172 | .expect("found binary") 173 | .args(&["-h", "someother", &filename1, &filename2]) 174 | .assert() 175 | .success() 176 | .stderr(predicate::str::is_empty()) 177 | .stdout(predicate::str::similar("someotherstring\n")); 178 | } 179 | 180 | #[test] 181 | fn line_numbers() { 182 | Command::cargo_bin("grep") 183 | .expect("found binary") 184 | .args(&["-n", "Hello"]) 185 | .write_stdin(SAMPLE_FILE) 186 | .assert() 187 | .success() 188 | .stderr(predicate::str::is_empty()) 189 | .stdout(predicate::str::starts_with("13:Hello")); 190 | } 191 | 192 | #[test] 193 | fn stdin_header_and_line_numbers() { 194 | Command::cargo_bin("grep") 195 | .expect("found binary") 196 | .args(&["-H", "-n", "Hello"]) 197 | .write_stdin(SAMPLE_FILE) 198 | .assert() 199 | .success() 200 | .stderr(predicate::str::is_empty()) 201 | .stdout(predicate::str::starts_with("(standard input):13:Hello")); 202 | } 203 | 204 | // If there's an error on a file, that should be reflected on stderr and 205 | // in the return code, but we should still look at any remaining files. 206 | #[test] 207 | fn error_on_first_of_multiple_files() { 208 | let mut file = NamedTempFile::new().expect("temp file"); 209 | write!(file, "{}", SAMPLE_FILE).expect("wrote temp file"); 210 | let filename = file.path().to_str().unwrap(); 211 | Command::cargo_bin("grep") 212 | .expect("found binary") 213 | .args(&["someother", "nonexistentfilefirst", &filename]) 214 | .assert() 215 | .code(2) 216 | .stderr(predicate::str::contains("No such file or directory")) 217 | .stdout(predicate::str::contains("someotherstring")); 218 | } 219 | 220 | #[test] 221 | fn headers_on_counts() { 222 | let mut file1 = NamedTempFile::new().expect("temp file"); 223 | write!(file1, "{}", "garbage\n").expect("wrote temp file"); 224 | let filename1 = file1.path().to_str().unwrap(); 225 | 226 | let mut file2 = NamedTempFile::new().expect("temp file"); 227 | write!(file2, "{}", SAMPLE_FILE).expect("wrote temp file"); 228 | let filename2 = file2.path().to_str().unwrap(); 229 | 230 | let expected = format!("{}:0\n{}:2\n", &filename1, &filename2); 231 | Command::cargo_bin("grep") 232 | .expect("found binary") 233 | .args(&["-c", "somestring", &filename1, &filename2]) 234 | .assert() 235 | .success() 236 | .stderr(predicate::str::is_empty()) 237 | .stdout(predicate::str::similar(expected)); 238 | } 239 | 240 | #[test] 241 | fn case_insensitive_match() { 242 | Command::cargo_bin("grep") 243 | .expect("found binary") 244 | .args(&["-i", "foo"]) 245 | .write_stdin("Foo\n") 246 | .assert() 247 | .success() 248 | .stderr(predicate::str::is_empty()) 249 | .stdout(predicate::str::similar("Foo\n")); 250 | } 251 | 252 | #[test] 253 | fn quiet_match() { 254 | Command::cargo_bin("grep") 255 | .expect("found binary") 256 | .args(&["-q", "foo"]) 257 | .write_stdin("foo\n") 258 | .assert() 259 | .success() 260 | .stderr(predicate::str::is_empty()) 261 | .stdout(predicate::str::is_empty()); 262 | } 263 | 264 | #[test] 265 | fn max_count() { 266 | Command::cargo_bin("grep") 267 | .expect("found binary") 268 | .args(&["-cm2", "some"]) 269 | .write_stdin(SAMPLE_FILE) 270 | .assert() 271 | .success() 272 | .stderr(predicate::str::is_empty()) 273 | .stdout(predicate::str::similar("2\n")); 274 | } 275 | --------------------------------------------------------------------------------