From ab6f49c573b4b803157f8f6961b7c8184193f58e Mon Sep 17 00:00:00 2001 From: Adrian Covaci <6562353+acovaci@users.noreply.github.com> Date: Sat, 25 May 2024 12:59:20 +0100 Subject: [PATCH 1/2] feat(config): _de_serialization of config object --- .gitignore | 2 + Cargo.lock | 376 +++++++++++++++++++++++++++- Cargo.toml | 8 +- src/cli/mod.rs | 41 +++ src/cli/project.rs | 27 +- src/cli/version.rs | 6 +- src/core/config.rs | 97 ------- src/core/config/config_object.rs | 63 +++++ src/core/config/manager.rs | 101 ++++++++ src/core/config/mod.rs | 6 + src/core/{ => config}/project.rs | 0 src/core/config/transcriber.rs | 149 +++++++++++ src/core/mod.rs | 2 - src/core/settings.rs | 37 --- src/error.rs | 23 +- src/filesystem/config.rs | 79 ------ src/fs/config.rs | 32 +++ src/{filesystem => fs}/mod.rs | 0 src/{filesystem => fs}/user.rs | 12 +- src/lib.rs | 5 +- src/main.rs | 58 +---- src/testing/mod.rs | 62 +++++ src/types/location.rs | 9 +- src/types/mod.rs | 2 +- src/types/project.rs | 13 + src/types/version.rs | 10 +- tests/fixtures/manager_dump.yml | 20 ++ tests/fixtures/manager_load.yml | 20 ++ tests/fixtures/transcriber_dump.yml | 20 ++ tests/fixtures/transcriber_load.yml | 20 ++ 30 files changed, 986 insertions(+), 314 deletions(-) delete mode 100644 src/core/config.rs create mode 100644 src/core/config/config_object.rs create mode 100644 src/core/config/manager.rs create mode 100644 src/core/config/mod.rs rename src/core/{ => config}/project.rs (100%) create mode 100644 src/core/config/transcriber.rs delete mode 100644 src/core/settings.rs delete mode 100644 src/filesystem/config.rs create mode 100644 src/fs/config.rs rename src/{filesystem => fs}/mod.rs (100%) rename src/{filesystem => fs}/user.rs (78%) create mode 100644 src/testing/mod.rs create mode 100644 tests/fixtures/manager_dump.yml create mode 100644 tests/fixtures/manager_load.yml create mode 100644 tests/fixtures/transcriber_dump.yml create mode 100644 tests/fixtures/transcriber_load.yml diff --git a/.gitignore b/.gitignore index 22ed677..a7a3ae7 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,5 @@ $RECYCLE.BIN/ # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,windows,linux,rust .vscode/* +tests/tmp +tests/.config/* diff --git a/Cargo.lock b/Cargo.lock index d4187db..00e73ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "anstream" version = "0.6.14" @@ -38,7 +53,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -48,7 +63,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -62,12 +77,33 @@ dependencies = [ "syn", ] +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.5.0" @@ -86,6 +122,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + [[package]] name = "cc" version = "1.0.98" @@ -207,6 +249,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crunchy" version = "0.2.2" @@ -248,6 +305,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -257,6 +326,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -278,13 +356,19 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "git2" version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" dependencies = [ - "bitflags", + "bitflags 2.5.0", "libc", "libgit2-sys", "log", @@ -311,13 +395,19 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -340,6 +430,26 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -372,6 +482,26 @@ dependencies = [ "serde", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -448,6 +578,27 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "nom" version = "7.1.3" @@ -458,6 +609,44 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.5.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -549,6 +738,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + [[package]] name = "pkg-config" version = "0.3.30" @@ -563,8 +758,11 @@ dependencies = [ "config", "git2", "home", + "notify", "serde", + "serde_yaml", "thiserror", + "tokio", ] [[package]] @@ -585,6 +783,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "ron" version = "0.8.1" @@ -592,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64", - "bitflags", + "bitflags 2.5.0", "serde", "serde_derive", ] @@ -607,12 +814,27 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.202" @@ -653,6 +875,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.8" @@ -725,6 +960,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "bytes", + "num_cpus", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.8.13" @@ -798,6 +1057,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "url" version = "2.5.0" @@ -827,19 +1092,62 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -848,28 +1156,46 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.5" @@ -882,24 +1208,48 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.5" diff --git a/Cargo.toml b/Cargo.toml index 5b4c0a5..ed1f356 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,11 @@ clap = { version = "4.5.4", features = ["derive"] } config = "0.14.0" git2 = "0.18.3" home = "0.5.9" -serde = { version = "1.0.202", features = ["serde_derive"] } +notify = "6.1.1" +serde = { version = "1.0.202", features = ["serde_derive", "derive"] } +serde_yaml = "0.9.34" thiserror = "1.0.61" +tokio = { version = "1.37.0", features = ["fs", "io-std", "io-util", "tokio-macros", "rt-multi-thread", "test-util", "macros"] } + +[dev-dependencies] +tokio = { version = "1.37.0", features = ["test-util"] } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 06c68bc..9afcf29 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,2 +1,43 @@ +use clap::{Parser, Subcommand}; +use plumb::{ + core::config::manager::ConfigManager, + error::{PlumbError, Res}, +}; + pub mod project; pub mod version; + +#[derive(Parser, Debug)] +#[command( + name = "plumb", + about = "A project manager for your development projects." +)] + +pub struct Args { + #[command(subcommand)] + command: Option, +} + +#[derive(Debug, Subcommand)] +enum Command { + /// Prints the version of the CLI. + Version, + + /// Interact with project configurations. + Project { + #[command(subcommand)] + command: project::ProjectCommand, + }, +} + +pub fn run() -> Res<()> { + let cli = Args::parse(); + + let config = ConfigManager::try_load(None)?; + + match cli.command { + Some(Command::Version) => version::version(), + Some(Command::Project { command }) => project::run(config, command), + _ => Err(PlumbError::InvalidArguments)?, + } +} diff --git a/src/cli/project.rs b/src/cli/project.rs index 8c988d8..790358a 100644 --- a/src/cli/project.rs +++ b/src/cli/project.rs @@ -1,7 +1,20 @@ -use plumb::{core::config::PlumbConfig, error::Error, types::Project}; +use clap::Subcommand; +use plumb::{core::config::manager::ConfigManager, error::Res, types::Project}; -pub fn list(config: PlumbConfig) -> Result<(), Error> { - for project in get_projects(config) { +#[derive(Debug, Subcommand)] +pub enum ProjectCommand { + /// List all projects. + List, +} + +pub fn run(config: ConfigManager, command: ProjectCommand) -> Res<()> { + match command { + ProjectCommand::List => list(config), + } +} + +pub fn list(config: ConfigManager) -> Res<()> { + for project in get_projects(config)? { println!( "{} @ {}", project.name(), @@ -11,8 +24,8 @@ pub fn list(config: PlumbConfig) -> Result<(), Error> { Ok(()) } -fn get_projects(config: PlumbConfig) -> Vec { - config.projects().to_vec() +fn get_projects(config: ConfigManager) -> Res> { + Ok(config.config()?.projects().to_vec()) } #[cfg(test)] @@ -23,8 +36,8 @@ mod tests { #[test] fn test_get_projects() { - let config = PlumbConfig::load(None).unwrap(); - let projects = get_projects(config); + let config = ConfigManager::try_load(None).unwrap(); + let projects = get_projects(config).unwrap(); assert_eq!( projects, vec![ diff --git a/src/cli/version.rs b/src/cli/version.rs index 306417e..fd5c4ec 100644 --- a/src/cli/version.rs +++ b/src/cli/version.rs @@ -1,11 +1,11 @@ -use plumb::{error::Error, types::SemanticVersion}; +use plumb::{error::Res, types::SemanticVersion}; -pub fn version() -> Result<(), Error> { +pub fn version() -> Res<()> { println!("plumb {}", get_version()?); Ok(()) } -fn get_version() -> Result { +fn get_version() -> Res { SemanticVersion::from_env() } diff --git a/src/core/config.rs b/src/core/config.rs deleted file mode 100644 index 6aea758..0000000 --- a/src/core/config.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::{collections::HashMap, path::Path}; - -use crate::{ - error::Error, - filesystem::config::ConfigFile, - types::{location::LocationKey, DefaultLocation, Location, Project}, -}; - -use super::{project::ProjectBuilder, settings::SettingsBuilder}; - -pub struct PlumbConfig { - locations: HashMap, - settings: PlumbSettings, - projects: Vec, -} - -#[derive(Debug, Clone, Default)] -pub struct PlumbSettings { - pub(self) default_project_location: Option, -} - -impl PlumbSettings { - pub fn new(default_locations: HashMap) -> Self { - Self { - default_project_location: default_locations.get(&DefaultLocation::Projects).cloned(), - } - } -} - -impl PlumbConfig { - pub fn new(locations: Vec) -> Self { - Self { - locations: locations - .into_iter() - .map(|l| (l.key().clone(), l)) - .collect(), - projects: vec![], - settings: PlumbSettings::default(), - } - } - - pub fn load(path: Option<&Path>) -> Result { - let config = ConfigFile::load(path)?; - let locations = config.parse_locations(); - let mut plumb_config = Self::new(locations); - - let mut settings_builder = plumb_config.settings_builder(); - - let settings = config.parse_settings(&mut settings_builder)?; - - plumb_config.settings = settings; - let project_builder = plumb_config - .project_builder() - .unwrap_or_else(|| panic!("No project builder found")); - - let projects = config.parse_projects(project_builder); - - for project in projects { - plumb_config.add_project(project); - } - - Ok(plumb_config) - } - - pub fn add_project(&mut self, project: Project) { - self.projects.push(project); - } - - pub fn project_builder(&self) -> Option { - Some(ProjectBuilder::new( - self.default_location_for(DefaultLocation::Projects)? - .clone(), - )) - } - - fn settings_builder(&self) -> SettingsBuilder { - SettingsBuilder::new(self) - } - - pub fn list_locations(&self) -> Vec<&Location> { - self.locations.values().collect() - } - - pub fn get_location(&self, key: LocationKey) -> Option<&Location> { - self.locations.get(&key) - } - - pub fn default_location_for(&self, kind: DefaultLocation) -> Option<&Location> { - match kind { - DefaultLocation::Projects => self.settings.default_project_location.as_ref(), - } - } - - pub fn projects(&self) -> &Vec { - &self.projects - } -} diff --git a/src/core/config/config_object.rs b/src/core/config/config_object.rs new file mode 100644 index 0000000..21ce842 --- /dev/null +++ b/src/core/config/config_object.rs @@ -0,0 +1,63 @@ +use std::collections::HashMap; + +use crate::{ + error::{PlumbError, Res}, + types::{location::LocationKey, Location, Project}, +}; + +use super::project::ProjectBuilder; + +#[derive(Debug, Clone, PartialEq)] +pub struct PlumbConfig { + pub(super) locations: PlumbLocations, + pub(super) projects: Vec, +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub(super) struct PlumbLocations { + pub defaults: PlumbDefaultLocations, + pub projects: HashMap, +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub(super) struct PlumbDefaultLocations { + pub projects: Option, +} + +impl PlumbConfig { + pub fn add_project(&mut self, project: Project) { + self.projects.push(project); + } + + pub fn new_project(&mut self, name: &str, location: Option) -> Res { + let builder = self.project_builder()?; + let project = builder.build(name, location); + self.add_project(project.clone()); + Ok(project) + } + + pub fn project_builder(&self) -> Res { + let default_location = self + .default_project_location() + .ok_or_else(|| PlumbError::NoDefaultLocation("projects".into()))? + .clone(); + + Ok(ProjectBuilder::new(default_location)) + } + + pub fn default_project_location(&self) -> Option<&Location> { + self.locations.defaults.projects.as_ref() + } + + pub fn list_project_locations(&self) -> Vec<&Location> { + self.locations.projects.values().collect() + } + + pub fn get_location(&self, key: LocationKey) -> Option<&Location> { + self.locations.projects.get(&key) + } + + pub fn projects(&self) -> &Vec { + &self.projects + } +} diff --git a/src/core/config/manager.rs b/src/core/config/manager.rs new file mode 100644 index 0000000..9c78114 --- /dev/null +++ b/src/core/config/manager.rs @@ -0,0 +1,101 @@ +use std::{ + fs::File, + io::Write, + path::{Path, PathBuf}, +}; + +use crate::{ + error::{PlumbError, Res}, + fs::{config::ConfigFile, user::get_config_file}, +}; + +use super::{transcriber::ConfigTranscriber, PlumbConfig}; + +// todo: clean up this structure somehow +#[derive(Debug, Clone)] +pub struct ConfigManager { + path: PathBuf, + builder: config::ConfigBuilder, + config: Option, +} + +impl ConfigManager { + pub fn new(path: Option<&Path>) -> Res { + let default_path = get_config_file()?; + let path = path.unwrap_or(&default_path); + let file = config::File::from(path); + let builder = config::Config::builder().add_source(file); + + Ok(Self { + path: path.into(), + builder, + config: None, + }) + } + + pub fn try_load(path: Option<&Path>) -> Res { + let mut manager = Self::new(path)?; + manager.load()?; + Ok(manager) + } + + pub fn config(&self) -> Res<&PlumbConfig> { + self.config.as_ref().ok_or(PlumbError::EmptyConfigFile) + } + + pub fn update(&mut self, config: PlumbConfig) -> Res<()> { + self.config = Some(config); + self.dump() + } + + pub fn load(&mut self) -> Res { + let config: ConfigFile = self.builder.build_cloned()?.try_deserialize()?; + let plumb_config = PlumbConfig::load(config)?; + self.config = Some(plumb_config.clone()); + Ok(plumb_config) + } + + pub fn dump_to(&self, path: &Path) -> Res<()> { + let config = self.config.as_ref().ok_or(PlumbError::EmptyConfigFile)?; + let content = serde_yaml::to_string(&config.dump())?; + let mut file = File::create(path)?; + file.write_all(content.as_bytes())?; + Ok(()) + } + + pub fn dump(&self) -> Res<()> { + self.dump_to(&self.path) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::testing::ExpectedFile; + + use super::*; + + #[test] + fn test_load_then_dump() { + let target_file = ExpectedFile::new("tests/fixtures/manager_dump.yml".into()); + let temp_file = target_file.temp_file(); + + let source_file = PathBuf::from("tests/fixtures/manager_load.yml"); + let mut manager = ConfigManager::new(Some(&source_file)).unwrap(); + + let source_config = manager.load().unwrap(); + + manager.dump_to(&temp_file).unwrap(); + + let target_config = ConfigManager::new(Some(&temp_file)) + .unwrap() + .load() + .unwrap(); + + assert_eq!(source_config, target_config); + + // todo: Figure out a way to assert this ignoring the vec/key order + // target_file.assert(); + } +} diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs new file mode 100644 index 0000000..1a3e5a6 --- /dev/null +++ b/src/core/config/mod.rs @@ -0,0 +1,6 @@ +pub mod config_object; +pub mod manager; +pub mod project; +pub mod transcriber; + +pub use config_object::PlumbConfig; diff --git a/src/core/project.rs b/src/core/config/project.rs similarity index 100% rename from src/core/project.rs rename to src/core/config/project.rs diff --git a/src/core/config/transcriber.rs b/src/core/config/transcriber.rs new file mode 100644 index 0000000..01589ef --- /dev/null +++ b/src/core/config/transcriber.rs @@ -0,0 +1,149 @@ +use std::collections::HashMap; + +use crate::{ + error::{PlumbError, Res}, + fs::config::{ + ConfigFile, LocationChunk, LocationDefaultsChunk, LocationsConfigChunk, ProjectChunk, + }, + types::{Location, Project}, +}; + +use super::{ + config_object::{PlumbDefaultLocations, PlumbLocations}, + PlumbConfig, +}; + +pub trait ConfigTranscriber { + fn load(config_file: ConfigFile) -> Res + where + Self: Sized; + fn dump(&self) -> ConfigFile; +} + +impl ConfigTranscriber for PlumbConfig { + fn dump(&self) -> ConfigFile { + let projects = self + .projects + .iter() + .map(|project| { + let name = project.name().to_string(); + let path = project.location().path().to_path_buf(); + ProjectChunk { + name, + path: Some(path), + } + }) + .collect(); + + let default_project_location_key = self + .default_project_location() + .map(|location| location.key().clone()); + + let location_defaults = + default_project_location_key.map(|projects| LocationDefaultsChunk { projects }); + + let project_locations = self + .locations + .projects + .values() + .map(|location| { + let name = location.name().clone(); + let path = location.path().to_path_buf(); + LocationChunk { name, path } + }) + .collect(); + + let locations = LocationsConfigChunk { + projects: project_locations, + defaults: location_defaults, + }; + + ConfigFile { + locations, + projects, + } + } + + fn load(config_file: ConfigFile) -> Res { + let locations_config_chunk = config_file.locations; + let project_locations: HashMap = locations_config_chunk + .projects + .iter() + .map(|location| { + ( + location + .name + .clone() + .unwrap_or(location.path.to_string_lossy().into_owned()), + Location::new(location.path.clone(), location.name.clone()), + ) + }) + .collect(); + let default_project_location_key = locations_config_chunk + .defaults + .ok_or(PlumbError::NoDefaultLocation(String::new()))? + .projects; + let default_project_location = project_locations + .get(&default_project_location_key) + .cloned() + .ok_or(PlumbError::NoDefaultLocation("projects".into()))?; + let locations = PlumbLocations { + defaults: PlumbDefaultLocations { + projects: Some(default_project_location.clone()), + }, + projects: project_locations, + }; + + let projects = config_file + .projects + .into_iter() + .map(|project| { + Project::new( + &project.name, + Location::new( + project + .path + .unwrap_or(default_project_location.clone().path().to_path_buf()), + None, + ), + ) + }) + .collect(); + + Ok(Self { + locations, + projects, + }) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::{core::config::manager::ConfigManager, testing::ExpectedFile}; + + #[test] + fn test_transcribe() { + let target_file = ExpectedFile::new("tests/fixtures/transcriber_dump.yml".into()); + let temp_file = target_file.temp_file(); + + let source_file = PathBuf::from("tests/fixtures/transcriber_load.yml"); + let mut manager = ConfigManager::new(Some(&source_file)).unwrap(); + let source_config = manager.load().unwrap(); + + manager.update(source_config.clone()).unwrap(); + manager.dump_to(&temp_file).unwrap(); + + let target_config = ConfigManager::new(Some(&temp_file)) + .unwrap() + .load() + .unwrap(); + + assert_eq!(source_config, target_config); + + // todo: Either implement assert in a way that ignores key/vec order or force order on the + // todo: expected file + //target_file.assert(); + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 4d107a8..ef68c36 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1 @@ pub mod config; -pub mod project; -pub mod settings; diff --git a/src/core/settings.rs b/src/core/settings.rs deleted file mode 100644 index cf12f29..0000000 --- a/src/core/settings.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::collections::HashMap; - -use crate::error::Error; -use crate::types::DefaultLocation; - -use super::config::{PlumbConfig, PlumbSettings}; - -pub struct SettingsBuilder<'a> { - config: &'a PlumbConfig, - default_locations: HashMap, -} - -impl SettingsBuilder<'_> { - pub fn new(config: &PlumbConfig) -> SettingsBuilder { - SettingsBuilder { - config, - default_locations: HashMap::new(), - } - } - - pub fn add_default_location(&mut self, kind: DefaultLocation, location: String) { - self.default_locations.insert(kind, location); - } - - pub fn build(&self) -> Result { - let default_locations = self - .default_locations - .iter() - .filter_map(|(k, v)| { - let location = self.config.get_location(v.to_string()); - location.map(|l| (k.clone(), l.clone())) - }) - .collect(); - - Ok(PlumbSettings::new(default_locations)) - } -} diff --git a/src/error.rs b/src/error.rs index 1a38cc9..84f6cd0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,21 @@ use thiserror::Error; -pub type Error = PlumbError; +pub type Res = Result; #[derive(Debug, Error)] pub enum PlumbError { #[error("Could not find home directory")] HomeDirNotFound, + #[error("Invalid config file format: {0}")] + InvalidConfigFileFormat(String), + + #[error("Attempting to write an empty configuration file")] + EmptyConfigFile, + + #[error("Attempting to write with no configuration manager")] + NoConfigManager, + #[error("Could not parse version string: {0}")] VersionParseError(String), @@ -18,4 +27,16 @@ pub enum PlumbError { #[error("Invalid arguments. Please see `plumb help` for more information.")] InvalidArguments, + + #[error("No default location found for {0}")] + NoDefaultLocation(String), + + #[error("Could not execute git command: {0}")] + GitError(#[from] git2::Error), + + #[error("Could not write the configuration file: {0}")] + WriteConfigError(#[from] serde_yaml::Error), + + #[error("Unknown error: {0}")] + UnknownError(#[from] std::boxed::Box), } diff --git a/src/filesystem/config.rs b/src/filesystem/config.rs deleted file mode 100644 index 6fc162b..0000000 --- a/src/filesystem/config.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::path::{Path, PathBuf}; - -use serde::Deserialize; - -use crate::{ - core::{config::PlumbSettings, project::ProjectBuilder, settings::SettingsBuilder}, - error::Error, - types::{location::DefaultLocation, Location, Project}, -}; - -use super::user::get_config_file; - -#[derive(Debug, Deserialize, Clone)] -pub struct ConfigFile { - pub locations: LocationsConfigChunk, - pub projects: Vec, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct LocationsConfigChunk { - pub(self) projects: Vec, - pub(self) defaults: LocationDefaultsConfig, -} - -#[derive(Debug, Deserialize, Clone)] -struct LocationChunk { - path: PathBuf, - name: Option, -} - -#[derive(Debug, Deserialize, Clone)] -struct LocationDefaultsConfig { - projects: String, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct ProjectChunk { - pub(self) name: String, - pub(self) location: Option, -} - -impl ConfigFile { - pub fn load(path: Option<&Path>) -> Result { - let path = path.map(PathBuf::from).unwrap_or(get_config_file()?); - - let config = config::Config::builder() - .add_source(config::File::from(path)) - .build()? - .try_deserialize::()?; - - Ok(config) - } - - pub fn parse_locations(&self) -> Vec { - self.locations - .projects - .iter() - .map(|l| Location::new(l.path.clone(), l.name.clone())) - .collect() - } - - pub fn parse_projects(&self, builder: ProjectBuilder) -> Vec { - self.projects - .iter() - .map(|p| { - builder.build( - p.name.as_str(), - p.location.as_ref().map(|l| Location::new(l.clone(), None)), - ) - }) - .collect() - } - - pub fn parse_settings(&self, builder: &mut SettingsBuilder) -> Result { - let default_locations = self.locations.defaults.clone(); - builder.add_default_location(DefaultLocation::Projects, default_locations.projects); - builder.build() - } -} diff --git a/src/fs/config.rs b/src/fs/config.rs new file mode 100644 index 0000000..5d7f500 --- /dev/null +++ b/src/fs/config.rs @@ -0,0 +1,32 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Clone, Serialize, PartialEq)] +pub struct ConfigFile { + pub locations: LocationsConfigChunk, + pub projects: Vec, +} + +#[derive(Debug, Deserialize, Clone, Serialize, PartialEq)] +pub struct LocationsConfigChunk { + pub projects: Vec, + pub defaults: Option, +} + +#[derive(Debug, Deserialize, Clone, Serialize, PartialEq)] +pub struct LocationChunk { + pub path: PathBuf, + pub name: Option, +} + +#[derive(Debug, Deserialize, Clone, Serialize, PartialEq)] +pub struct LocationDefaultsChunk { + pub projects: String, +} + +#[derive(Debug, Deserialize, Clone, Serialize, PartialEq)] +pub struct ProjectChunk { + pub name: String, + pub path: Option, +} diff --git a/src/filesystem/mod.rs b/src/fs/mod.rs similarity index 100% rename from src/filesystem/mod.rs rename to src/fs/mod.rs diff --git a/src/filesystem/user.rs b/src/fs/user.rs similarity index 78% rename from src/filesystem/user.rs rename to src/fs/user.rs index 2d673ac..d2a2a15 100644 --- a/src/filesystem/user.rs +++ b/src/fs/user.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; -use crate::error::Error; +use crate::error::{PlumbError, Res}; -pub fn get_config_file() -> Result { +pub fn get_config_file() -> Res { if let Some(override_path) = get_config_file_override() { return Ok(override_path); } @@ -12,12 +12,12 @@ pub fn get_config_file() -> Result { Ok(config_dir.join(config_file)) } -pub fn get_config_filename() -> Result { +pub fn get_config_filename() -> Res { const DEFAULT_CONFIG: &str = "plumb.yaml"; Ok(DEFAULT_CONFIG.to_string()) } -pub fn get_config_dir() -> Result { +pub fn get_config_dir() -> Res { if let Ok(config_dir) = std::env::var("XDG_CONFIG_HOME") { return Ok(PathBuf::from(config_dir)); } @@ -39,11 +39,11 @@ pub fn get_config_file_override() -> Option { None } -pub fn get_home_dir() -> Result { +pub fn get_home_dir() -> Res { if cfg!(test) || cfg!(debug_assertions) { let cwd = std::env::current_dir()?; return Ok(cwd.join("tests")); } - home::home_dir().ok_or(Error::HomeDirNotFound) + home::home_dir().ok_or(PlumbError::HomeDirNotFound) } diff --git a/src/lib.rs b/src/lib.rs index fd5e178..c072cf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,7 @@ pub mod core; pub mod error; -pub mod filesystem; +pub mod fs; pub mod types; + +#[cfg(debug_assertions)] +pub mod testing; diff --git a/src/main.rs b/src/main.rs index ac1be3a..8ede736 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,42 +1,9 @@ -use clap::{Parser, Subcommand}; - -use plumb::{core::config::PlumbConfig, error::Error}; +use cli::run; mod cli; -#[derive(Parser, Debug)] -#[command( - name = "plumb", - about = "A project manager for your development projects." -)] - -struct Args { - #[command(subcommand)] - command: Option, -} - -#[derive(Debug, Subcommand)] -enum Command { - /// Prints the version of the CLI. - Version, - - /// Interact with project configurations. - Project { - #[command(subcommand)] - command: ProjectCommand, - }, -} - -#[derive(Debug, Subcommand)] -enum ProjectCommand { - /// List all projects. - List, -} - pub fn main() { - let cli = Args::parse(); - - match run(cli) { + match run() { Ok(_) => {} Err(e) => { eprintln!("Error: {}", e); @@ -44,24 +11,3 @@ pub fn main() { } } } - -fn run(cli: Args) -> Result<(), Error> { - let config = PlumbConfig::load(None).unwrap_or_else(|e| { - eprintln!("Error loading configuration: {}", e); - std::process::exit(1); - }); - - match cli.command { - Some(Command::Version) => { - cli::version::version()?; - } - Some(Command::Project { command }) => match command { - ProjectCommand::List => { - cli::project::list(config)?; - } - }, - _ => Err(Error::InvalidArguments)?, - } - - Ok(()) -} diff --git a/src/testing/mod.rs b/src/testing/mod.rs new file mode 100644 index 0000000..993d9a5 --- /dev/null +++ b/src/testing/mod.rs @@ -0,0 +1,62 @@ +use std::{ + env::temp_dir, + fs::{read_to_string, remove_file}, + path::{Path, PathBuf}, +}; + +pub struct TestDir(PathBuf); + +impl Default for TestDir { + fn default() -> Self { + Self::new() + } +} + +impl TestDir { + pub fn new() -> Self { + let path = temp_dir().join("plumb"); + std::fs::create_dir_all(&path).unwrap(); + Self(path) + } + + pub fn path(&self) -> &Path { + &self.0 + } + + pub fn file(&self, name: &str) -> PathBuf { + self.0.join(name) + } +} + +pub struct ExpectedFile { + pub expected: PathBuf, + pub actual: PathBuf, +} + +impl ExpectedFile { + pub fn new(path: PathBuf) -> Self { + let actual = TestDir::new().file(path.file_name().unwrap().to_str().unwrap()); + + Self { + expected: path, + actual: actual.clone(), + } + } + + pub fn temp_file(&self) -> PathBuf { + self.actual.clone() + } + + pub fn assert(&self) { + let expected = read_to_string(&self.expected).unwrap(); + let actual = read_to_string(&self.actual).unwrap(); + remove_file(&self.actual).unwrap(); + assert_eq!(expected, actual); + } +} + +pub fn assert_files_equal(expected: &Path, actual: &Path) { + let expected = read_to_string(expected).unwrap(); + let actual = read_to_string(actual).unwrap(); + assert_eq!(expected, actual); +} diff --git a/src/types/location.rs b/src/types/location.rs index 97c5e62..3df1984 100644 --- a/src/types/location.rs +++ b/src/types/location.rs @@ -9,11 +9,6 @@ pub struct Location { name: Option, } -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum DefaultLocation { - Projects, -} - impl Location { pub fn new(path: PathBuf, name: Option) -> Self { Self { @@ -34,4 +29,8 @@ impl Location { pub fn key(&self) -> &LocationKey { &self.key } + + pub fn join(&self, path: &str) -> Location { + Location::new(self.path.join(path), None) + } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 214617b..aa31f75 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -2,6 +2,6 @@ pub mod location; pub mod project; pub mod version; -pub use location::{DefaultLocation, Location}; +pub use location::Location; pub use project::Project; pub use version::SemanticVersion; diff --git a/src/types/project.rs b/src/types/project.rs index 7796e0a..faf7b42 100644 --- a/src/types/project.rs +++ b/src/types/project.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use super::Location; #[derive(Debug, Clone, Eq, PartialEq)] @@ -14,6 +16,13 @@ impl Project { } } + pub fn new_in_location(name: &str, location: &Location) -> Self { + Self { + name: name.to_string(), + location: location.join(name), + } + } + pub fn name(&self) -> &String { &self.name } @@ -21,4 +30,8 @@ impl Project { pub fn location(&self) -> &Location { &self.location } + + pub fn path(&self) -> &PathBuf { + self.location.path() + } } diff --git a/src/types/version.rs b/src/types/version.rs index 7f07854..08537d2 100644 --- a/src/types/version.rs +++ b/src/types/version.rs @@ -1,6 +1,6 @@ use std::fmt::{Display, Formatter}; -use crate::error::Error; +use crate::error::{PlumbError, Res}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SemanticVersion { @@ -18,7 +18,7 @@ impl SemanticVersion { } } - pub fn from_env() -> Result { + pub fn from_env() -> Res { Self::try_from(env!("CARGO_PKG_VERSION")) } @@ -36,13 +36,13 @@ impl SemanticVersion { } impl TryFrom<&str> for SemanticVersion { - type Error = Error; + type Error = PlumbError; - fn try_from(version: &str) -> Result { + fn try_from(version: &str) -> Res { let parts: Vec<&str> = version.split('.').collect(); let (major, minor, patch) = match (parts[0].parse(), parts[1].parse(), parts[2].parse()) { (Ok(major), Ok(minor), Ok(patch)) => (major, minor, patch), - _ => return Err(Error::VersionParseError(version.to_string())), + _ => return Err(PlumbError::VersionParseError(version.to_string())), }; Ok(Self { diff --git a/tests/fixtures/manager_dump.yml b/tests/fixtures/manager_dump.yml new file mode 100644 index 0000000..3b05c04 --- /dev/null +++ b/tests/fixtures/manager_dump.yml @@ -0,0 +1,20 @@ +--- +locations: + projects: + - path: ~/projects + name: projects + - path: ~/other_projects + name: + - path: ~/yet_another_projects + name: + defaults: + projects: projects +projects: + - name: project1 + path: + - name: project2 + path: ~/other_projects/project2 + - name: project3 + path: + - name: project4 + path: diff --git a/tests/fixtures/manager_load.yml b/tests/fixtures/manager_load.yml new file mode 100644 index 0000000..3b05c04 --- /dev/null +++ b/tests/fixtures/manager_load.yml @@ -0,0 +1,20 @@ +--- +locations: + projects: + - path: ~/projects + name: projects + - path: ~/other_projects + name: + - path: ~/yet_another_projects + name: + defaults: + projects: projects +projects: + - name: project1 + path: + - name: project2 + path: ~/other_projects/project2 + - name: project3 + path: + - name: project4 + path: diff --git a/tests/fixtures/transcriber_dump.yml b/tests/fixtures/transcriber_dump.yml new file mode 100644 index 0000000..415cf0d --- /dev/null +++ b/tests/fixtures/transcriber_dump.yml @@ -0,0 +1,20 @@ +--- +locations: + projects: + - path: ~/yet_another_projects + name: + - path: ~/other_projects + name: + - path: ~/projects + name: projects + defaults: + projects: projects +projects: + - name: project1 + path: ~/projects + - name: project2 + path: ~/other_projects/project2 + - name: project3 + path: ~/projects + - name: project4 + path: ~/projects diff --git a/tests/fixtures/transcriber_load.yml b/tests/fixtures/transcriber_load.yml new file mode 100644 index 0000000..47b0f7f --- /dev/null +++ b/tests/fixtures/transcriber_load.yml @@ -0,0 +1,20 @@ +--- +locations: + projects: + - path: ~/projects + name: projects + - path: ~/other_projects + name: + - path: ~/yet_another_projects + name: + defaults: + projects: projects +projects: + - name: project1 + path: ~/projects + - name: project2 + path: ~/other_projects/project2 + - name: project3 + path: ~/projects + - name: project4 + path: ~/projects From a1489c5981fb1cdbf8a746b51fa310922d78b014 Mon Sep 17 00:00:00 2001 From: Adrian Covaci <6562353+acovaci@users.noreply.github.com> Date: Sat, 25 May 2024 13:14:53 +0100 Subject: [PATCH 2/2] fix(project): Now projects are within their default dirs, not the dirs themselves --- src/cli/project.rs | 24 +++++++++++++++++++++--- src/core/config/project.rs | 8 +------- src/core/config/transcriber.rs | 2 +- tests/fixtures/manager_load.yml | 5 ----- tests/fixtures/project_list.yml | 15 +++++++++++++++ tests/fixtures/transcriber_dump.yml | 6 +++--- tests/fixtures/transcriber_load.yml | 4 ++-- 7 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 tests/fixtures/project_list.yml diff --git a/src/cli/project.rs b/src/cli/project.rs index 790358a..7d2f707 100644 --- a/src/cli/project.rs +++ b/src/cli/project.rs @@ -30,19 +30,37 @@ fn get_projects(config: ConfigManager) -> Res> { #[cfg(test)] mod tests { + use std::path::PathBuf; + use super::*; use plumb::types::Location; #[test] fn test_get_projects() { - let config = ConfigManager::try_load(None).unwrap(); + let config = + ConfigManager::try_load(Some(&PathBuf::from("tests/fixtures/project_list.yml"))) + .unwrap(); let projects = get_projects(config).unwrap(); assert_eq!( projects, vec![ - Project::new("plumb", Location::new("~/projects/plumb".into(), None)), - Project::new("monad", Location::new("~/projects/monad".into(), None)) + Project::new( + "project1", + Location::new("~/projects/project1".into(), None) + ), + Project::new( + "project2", + Location::new("~/other_projects/project2".into(), None) + ), + Project::new( + "project3", + Location::new("~/projects/project3".into(), None) + ), + Project::new( + "project4", + Location::new("~/projects/project4".into(), None) + ) ] ); } diff --git a/src/core/config/project.rs b/src/core/config/project.rs index becd593..1df1230 100644 --- a/src/core/config/project.rs +++ b/src/core/config/project.rs @@ -12,12 +12,6 @@ impl ProjectBuilder { } pub fn build(&self, name: &str, location: Option) -> Project { - Project::new( - name, - location.unwrap_or({ - let path = self.location.path().join(name); - Location::new(path, None) - }), - ) + Project::new(name, location.unwrap_or(self.location.join(name))) } } diff --git a/src/core/config/transcriber.rs b/src/core/config/transcriber.rs index 01589ef..9bb633c 100644 --- a/src/core/config/transcriber.rs +++ b/src/core/config/transcriber.rs @@ -103,7 +103,7 @@ impl ConfigTranscriber for PlumbConfig { Location::new( project .path - .unwrap_or(default_project_location.clone().path().to_path_buf()), + .unwrap_or(default_project_location.join(&project.name).path().clone()), None, ), ) diff --git a/tests/fixtures/manager_load.yml b/tests/fixtures/manager_load.yml index 3b05c04..1949415 100644 --- a/tests/fixtures/manager_load.yml +++ b/tests/fixtures/manager_load.yml @@ -4,17 +4,12 @@ locations: - path: ~/projects name: projects - path: ~/other_projects - name: - path: ~/yet_another_projects - name: defaults: projects: projects projects: - name: project1 - path: - name: project2 path: ~/other_projects/project2 - name: project3 - path: - name: project4 - path: diff --git a/tests/fixtures/project_list.yml b/tests/fixtures/project_list.yml new file mode 100644 index 0000000..1949415 --- /dev/null +++ b/tests/fixtures/project_list.yml @@ -0,0 +1,15 @@ +--- +locations: + projects: + - path: ~/projects + name: projects + - path: ~/other_projects + - path: ~/yet_another_projects + defaults: + projects: projects +projects: + - name: project1 + - name: project2 + path: ~/other_projects/project2 + - name: project3 + - name: project4 diff --git a/tests/fixtures/transcriber_dump.yml b/tests/fixtures/transcriber_dump.yml index 415cf0d..b65bdab 100644 --- a/tests/fixtures/transcriber_dump.yml +++ b/tests/fixtures/transcriber_dump.yml @@ -11,10 +11,10 @@ locations: projects: projects projects: - name: project1 - path: ~/projects + path: ~/projects/project1 - name: project2 path: ~/other_projects/project2 - name: project3 - path: ~/projects + path: ~/projects/project3 - name: project4 - path: ~/projects + path: ~/projects/project4 diff --git a/tests/fixtures/transcriber_load.yml b/tests/fixtures/transcriber_load.yml index 47b0f7f..dcef115 100644 --- a/tests/fixtures/transcriber_load.yml +++ b/tests/fixtures/transcriber_load.yml @@ -1,10 +1,10 @@ --- locations: projects: - - path: ~/projects - name: projects - path: ~/other_projects name: + - path: ~/projects + name: projects - path: ~/yet_another_projects name: defaults: