From 95c73dd9fdf507fbc167018c33b3c832dbe1a0db Mon Sep 17 00:00:00 2001 From: bin liu <liubin0329@gmail.com> Date: Wed, 23 Nov 2022 21:10:49 +0800 Subject: [PATCH 1/7] add `check` sub command to check if a key exists It will check if the key exists. Signed-off-by: bin liu <liubin0329@gmail.com> --- README.md | 40 +++++++++++++++++++++++++++++++++++++--- src/main.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7f7d25d..a982f29 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,43 @@ FLAGS: -V, --version Prints version information SUBCOMMANDS: - get Print some data from the file - help Prints this message or the help of the given subcommand(s) - set Edit the file to set some data (currently, just print modified version) + check Check if a key exists + get Print some data from the file + help Prints this message or the help of the given subcommand(s) + set Edit the file to set some data (currently, just print modified version) +``` + +### `toml check` + +``` +$ toml check --help +toml-check 0.2.0 +Check if a key exists + +USAGE: + toml check <path> <query> + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +ARGS: + <path> Path to the TOML file to read + <query> Query within the TOML data (e.g. `dependencies.serde`, `foo[0].bar`) +``` + +Check whether a key exists. It will print `true` to stdout in case exists, and set exit code to `0`, +otherwise it will print `false` to stderr and set exit code to `1`. + +```sh +$ toml check test.toml plugins.name2 +flase +$ echo $? +1 +$ toml check test.toml plugins.name +true +$ echo $? +0 ``` ### `toml get` diff --git a/src/main.rs b/src/main.rs index 0d1c10f..c9d5185 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,20 @@ use structopt::StructOpt; use toml_edit::{value, Document, Item, Table, Value}; use query_parser::{parse_query, Query, TpathSegment}; +use TpathSegment::{Name, Num}; // TODO: Get more of the description in the README into the CLI help. #[derive(StructOpt)] #[structopt(about)] enum Args { + /// Check if a key exists + Check { + /// Path to the TOML file to read + #[structopt(parse(from_os_str))] + path: PathBuf, + /// Query within the TOML data (e.g. `dependencies.serde`, `foo[0].bar`) + query: String, + }, /// Print some data from the file Get { /// Path to the TOML file to read @@ -58,6 +67,7 @@ enum CliError { fn main() -> Result<(), Error> { let args = Args::from_args(); match args { + Args::Check { path, query } => check(path, &query), Args::Get { path, query, opts } => get(path, &query, opts)?, Args::Set { path, @@ -75,6 +85,41 @@ fn read_parse(path: PathBuf) -> Result<Document, Error> { Ok(data.parse::<Document>()?) } +fn check_exists(path: PathBuf, query: &str) -> Result<bool, Error> { + let tpath = parse_query_cli(query)?.0; + let doc = read_parse(path)?; + let mut item = doc.as_item(); + + for seg in tpath { + match seg { + Name(n) => { + let i = item.get(n); + if i.is_none() { + return Ok(false); + } + item = i.unwrap(); + } + Num(n) => item = &item[n], + } + } + + Ok(true) +} + +/// Check whether a key exists. +/// It will print 'true' to stdout in case exists, and set exit code to '0' +/// otherwise it will print 'false' to stderr and set exit code to '1' +fn check(path: PathBuf, query: &str) { + if let Ok(r) = check_exists(path, query) { + if r { + println!("true"); + std::process::exit(0); + } + } + eprintln!("flase"); + std::process::exit(1); +} + fn get(path: PathBuf, query: &str, opts: GetOpts) -> Result<(), Error> { let tpath = parse_query_cli(query)?.0; let doc = read_parse(path)?; @@ -90,8 +135,6 @@ fn get(path: PathBuf, query: &str, opts: GetOpts) -> Result<(), Error> { } fn print_toml_fragment(doc: &Document, tpath: &[TpathSegment]) { - use TpathSegment::{Name, Num}; - let mut item = doc.as_item(); let mut breadcrumbs = vec![]; for seg in tpath { @@ -141,7 +184,6 @@ fn set(path: PathBuf, query: &str, value_str: &str) -> Result<(), Error> { let mut item = doc.as_item_mut(); let mut already_inline = false; let mut tpath = &tpath[..]; - use TpathSegment::{Name, Num}; while let Some(seg) = tpath.first() { tpath = &tpath[1..]; // TODO simplify to `for`, unless end up needing a tail match seg { @@ -192,7 +234,6 @@ fn parse_query_cli(query: &str) -> Result<Query, CliError> { } fn walk_tpath<'a>(mut item: &'a toml_edit::Item, tpath: &[TpathSegment]) -> &'a toml_edit::Item { - use TpathSegment::{Name, Num}; for seg in tpath { match seg { Name(n) => item = &item[n], From 652f864decf893814add3b48221ee5505f0139fe Mon Sep 17 00:00:00 2001 From: bin liu <liubin0329@gmail.com> Date: Wed, 23 Nov 2022 22:06:18 +0800 Subject: [PATCH 2/7] add realse action to release tarball for downloading Signed-off-by: bin liu <liubin0329@gmail.com> --- .github/workflows/release.yml | 85 +++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..dc4109e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,85 @@ +name: release + +on: + push: + tags: + - v[0-9]+.[0-9]+.[0-9]+* + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + RUST_TARGET: x86_64-unknown-linux-musl + +jobs: + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build toml cli + # uses: juankaram/rust-musl-action@master + uses: gmiam/rust-musl-action@master + with: + args: cargo build --target $RUST_TARGET --release + - name: store-artifacts + uses: actions/upload-artifact@v2 + with: + if-no-files-found: error + name: toml-artifacts-linux + path: | + target/${{ env.RUST_TARGET }}/release/toml + + prepare-tarball-linux: + runs-on: ubuntu-latest + needs: [build-linux] + steps: + - name: download artifacts + uses: actions/download-artifact@v2 + with: + name: toml-artifacts-linux + path: toml-cli + - name: prepare release tarball + run: | + tag=$(echo $GITHUB_REF | cut -d/ -f3-) + tarball="toml-cli-$tag-linux-amd64.tgz" + chmod +x toml-cli/* + tar cf - toml-cli | gzip > ${tarball} + echo "tarball=${tarball}" >> $GITHUB_ENV + + shasum="$tarball.sha256sum" + sha256sum $tarball > $shasum + echo "tarball_shasum=${shasum}" >> $GITHUB_ENV + - name: store-artifacts + uses: actions/upload-artifact@v2 + with: + name: release-tarball + path: | + ${{ env.tarball }} + ${{ env.tarball_shasum }} + + create-release: + runs-on: ubuntu-latest + needs: [prepare-tarball-linux] + steps: + - name: download artifacts + uses: actions/download-artifact@v2 + with: + name: release-tarball + path: tarballs + - name: prepare release env + run: | + echo "tarballs<<EOF" >> $GITHUB_ENV + for I in $(ls tarballs);do echo "tarballs/${I}" >> $GITHUB_ENV; done + echo "EOF" >> $GITHUB_ENV + tag=$(echo $GITHUB_REF | cut -d/ -f3-) + echo "tag=${tag}" >> $GITHUB_ENV + cat $GITHUB_ENV + - name: push release + if: github.event_name == 'push' + uses: softprops/action-gh-release@v1 + with: + name: "Toml cli ${{ env.tag }}" + body: | + "Toml cli release ${{ env.tag }}" + generate_release_notes: true + files: | + ${{ env.tarballs }} From d695203c07435ca688bd49b5eac7e299caeca69b Mon Sep 17 00:00:00 2001 From: bin liu <liubin0329@gmail.com> Date: Thu, 24 Nov 2022 11:39:50 +0800 Subject: [PATCH 3/7] add Dockerfile to release container image Signed-off-by: bin liu <liubin0329@gmail.com> --- .github/workflows/release.yml | 33 +++++++++++++++++++++++++++++++++ .gitignore | 1 + misc/Dockerfile | 4 ++++ 3 files changed, 38 insertions(+) create mode 100644 misc/Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc4109e..46ec6c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,8 @@ on: env: CARGO_TERM_COLOR: always RUST_TARGET: x86_64-unknown-linux-musl + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} jobs: build-linux: @@ -83,3 +85,34 @@ jobs: generate_release_notes: true files: | ${{ env.tarballs }} + + publish-image: + runs-on: ubuntu-latest + needs: [build-linux] + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Log in to the container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: download artifacts + uses: actions/download-artifact@v2 + with: + name: toml-artifacts-linux + path: misc + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + - name: build and push toml cli image + uses: docker/build-push-action@v3 + with: + context: misc + file: misc/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 53eaa21..346d12b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target **/*.rs.bk +toml diff --git a/misc/Dockerfile b/misc/Dockerfile new file mode 100644 index 0000000..11df8c4 --- /dev/null +++ b/misc/Dockerfile @@ -0,0 +1,4 @@ +FROM alpine:3.17 + +ADD toml /bin/toml +RUN chmod +x /bin/toml From 783e1a22bcbd3c776fd7e94ab0b21b20767688cb Mon Sep 17 00:00:00 2001 From: bin liu <liubin0329@gmail.com> Date: Thu, 24 Nov 2022 14:33:25 +0800 Subject: [PATCH 4/7] support set different data type now support set bool/i64. Signed-off-by: bin liu <liubin0329@gmail.com> --- src/main.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index c9d5185..5cc29e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -220,13 +220,23 @@ fn set(path: PathBuf, query: &str, value_str: &str) -> Result<(), Error> { } } } - *item = value(value_str); + *item = detect_value(value_str); // TODO actually write back print!("{}", doc); Ok(()) } +fn detect_value(value_str: &str) -> Item { + if let Ok(i) = value_str.parse::<i64>() { + value(i) + } else if let Ok(b) = value_str.parse::<bool>() { + value(b) + } else { + value(value_str) + } +} + fn parse_query_cli(query: &str) -> Result<Query, CliError> { parse_query(query).map_err(|_err| { CliError::BadQuery() // TODO: specific message From a99be312ac7c7293c11f119f50b8bd0d79e0d9fa Mon Sep 17 00:00:00 2001 From: bin liu <liubin0329@gmail.com> Date: Thu, 24 Nov 2022 14:35:30 +0800 Subject: [PATCH 5/7] fix type: flase -> false Signed-off-by: bin liu <liubin0329@gmail.com> --- README.md | 2 +- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a982f29..297fe6b 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ otherwise it will print `false` to stderr and set exit code to `1`. ```sh $ toml check test.toml plugins.name2 -flase +false $ echo $? 1 $ toml check test.toml plugins.name diff --git a/src/main.rs b/src/main.rs index 5cc29e2..dff67f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -116,7 +116,7 @@ fn check(path: PathBuf, query: &str) { std::process::exit(0); } } - eprintln!("flase"); + eprintln!("false"); std::process::exit(1); } From 63aa4c4f35191134ef79cb5c2598fa73069d7d28 Mon Sep 17 00:00:00 2001 From: bin liu <liubin0329@gmail.com> Date: Thu, 24 Nov 2022 16:00:35 +0800 Subject: [PATCH 6/7] support edit file in place add two opitons for set sub command: - overwrite: save the result to the TOML file. - backup: create a backup file before overwrite. Both of them are false by default. Signed-off-by: bin liu <liubin0329@gmail.com> --- Cargo.lock | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + README.md | 10 +- src/main.rs | 39 ++++++-- 4 files changed, 302 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78f01d3..77638a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -70,6 +79,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + [[package]] name = "bytes" version = "1.3.0" @@ -88,6 +103,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + [[package]] name = "clap" version = "2.34.0" @@ -103,6 +133,16 @@ dependencies = [ "vec_map", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "combine" version = "4.6.6" @@ -113,6 +153,56 @@ dependencies = [ "memchr", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cxx" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.8.0" @@ -171,6 +261,30 @@ dependencies = [ "libc", ] +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "indexmap" version = "1.9.2" @@ -196,6 +310,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -221,6 +344,24 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.5.0" @@ -247,6 +388,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.29.0" @@ -256,6 +416,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -310,6 +476,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "serde" version = "1.0.148" @@ -386,6 +558,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -395,10 +576,22 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi", + "winapi", +] + [[package]] name = "toml-cli" version = "0.2.0" dependencies = [ + "chrono", "failure", "nom", "serde", @@ -461,6 +654,66 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + [[package]] name = "winapi" version = "0.3.9" @@ -477,6 +730,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index cbd9d32..5ca8ecc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ serde = "1.0" serde_json = "1.0" structopt = "0.3" toml_edit = "0.15" +chrono = "0.4" diff --git a/README.md b/README.md index 297fe6b..e07bc18 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ SUBCOMMANDS: check Check if a key exists get Print some data from the file help Prints this message or the help of the given subcommand(s) - set Edit the file to set some data (currently, just print modified version) + set Edit the file to set some data ``` ### `toml check` @@ -164,14 +164,16 @@ ARGS: ``` $ toml set --help toml-set 0.2.0 -Edit the file to set some data (currently, just print modified version) +Edit the file to set some data USAGE: toml set <path> <query> <value-str> FLAGS: - -h, --help Prints help information - -V, --version Prints version information + --backup Create a backup file when `overwrite` is set(default: doesn't create a backup file) + -h, --help Prints help information + --overwrite Overwrite the TOML file (default: print to stdout) + -V, --version Prints version information ARGS: <path> Path to the TOML file to read diff --git a/src/main.rs b/src/main.rs index dff67f0..4b37af6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ mod query_parser; +use chrono::{DateTime, Utc}; use std::fs; +use std::fs::OpenOptions; +use std::io::Write; use std::path::PathBuf; use std::str; @@ -34,7 +37,7 @@ enum Args { #[structopt(flatten)] opts: GetOpts, }, - /// Edit the file to set some data (currently, just print modified version) + /// Edit the file to set some data Set { /// Path to the TOML file to read #[structopt(parse(from_os_str))] @@ -43,6 +46,8 @@ enum Args { query: String, /// String value to place at the given spot (bool, array, etc. are TODO) value_str: String, // TODO more forms + #[structopt(flatten)] + opts: SetOpts, }, // TODO: append/add (name TBD) } @@ -54,6 +59,16 @@ struct GetOpts { output_toml: bool, } +#[derive(StructOpt)] +struct SetOpts { + /// Overwrite the TOML file (default: print to stdout) + #[structopt(long)] + overwrite: bool, + /// Create a backup file when `overwrite` is set(default: doesn't create a backup file) + #[structopt(long)] + backup: bool, +} + #[derive(Debug, Fail)] enum CliError { #[fail(display = "bad query")] @@ -73,7 +88,8 @@ fn main() -> Result<(), Error> { path, query, value_str, - } => set(path, &query, &value_str)?, + opts, + } => set(path, &query, &value_str, opts)?, } Ok(()) } @@ -177,9 +193,9 @@ fn print_toml_fragment(doc: &Document, tpath: &[TpathSegment]) { print!("{}", doc); } -fn set(path: PathBuf, query: &str, value_str: &str) -> Result<(), Error> { +fn set(path: PathBuf, query: &str, value_str: &str, opts: SetOpts) -> Result<(), Error> { let tpath = parse_query_cli(query)?.0; - let mut doc = read_parse(path)?; + let mut doc = read_parse(path.clone())?; let mut item = doc.as_item_mut(); let mut already_inline = false; @@ -222,8 +238,19 @@ fn set(path: PathBuf, query: &str, value_str: &str) -> Result<(), Error> { } *item = detect_value(value_str); - // TODO actually write back - print!("{}", doc); + if opts.overwrite { + // write content to path + if opts.backup { + let now: DateTime<Utc> = Utc::now(); + let ext = now.format("%Y%m%d-%H%M%S-%f"); + let backup_file = format!("{}.{}", path.display(), ext); + fs::copy(path.clone(), backup_file)?; + } + let mut output = OpenOptions::new().write(true).truncate(true).open(path)?; + write!(output, "{}", doc)?; + } else { + print!("{}", doc); + } Ok(()) } From be95a30f676f7300a849238bf7b6160fcfe17f7a Mon Sep 17 00:00:00 2001 From: bin liu <liubin0329@gmail.com> Date: Fri, 2 Dec 2022 20:00:04 +0800 Subject: [PATCH 7/7] add some unit tests and a workflow to run tests in pull requests. Signed-off-by: bin liu <liubin0329@gmail.com> --- .github/workflows/ci.yml | 22 +++++ Cargo.lock | 51 +++++++++++ Cargo.toml | 3 + Makefile | 24 +++++ src/main.rs | 186 ++++++++++++++++++++++++++++++++++++--- test/test.rs | 2 +- 6 files changed, 274 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Makefile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9dc281b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,22 @@ +name: CI +on: + pull_request: + types: + - opened + - edited + - reopened + - synchronize + +env: + CARGO_TERM_COLOR: always + RUST_TARGET: x86_64-unknown-linux-musl + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build and test + uses: gmiam/rust-musl-action@master + with: + args: make build && make ut diff --git a/Cargo.lock b/Cargo.lock index 77638a6..fa77d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,6 +231,15 @@ dependencies = [ "synstructure", ] +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "gimli" version = "0.26.2" @@ -295,6 +304,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "itertools" version = "0.10.5" @@ -464,6 +482,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -558,6 +594,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -597,6 +647,7 @@ dependencies = [ "serde", "serde_json", "structopt", + "tempfile", "toml_edit", ] diff --git a/Cargo.toml b/Cargo.toml index 5ca8ecc..2f232cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,6 @@ serde_json = "1.0" structopt = "0.3" toml_edit = "0.15" chrono = "0.4" + +[dev-dependencies] +tempfile = "3.3.0" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3aaba14 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +default: build + +CARGO ?= $(shell which cargo) +RUST_TARGET ?= x86_64-unknown-linux-musl + +.format: + ${CARGO} fmt -- --check + +build: .format + ${CARGO} build --target ${RUST_TARGET} --release + # Cargo will skip checking if it is already checked + ${CARGO} clippy --bins --tests -- -Dwarnings + +clean: + ${CARGO} clean + +ut: + RUST_BACKTRACE=1 ${CARGO} test --workspace -- --skip integration --nocapture + +integration: + # run tests under `test` directory + RUST_BACKTRACE=1 ${CARGO} test --workspace -- integration --nocapture + +test: ut integration diff --git a/src/main.rs b/src/main.rs index 4b37af6..94149a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,14 +52,14 @@ enum Args { // TODO: append/add (name TBD) } -#[derive(StructOpt)] +#[derive(Clone, Copy, Default, StructOpt)] struct GetOpts { /// Print as a TOML fragment (default: print as JSON) #[structopt(long)] output_toml: bool, } -#[derive(StructOpt)] +#[derive(Clone, Copy, Default, StructOpt)] struct SetOpts { /// Overwrite the TOML file (default: print to stdout) #[structopt(long)] @@ -137,20 +137,31 @@ fn check(path: PathBuf, query: &str) { } fn get(path: PathBuf, query: &str, opts: GetOpts) -> Result<(), Error> { + let value = get_value(path, query, opts)?; + if opts.output_toml { + print!("{}", value); + } else { + println!("{}", value); + } + Ok(()) +} + +fn get_value(path: PathBuf, query: &str, opts: GetOpts) -> Result<String, Error> { let tpath = parse_query_cli(query)?.0; let doc = read_parse(path)?; - if opts.output_toml { - print_toml_fragment(&doc, &tpath); + let value = if opts.output_toml { + format_toml_fragment(&doc, &tpath) } else { let item = walk_tpath(doc.as_item(), &tpath); // TODO: support shell-friendly output like `jq -r` - println!("{}", serde_json::to_string(&JsonItem(item))?); - } - Ok(()) + serde_json::to_string(&JsonItem(item))? + }; + + Ok(value) } -fn print_toml_fragment(doc: &Document, tpath: &[TpathSegment]) { +fn format_toml_fragment(doc: &Document, tpath: &[TpathSegment]) -> String { let mut item = doc.as_item(); let mut breadcrumbs = vec![]; for seg in tpath { @@ -190,10 +201,23 @@ fn print_toml_fragment(doc: &Document, tpath: &[TpathSegment]) { } } let doc = Document::from(item.into_table().unwrap()); - print!("{}", doc); + format!("{}", doc) } fn set(path: PathBuf, query: &str, value_str: &str, opts: SetOpts) -> Result<(), Error> { + let result = set_value(path, query, value_str, opts)?; + if let Some(doc) = result { + print!("{}", doc); + } + Ok(()) +} + +fn set_value( + path: PathBuf, + query: &str, + value_str: &str, + opts: SetOpts, +) -> Result<Option<String>, Error> { let tpath = parse_query_cli(query)?.0; let mut doc = read_parse(path.clone())?; @@ -238,7 +262,7 @@ fn set(path: PathBuf, query: &str, value_str: &str, opts: SetOpts) -> Result<(), } *item = detect_value(value_str); - if opts.overwrite { + let result = if opts.overwrite { // write content to path if opts.backup { let now: DateTime<Utc> = Utc::now(); @@ -248,10 +272,12 @@ fn set(path: PathBuf, query: &str, value_str: &str, opts: SetOpts) -> Result<(), } let mut output = OpenOptions::new().write(true).truncate(true).open(path)?; write!(output, "{}", doc)?; + None } else { - print!("{}", doc); - } - Ok(()) + Some(format!("{}", doc)) + }; + + Ok(result) } fn detect_value(value_str: &str) -> Item { @@ -353,3 +379,137 @@ impl Serialize for JsonValue<'_> { } } } + +#[cfg(test)] +mod tests { + use std::fs; + + // functions to test + use super::check_exists; + use super::detect_value; + use super::{get_value, GetOpts}; + use super::{set_value, SetOpts}; + + #[test] + fn test_detect_value() { + let i = detect_value("abc"); + assert_eq!("string", i.type_name()); + assert!(i.is_str()); + assert_eq!(Some("abc"), i.as_str()); + + let i = detect_value("123"); + assert_eq!("integer", i.type_name()); + assert!(i.is_integer()); + assert_eq!(Some(123), i.as_integer()); + + let i = detect_value("true"); + assert_eq!("boolean", i.type_name()); + assert!(i.is_bool()); + assert_eq!(Some(true), i.as_bool()); + } + + #[test] + fn test_check_exists() { + let body = r#"[a] +b = "c" +[x] +y = "z""#; + let dir = tempfile::tempdir().expect("failed to create tempdir"); + let toml_file = dir.path().join("test.toml"); + fs::write(&toml_file, body).expect("failed to create tempfile"); + + // x.y exists + let result = check_exists(toml_file.clone(), "x.y"); + assert!(result.is_ok()); + assert!(result.unwrap()); + + // x.z does not exists + let result = check_exists(toml_file, "x.z"); + assert!(result.is_ok()); + assert!(!result.unwrap()); + } + + #[test] + fn test_get_value() { + let body = r#"[a] +b = "c" +[x] +y = "z""#; + let dir = tempfile::tempdir().expect("failed to create tempdir"); + let toml_file = dir.path().join("test.toml"); + fs::write(&toml_file, body).expect("failed to write tempfile"); + + let opts = GetOpts::default(); + // x.y exists + let result = get_value(toml_file.clone(), "x.y", opts); + assert!(result.is_ok()); + assert_eq!("\"z\"", result.unwrap()); + + // x.z does not exists + // FIXME: get_value now will panic, it's not a well-desined API. + let result = std::panic::catch_unwind(|| { + let _ = get_value(toml_file.clone(), "x.z", opts); + }); + assert!(result.is_err()); + } + + #[test] + fn test_set_value() { + // fn set(path: PathBuf, query: &str, value_str: &str, opts: SetOpts) -> Result<(), Error> { + let body = r#"[a] +b = "c" +[x] +y = "z""#; + let dir = tempfile::tempdir().expect("failed to create tempdir"); + let toml_file = dir.path().join("test.toml"); + fs::write(&toml_file, body).expect("failed to write tempfile"); + + let mut opts = SetOpts::default(); + // x.y exists + let result = set_value(toml_file.clone(), "x.y", "new", opts); + assert!(result.is_ok()); + let excepted = r#"[a] +b = "c" +[x] +y = "new" +"#; + assert_eq!(excepted, result.unwrap().unwrap()); + + let result = set_value(toml_file.clone(), "x.z", "123", opts); + assert!(result.is_ok()); + let excepted = r#"[a] +b = "c" +[x] +y = "z" +z = 123 +"#; + assert_eq!(excepted, result.unwrap().unwrap()); + + let result = set_value(toml_file.clone(), "x.z", "false", opts); + assert!(result.is_ok()); + let excepted = r#"[a] +b = "c" +[x] +y = "z" +z = false +"#; + assert_eq!(excepted, result.unwrap().unwrap()); + + // test overwrite the original file + opts.overwrite = true; + let result = set_value(toml_file.clone(), "x.z", "false", opts); + assert!(result.is_ok()); + println!("{:?}", result); + // --overwrite will not generate any output. + assert_eq!(None, result.unwrap()); + + let excepted = r#"[a] +b = "c" +[x] +y = "z" +z = false +"#; + let new_body = fs::read_to_string(toml_file).expect("failed to read TOML file"); + assert_eq!(excepted, new_body); + } +} diff --git a/test/test.rs b/test/test.rs index f17eb54..d11b7b6 100644 --- a/test/test.rs +++ b/test/test.rs @@ -5,7 +5,7 @@ use std::process; use std::str; #[test] -fn help_if_no_args() { +fn integration_test_help_if_no_args() { // Probably want to factor out much of this when adding more tests. let proc = process::Command::new(get_exec_path()).output().unwrap(); assert!(!proc.status.success());