From 7f5dd75b3e65c650189c39b13aa9dec3052c6799 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 16 Dec 2021 23:54:02 +0100 Subject: [PATCH] feat(shell): initial draft --- Cargo.lock | 773 ++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + shell/Cargo.toml | 20 ++ shell/src/args.rs | 26 ++ shell/src/cmd/help.rs | 12 + shell/src/cmd/list.rs | 50 +++ shell/src/cmd/mod.rs | 15 + shell/src/complete.rs | 56 +++ shell/src/config.rs | 34 ++ shell/src/input.rs | 97 ++++++ shell/src/lib.rs | 224 ++++++++++++ shell/src/main.rs | 20 ++ shell/src/paths.rs | 35 ++ shell/src/session.rs | 71 ++++ shell/src/term.rs | 64 ++++ 15 files changed, 1475 insertions(+), 23 deletions(-) create mode 100644 shell/Cargo.toml create mode 100644 shell/src/args.rs create mode 100644 shell/src/cmd/help.rs create mode 100644 shell/src/cmd/list.rs create mode 100644 shell/src/cmd/mod.rs create mode 100644 shell/src/complete.rs create mode 100644 shell/src/config.rs create mode 100644 shell/src/input.rs create mode 100644 shell/src/lib.rs create mode 100644 shell/src/main.rs create mode 100644 shell/src/paths.rs create mode 100644 shell/src/session.rs create mode 100644 shell/src/term.rs diff --git a/Cargo.lock b/Cargo.lock index b3058a7908728..58354ed72c5a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "arrayvec" version = "0.5.2" @@ -142,6 +151,15 @@ dependencies = [ "serde", ] +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + [[package]] name = "async-trait" version = "0.1.51" @@ -175,6 +193,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "auto_impl" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cbf586c80ada5e5ccdecae80d3ef0854f224e2dd74435f8d87e6831b8d0a38" +dependencies = [ + "proc-macro-error 1.0.4", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "auto_impl" version = "0.5.0" @@ -220,13 +250,19 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + [[package]] name = "base58check" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee2fe4c9a0c84515f136aaae2466744a721af6d63339c18689d9e995d74d99b" dependencies = [ - "base58", + "base58 0.1.0", "sha2 0.8.2", ] @@ -294,6 +330,18 @@ dependencies = [ "radium 0.3.0", ] +[[package]] +name = "bitvec" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" +dependencies = [ + "funty", + "radium 0.5.3", + "tap", + "wyz", +] + [[package]] name = "bitvec" version = "0.20.4" @@ -317,6 +365,16 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -589,6 +647,17 @@ dependencies = [ "vec_map", ] +[[package]] +name = "clipboard-win" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -671,7 +740,7 @@ dependencies = [ "libc", "log", "matches", - "nix", + "nix 0.13.1", "serde", "thiserror", "wasm-bindgen", @@ -749,6 +818,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "contract-metadata" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6ebbaeb7c25c27c6dec510353f8c2ac7da40ac5a291ff34aceda3c9977809f" +dependencies = [ + "semver 1.0.4", + "serde", + "serde_json", + "url", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -867,6 +948,26 @@ dependencies = [ "cipher 0.3.0", ] +[[package]] +name = "ctrlc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" +dependencies = [ + "nix 0.23.0", + "winapi", +] + +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if 1.0.0", + "num_cpus", +] + [[package]] name = "der" version = "0.4.3" @@ -910,6 +1011,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + [[package]] name = "digest" version = "0.8.1" @@ -939,6 +1046,27 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "ecdsa" version = "0.12.4" @@ -999,6 +1127,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ena" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +dependencies = [ + "log", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1014,6 +1151,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enum-as-inner" version = "0.3.3" @@ -1039,12 +1182,35 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "environmental" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" +[[package]] +name = "error-code" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff" +dependencies = [ + "libc", + "str-buf", +] + [[package]] name = "eth-keystore" version = "0.3.0" @@ -1069,11 +1235,9 @@ dependencies = [ [[package]] name = "ethabi" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76ef192b63e8a44b3d08832acebbb984c3fba154b5c26f70037c860202a0d4b" +version = "16.0.0" +source = "git+https://github.com/rust-ethereum/ethabi?branch=master#7f4bb3d6164775928485b2bad2f6e130a96f438e" dependencies = [ - "anyhow", "ethereum-types", "hex", "serde", @@ -1136,7 +1300,7 @@ dependencies = [ [[package]] name = "ethers" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "ethers-contract", "ethers-core", @@ -1150,7 +1314,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -1168,7 +1332,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "Inflector", "anyhow", @@ -1189,7 +1353,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "ethers-contract-abigen", "ethers-core", @@ -1203,7 +1367,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "arrayvec 0.7.2", "bytes", @@ -1231,7 +1395,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "0.2.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "ethers-core", "reqwest", @@ -1244,7 +1408,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "async-trait", "ethers-contract", @@ -1267,10 +1431,10 @@ dependencies = [ [[package]] name = "ethers-providers" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "async-trait", - "auto_impl", + "auto_impl 0.5.0", "ethers-core", "futures-channel", "futures-core", @@ -1296,7 +1460,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "0.6.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "async-trait", "coins-bip32", @@ -1318,7 +1482,7 @@ dependencies = [ [[package]] name = "ethers-solc" version = "0.1.0" -source = "git+https://github.com/gakonst/ethers-rs#f4c89c6cb1038f40b806224ff62b11410dd1b0ee" +source = "git+https://github.com/gakonst/ethers-rs#4d647453e3df09d0934964ef913951cd38366286" dependencies = [ "colored", "ethers-core", @@ -1348,7 +1512,7 @@ name = "evm" version = "0.33.0" source = "git+https://github.com/rust-blockchain/evm#9ac4d47b5e7a34743e665d9ce24a2cfbd9371f99" dependencies = [ - "auto_impl", + "auto_impl 0.5.0", "environmental", "ethereum", "evm-core", @@ -1413,7 +1577,7 @@ name = "evm-runtime" version = "0.33.0" source = "git+https://github.com/rust-blockchain/evm#9ac4d47b5e7a34743e665d9ce24a2cfbd9371f99" dependencies = [ - "auto_impl", + "auto_impl 0.5.0", "environmental", "evm-core", "primitive-types", @@ -1463,6 +1627,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fd-lock" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "windows-sys", +] + [[package]] name = "ff" version = "0.10.1" @@ -1485,6 +1660,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + [[package]] name = "fnv" version = "1.0.7" @@ -1528,6 +1709,24 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "forge-shell" +version = "0.1.0" +dependencies = [ + "colored", + "ctrlc", + "dirs-next", + "ethers", + "eyre", + "log", + "pretty_env_logger", + "regex", + "rustyline", + "shellwords", + "solang", + "structopt", +] + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -1823,6 +2022,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "handlebars" +version = "4.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167fa173496c9eadd8749cca6f8339ac88e248f3ad2442791d0b743318a94fc0" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error 2.0.1", + "serde", + "serde_json", +] + [[package]] name = "hash-db" version = "0.15.2" @@ -1957,6 +2170,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error 1.2.3", +] + [[package]] name = "hyper" version = "0.14.13" @@ -2162,6 +2384,38 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +[[package]] +name = "lalrpop" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" +dependencies = [ + "ascii-canvas", + "atty", + "bit-set", + "diff", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" +dependencies = [ + "regex", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2247,6 +2501,25 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "lsp-types" +version = "0.89.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e0dedfd52cc32325598b2631e0eba31b7b708959676a9f837042f276b09a2" +dependencies = [ + "bitflags", + "serde", + "serde_json", + "serde_repr", + "url", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "matchers" version = "0.0.1" @@ -2277,6 +2550,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg 1.0.1", +] + [[package]] name = "mime" version = "0.3.16" @@ -2333,6 +2615,21 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nix" version = "0.13.1" @@ -2346,6 +2643,37 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec 0.19.6", + "funty", + "memchr", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -2400,6 +2728,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -2574,6 +2913,12 @@ dependencies = [ "syn", ] +[[package]] +name = "parity-wasm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" + [[package]] name = "parking_lot" version = "0.11.2" @@ -2670,6 +3015,50 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pharos" version = "0.5.2" @@ -2680,6 +3069,65 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.4", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" + [[package]] name = "pin-project" version = "1.0.8" @@ -2734,6 +3182,22 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "primitive-types" version = "0.10.1" @@ -2817,6 +3281,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "1.0.33" @@ -2879,12 +3349,28 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "radium" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.4.6" @@ -3110,6 +3596,16 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.3", + "redox_syscall", +] + [[package]] name = "regex" version = "1.5.4" @@ -3319,6 +3815,12 @@ dependencies = [ "base64 0.13.0", ] +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + [[package]] name = "rusty-fork" version = "0.3.0" @@ -3331,6 +3833,30 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "rustyline" +version = "9.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c38cfbd0a4d7df7aab7cf53732d5d43449d0300358fd15cd4e8c8468a956aca" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "clipboard-win", + "dirs-next", + "fd-lock", + "libc", + "log", + "memchr", + "nix 0.23.0", + "radix_trie", + "scopeguard", + "smallvec", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + [[package]] name = "ryu" version = "1.0.5" @@ -3561,6 +4087,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.0" @@ -3573,6 +4110,18 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + [[package]] name = "sha2" version = "0.8.2" @@ -3642,6 +4191,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellwords" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e515aa4699a88148ed5ef96413ceef0048ce95b43fbc955a33bde0a70fcae6" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -3661,6 +4220,12 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "siphasher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" + [[package]] name = "slab" version = "0.4.4" @@ -3683,6 +4248,45 @@ dependencies = [ "winapi", ] +[[package]] +name = "solang" +version = "0.1.9" +source = "git+https://github.com/hyperledger-labs/solang#6f1a905a2414dccccdd7e223ae34c41ceb16efd5" +dependencies = [ + "base58 0.2.0", + "bitvec 0.20.4", + "blake2-rfc", + "cc", + "clap", + "contract-metadata", + "funty", + "handlebars", + "hex", + "indexmap", + "itertools", + "lalrpop", + "lalrpop-util", + "lazy_static", + "num-bigint", + "num-derive", + "num-rational", + "num-traits", + "parity-wasm", + "phf", + "regex", + "ripemd160", + "semver 1.0.4", + "serde", + "serde_derive", + "serde_json", + "sha2 0.9.8", + "tempfile", + "tiny-keccak", + "tokio", + "tower-lsp", + "unicode-xid", +] + [[package]] name = "spin" version = "0.5.2" @@ -3704,6 +4308,25 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "str-buf" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" + +[[package]] +name = "string_cache" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.8.0", + "precomputed-hash", +] + [[package]] name = "strsim" version = "0.8.0" @@ -3712,9 +4335,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.23" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa" +checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" dependencies = [ "clap", "lazy_static", @@ -3723,9 +4346,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck", "proc-macro-error 1.0.4", @@ -3845,6 +4468,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminal_size" version = "0.1.17" @@ -4002,6 +4645,40 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-lsp" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "701465c44f9e5655785b1420f3dea1d33d15f2fea2e0a65d25c4fd91ec0e6238" +dependencies = [ + "async-trait", + "auto_impl 0.4.1", + "bytes", + "dashmap", + "futures", + "log", + "lsp-types", + "nom", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tower-lsp-macros", + "tower-service", +] + +[[package]] +name = "tower-lsp-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb72acc00b574cffa151d0725b3b26404554b371b4c6e059c58eb23f9f097140" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tower-service" version = "0.3.1" @@ -4207,8 +4884,15 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde", ] +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + [[package]] name = "uuid" version = "0.8.2" @@ -4436,6 +5120,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" + +[[package]] +name = "windows_i686_gnu" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" + +[[package]] +name = "windows_i686_msvc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" + [[package]] name = "winreg" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index af479da934b0c..1bbed9f1336c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "cast", "forge", "cli", + "shell", ] # Binary size optimizations diff --git a/shell/Cargo.toml b/shell/Cargo.toml new file mode 100644 index 0000000000000..aac4080b06180 --- /dev/null +++ b/shell/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "forge-shell" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Foundry's Solidity shell" + +[dependencies] +colored = "2.0.0" +ctrlc = "3.2.1" +rustyline = "9.1.1" +shellwords = "1.1.0" +structopt = "0.3.25" +ethers = { git = "https://github.com/gakonst/ethers-rs" } +regex = { version = "1.5.4", default-features = false } +eyre = "0.6.5" +dirs-next = "2.0.0" +log = "0.4.14" +solang = { git = "https://github.com/hyperledger-labs/solang", default-features = false } +pretty_env_logger = "0.4.0" diff --git a/shell/src/args.rs b/shell/src/args.rs new file mode 100644 index 0000000000000..1a9e986845ea2 --- /dev/null +++ b/shell/src/args.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; +use structopt::{clap::AppSettings, StructOpt}; + +/// Main forge-shell args +#[derive(Debug, StructOpt)] +#[structopt(global_settings = &[AppSettings::ColoredHelp])] +pub struct Args { + #[structopt( + help = "Select a series of libraries to pre load into session. These can be directories or single files." + )] + pub libs: Vec, + #[structopt( + help = "Select a whole project to load into session during start up.", + short = "w", + long = "workspace" + )] + pub workspace: Option, + + #[structopt(subcommand)] + pub subcommand: Option, +} + +#[derive(Debug, StructOpt)] +pub enum SubCommand { + // TODO want subcommands do we need? +} diff --git a/shell/src/cmd/help.rs b/shell/src/cmd/help.rs new file mode 100644 index 0000000000000..bed7f6194ef68 --- /dev/null +++ b/shell/src/cmd/help.rs @@ -0,0 +1,12 @@ +use colored::*; + +#[inline] +fn help(cmd: &str, descr: &str) { + println!(" {:13} {}", cmd, descr); +} + +pub fn print_help() { + println!("{}", "COMMANDS".bright_yellow()); + help("list", "lists various information"); + println!("\nRun -h for more help.\n"); +} diff --git a/shell/src/cmd/list.rs b/shell/src/cmd/list.rs new file mode 100644 index 0000000000000..b05a2e4a6beff --- /dev/null +++ b/shell/src/cmd/list.rs @@ -0,0 +1,50 @@ +use crate::{Cmd, Shell}; +use structopt::{clap::AppSettings, StructOpt}; + +#[derive(Debug, StructOpt)] +#[structopt(global_settings = &[AppSettings::ColoredHelp])] +pub enum Args { + #[structopt(about = "Lists all contract names.")] + Contracts { + #[structopt(help = "Print all contract names from the project matching that name.")] + name: Option, + #[structopt( + help = "Print all contract names from every project.", + long, + short, + conflicts_with = "name" + )] + all: bool, + }, +} + +impl Cmd for Args { + fn run(self, shell: &mut Shell) -> eyre::Result<()> { + match self { + Args::Contracts { name, all } => { + if all { + for artifacts in shell.session.artifacts.values() { + for name in artifacts.keys() { + println!("{}", name); + } + } + } else { + if let Some(artifacts) = name + .as_ref() + .or(shell.workspace()) + .map(|name| shell.session.artifacts.get(name)) + .flatten() + { + for name in artifacts.keys() { + println!("{}", name); + } + } else { + println!("."); + } + } + } + } + + Ok(()) + } +} diff --git a/shell/src/cmd/mod.rs b/shell/src/cmd/mod.rs new file mode 100644 index 0000000000000..1cdcc9c93406e --- /dev/null +++ b/shell/src/cmd/mod.rs @@ -0,0 +1,15 @@ +//! Various forge shell commands + +pub mod help; +pub mod list; +use crate::Shell; + +/// A trait for forge shell commands +pub trait Cmd: structopt::StructOpt + Sized { + fn run(self, shell: &mut Shell) -> eyre::Result<()>; + + fn run_str(shell: &mut Shell, args: &[String]) -> eyre::Result<()> { + let args = Self::from_iter_safe(args)?; + args.run(shell) + } +} diff --git a/shell/src/complete.rs b/shell/src/complete.rs new file mode 100644 index 0000000000000..f01ee745b85c7 --- /dev/null +++ b/shell/src/complete.rs @@ -0,0 +1,56 @@ +use colored::*; +use rustyline::{ + completion::Completer, highlight::Highlighter, hint::Hinter, CompletionType, EditMode, Editor, +}; +use std::borrow::Cow; + +/// Returns a new Vi edit style editor +pub fn editor() -> Editor { + let config = rustyline::Config::builder() + .edit_mode(EditMode::Vi) + .completion_type(CompletionType::List) + .keyseq_timeout(0) // https://github.com/kkawakam/rustyline/issues/371 + .build(); + Editor::with_config(config) +} +/// Type responsible to provide hints, complete, highlight +#[derive(Default)] +pub struct SolReplCompleter { + /// Stores the available command context + context: Vec, +} + +impl SolReplCompleter { + pub fn set(&mut self, context: Vec) { + self.context = context; + } + + /// Creates a new editor and register the sol repl as helper + pub fn into_editor(self) -> Editor { + let mut editor = editor(); + editor.set_helper(Some(self)); + editor + } +} + +impl rustyline::validate::Validator for SolReplCompleter {} + +impl Completer for SolReplCompleter { + type Candidate = String; +} + +impl Highlighter for SolReplCompleter { + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( + &'s self, + prompt: &'p str, + _default: bool, + ) -> Cow<'b, str> { + prompt.yellow().to_string().into() + } +} + +impl Hinter for SolReplCompleter { + type Hint = String; +} + +impl rustyline::Helper for SolReplCompleter {} diff --git a/shell/src/config.rs b/shell/src/config.rs new file mode 100644 index 0000000000000..9ac908501261f --- /dev/null +++ b/shell/src/config.rs @@ -0,0 +1,34 @@ +//! forge config + +use eyre::{Context, ContextCompat}; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Clone, Default)] +// Serialize, Deserialize +pub struct Config {} + +impl Config { + /// Returns the path to the forge toml file at `~/forge.toml` + pub fn path() -> eyre::Result { + let path = dirs_next::config_dir().wrap_err_with(|| "Failed to detect config directory")?; + let path = path.join("forge.toml"); + Ok(path) + } + + pub fn load_or_default() -> eyre::Result { + let path = Config::path()?; + if path.exists() { + Config::load_from(&path) + } else { + Ok(Config::default()) + } + } + + pub fn load_from(path: impl AsRef) -> eyre::Result { + let _config = std::fs::read(&path).wrap_err("Failed to read config file")?; + + // let config = toml::from_slice(&config)?; + // Ok(config) + todo!() + } +} diff --git a/shell/src/input.rs b/shell/src/input.rs new file mode 100644 index 0000000000000..199ea2e3f948f --- /dev/null +++ b/shell/src/input.rs @@ -0,0 +1,97 @@ +use crate::Shell; +use log::debug; +use solang::parser::pt::*; + +/// A user input +#[derive(Debug)] +pub enum Input { + /// A known command with its shellwords + Command(Command, Vec), + /// A deserialized solidity source unit + Solang(SourceUnit), + /// Unmatched input + Other(String), +} + +impl Input { + /// Consumes the line + pub fn read_line(line: impl Into, shell: &mut Shell) -> Option { + let line = line.into(); + if line.is_empty() { + None + } else { + debug!("Readline returned {:?}", line); + + shell.rl.add_history_entry(line.as_str()); + + if line.starts_with('#') { + // shell comment + return None + } + + let words = match shellwords::split(&line) { + Ok(cmd) => cmd, + Err(err) => { + eprintln!("Error: {:?}", err); + return None + } + }; + debug!("shellwords output: {:?}", words); + + if words.is_empty() { + return None + } + + // try to find a matching native command + if let Some(cmd) = Command::from_str(&words[0]) { + return Some(Input::Command(cmd, words)) + } + + // try to deserialize as supported solidity command + if let Ok(unit) = solang::parser::parse(&line, 1) { + return Some(Input::Solang(unit)) + } + + // return unresolved content, delegate eval to shell itself, like `msg.sender` + Some(Input::Other(line)) + } + } +} + +/// Various supported solang elements +#[derive(Debug)] +pub enum SolangInput { + Contract(Box), + Function(Box), + Variable(Box), + Struct(Box), + // TODO various math expressions +} + +/// various sol shell commands +#[derive(Debug)] +pub enum Command { + Dump, + Load, + Help, + Quit, + Exit, + Set, + List, + Interrupt, +} + +impl Command { + fn from_str(s: &str) -> Option { + match s { + "dump" => Some(Command::Dump), + "load" => Some(Command::Load), + "help" => Some(Command::Help), + "quit" => Some(Command::Quit), + "exit" => Some(Command::Exit), + "set" => Some(Command::Set), + "ls" | "list" => Some(Command::List), + _ => None, + } + } +} diff --git a/shell/src/lib.rs b/shell/src/lib.rs new file mode 100644 index 0000000000000..d46c52043ba20 --- /dev/null +++ b/shell/src/lib.rs @@ -0,0 +1,224 @@ +// TODO remove later +#![allow(dead_code)] +mod args; + +pub use args::Args; +mod cmd; +mod complete; +mod config; +mod input; +mod paths; +mod session; +mod term; +pub use crate::config::Config; + +use self::complete::SolReplCompleter; +use crate::{ + cmd::Cmd, + input::{Command, Input}, + session::Session, + term::Prompt, +}; +use eyre::WrapErr; +use log::debug; +use rustyline::{error::ReadlineError, Editor}; +use solang::parser::pt::SourceUnit; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + +/// Solidity shell +#[derive(Debug)] +pub struct Shell { + /// The line editor that provides user input + pub(crate) rl: Editor, + /// CTRL-C hook + pub(crate) signal_register: Arc, + /// Prompt to use when starting a new line + pub(crate) prompt: Prompt, + /// Internal context aware session + pub(crate) session: Session, + /// General config vals + pub(crate) config: Config, + /// internal CTRL-C counter + ctrl_c_counter: u8, +} + +impl Shell { + pub fn new(args: Args, config: Config) -> eyre::Result { + let rl = SolReplCompleter::default().into_editor(); + + let mut session = Session::default(); + session.libs.extend(args.libs); + + let prompt = if let Some(workspace) = args.workspace { + term::info(format!("loading workspace `{}`...", workspace.display())); + let name = session.add_project(workspace)?; + session.compile_all()?; + Prompt::new(name) + } else { + Default::default() + }; + + Ok(Self { + rl, + signal_register: Arc::new(Default::default()), + prompt, + session, + config, + ctrl_c_counter: 0, + }) + } + + /// Returns the current configured workspace if any + pub fn workspace(&self) -> Option<&String> { + self.prompt.project.as_ref() + } + + pub fn load_history(&mut self) -> eyre::Result<()> { + Ok(self.rl.load_history(&paths::history_path()?)?) + } + + pub fn save_history(&mut self) -> eyre::Result<()> { + Ok(self.rl.save_history(&paths::history_path()?)?) + } + + /// Reads the next user input line, converts that into an Input + pub fn readline(&mut self) -> Option { + let readline = self.rl.readline(&self.prompt.to_string()); + + if readline.is_ok() { + self.ctrl_c_counter = 0; + } + + match readline { + Ok(line) => Input::read_line(line, self), + Err(ReadlineError::Interrupted) => { + // ^C + self.ctrl_c_counter += 1; + + if self.ctrl_c_counter > 1 { + Some(Input::Command(Command::Interrupt, vec![])) + } else { + None + } + } + Err(ReadlineError::Eof) => { + // ^D + None + } + Err(err) => { + println!("Error: {:?}", err); + Some(Input::Command(Command::Interrupt, vec![])) + } + } + } + + /// Registers the signal handler to terminate the program once we received a CTRL-C signal + pub fn set_signal_handler(&self) -> eyre::Result<()> { + let ctr = self.signal_register.clone(); + ctrlc::set_handler(move || { + let prev = ctr.add_ctrlc(); + eprintln!("prev {}", prev); + if prev == 1 { + ::std::process::exit(0); + } + })?; + + Ok(()) + } + + /// Executes a native command + fn run_cmd(&mut self, cmd: Command, args: Vec) -> eyre::Result { + match cmd { + Command::Quit | Command::Exit | Command::Interrupt => return Ok(true), + Command::Help => { + cmd::help::print_help(); + } + Command::List => exec::(self, &args)?, + _ => {} + } + + Ok(false) + } + + /// Executes a solang input + fn on_solidty_input(&mut self, _unit: SourceUnit) -> eyre::Result<()> { + Ok(()) + } + + /// Handle an unresolved input + fn on_unknown(&mut self, s: String) -> eyre::Result<()> { + eyre::bail!("unknown input: {:?}, try \"help\"", s) + } + + /// Runs the next instruction + /// + /// Returns `true` if we should exit the shell + fn run_once(&mut self) -> eyre::Result { + let line = self.readline(); + debug!("Received line: {:?}", line); + match line { + Some(Input::Command(cmd, args)) => return self.run_cmd(cmd, args), + Some(Input::Solang(unit)) => self.on_solidty_input(unit)?, + Some(Input::Other(other)) => self.on_unknown(other)?, + None => (), + } + Ok(false) + } +} + +fn exec(shell: &mut Shell, args: &[String]) -> eyre::Result<()> { + T::run_str(shell, args) +} + +#[derive(Debug)] +pub struct SignalRegister(AtomicUsize); + +impl Default for SignalRegister { + fn default() -> Self { + SignalRegister(AtomicUsize::new(1)) + } +} + +impl SignalRegister { + pub fn catch_ctrl(&self) { + self.0.store(0, Ordering::SeqCst); + } + + pub fn add_ctrlc(&self) -> usize { + self.0.fetch_add(1, Ordering::SeqCst) + } + + pub fn ctrlc_received(&self) -> bool { + self.0.load(Ordering::SeqCst) == 1 + } + + pub fn reset_ctrlc(&self) { + self.0.store(1, Ordering::SeqCst); + } +} + +/// Starts and runs the shell until exited +pub fn run(args: Args, config: Config) -> eyre::Result<()> { + term::print_banner(); + let mut shell = Shell::new(args, config)?; + shell.load_history().ok(); + shell.set_signal_handler().wrap_err("Failed to set signal handler")?; + + loop { + match shell.run_once() { + Ok(true) => break, + Ok(_) => (), + Err(err) => { + term::error(&err.to_string()); + eprintln!("{}", err) + } + } + } + + shell.save_history()?; + + Ok(()) +} diff --git a/shell/src/main.rs b/shell/src/main.rs new file mode 100644 index 0000000000000..9bef09ef429a6 --- /dev/null +++ b/shell/src/main.rs @@ -0,0 +1,20 @@ +use forge_shell::{Args, Config}; +use structopt::StructOpt; + +fn main() -> eyre::Result<()> { + pretty_env_logger::init(); + + if let Err(err) = run() { + eprintln!("Error: {}", err); + std::process::exit(1); + } + Ok(()) +} + +// TODO move to forge cli later +fn run() -> eyre::Result<()> { + let args = Args::from_args(); + let config = Config::default(); + + forge_shell::run(args, config) +} diff --git a/shell/src/paths.rs b/shell/src/paths.rs new file mode 100644 index 0000000000000..66758809c2a74 --- /dev/null +++ b/shell/src/paths.rs @@ -0,0 +1,35 @@ +use eyre::{ContextCompat, WrapErr}; +use std::{fs, path::PathBuf}; + +const APP_NAME: &str = "forge-shell"; + +pub fn forge_shell_dir() -> eyre::Result { + let path = dirs_next::data_dir().wrap_err("Failed to find data directory")?; + let path = path.join(APP_NAME); + fs::create_dir_all(&path).wrap_err("Failed to create data directory")?; + Ok(path) +} + +pub fn project_dir() -> eyre::Result { + let path = forge_shell_dir()?.join("project"); + fs::create_dir_all(&path).wrap_err("Failed to create project directory")?; + Ok(path) +} + +pub fn history_path() -> eyre::Result { + let path = forge_shell_dir()?; + Ok(path.join("history")) +} + +pub fn data_dir() -> eyre::Result { + let path = forge_shell_dir()?.join("data"); + fs::create_dir_all(&path).wrap_err("Failed to create module directory")?; + Ok(path) +} + +pub fn cache_dir() -> eyre::Result { + let path = dirs_next::cache_dir().wrap_err("Failed to find cache directory")?; + let path = path.join(APP_NAME); + fs::create_dir_all(&path).wrap_err("Failed to create cache directory")?; + Ok(path) +} diff --git a/shell/src/session.rs b/shell/src/session.rs new file mode 100644 index 0000000000000..64c97d2d94038 --- /dev/null +++ b/shell/src/session.rs @@ -0,0 +1,71 @@ +//! The forge-shell internal session + +use crate::term; +use ethers::{ + prelude::{artifacts::Settings, SolcConfig}, + solc::{artifacts::CompactContract, EvmVersion, Project, ProjectPathsConfig}, +}; +use std::{ + collections::{BTreeMap, HashSet}, + path::{Path, PathBuf}, +}; + +#[derive(Debug, Default)] +pub struct Session { + /// All loaded projects identified by their name, the directory name + pub projects: BTreeMap, + /// Additional libraries + pub libs: HashSet, + /// all artifacts (project name -> contract -> artifact) + pub artifacts: BTreeMap>, +} + +impl Session { + /// Registers a new project + pub fn add_project(&mut self, path: impl AsRef) -> eyre::Result { + let path = path.as_ref(); + if !path.is_dir() { + eyre::bail!("\"{}\" is not a valid project path", path.display()) + } + + let dir_name = path + .file_name() + .ok_or_else(|| eyre::eyre!("Failed to get dir name \"{}\"", path.display()))? + .to_string_lossy() + .to_string(); + + if self.projects.contains_key(&dir_name) { + eyre::bail!("A project with the name \"{}\" already exists", dir_name) + } + + let solc_settings = + Settings { evm_version: Some(EvmVersion::default()), ..Default::default() }; + let paths = ProjectPathsConfig::dapptools(path)?; + let project = Project::builder() + .allowed_path(&paths.root) + .allowed_paths(paths.libraries.clone()) + .paths(paths) + .solc_config(SolcConfig::builder().settings(solc_settings).build()?) + .build()?; + self.projects.insert(dir_name.clone(), project); + Ok(dir_name) + } + + /// Compiles all registered projects + pub fn compile_all(&mut self) -> eyre::Result<()> { + for (name, project) in &self.projects { + term::info(format!("compiling `{}`", name)); + let output = project.compile()?; + if output.has_compiler_errors() { + term::error(&output.to_string()) + } else if output.is_unchanged() { + term::info(format!("no files changed, compilation skipped for `{}`.", name)); + } else { + term::success(format!("successfully compiled `{}`", name)); + } + let artifacts: BTreeMap<_, _> = output.into_artifacts().collect(); + self.artifacts.insert(name.clone(), artifacts); + } + Ok(()) + } +} diff --git a/shell/src/term.rs b/shell/src/term.rs new file mode 100644 index 0000000000000..52b0e49194e1b --- /dev/null +++ b/shell/src/term.rs @@ -0,0 +1,64 @@ +use colored::*; +use std::fmt; + +pub fn success(line: impl AsRef) { + println!("{}", line.as_ref().bright_green()); +} + +pub fn info(line: impl AsRef) { + println!("{}", line.as_ref().bright_blue()); +} + +pub fn debug(line: impl AsRef) { + println!("{}", line.as_ref().bright_cyan().dimmed()); +} + +pub fn warn(line: impl AsRef) { + eprintln!("{}", line.as_ref().yellow()); +} + +pub fn error(line: impl AsRef) { + eprintln!("{}", line.as_ref().bright_red()); +} + +#[derive(Debug, Default)] +pub struct Prompt { + /// The current set project + pub project: Option, +} + +impl Prompt { + pub fn new(project: String) -> Prompt { + Prompt { project: Some(project) } + } +} + +impl fmt::Display for Prompt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("[forge]")?; + if let Some(ref project) = self.project { + write!(f, "[{}]", project)?; + } + write!(f, " > ") + } +} + +pub fn print_banner() { + println!( + r#" + ___ + /'___) + | (__ _ _ __ __ __ + | ,__)'_`\ ( '__)'_ `\ /'__`\ + | | ( (_) )| | ( (_) |( ___/ + (_) `\___/'(_) `\__ |`\____) + ( )_) | + \___/' + {} | {} + {} +"#, + "solidity".green(), + "shell".green(), + "https://github.com/gakonst/foundry".green() + ); +}