diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..280f65a --- /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 all diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..46ec6c6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,118 @@ +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 + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +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<> $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 }} + + 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/Cargo.lock b/Cargo.lock index 78f01d3..179e03f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,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" @@ -181,6 +190,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" @@ -298,6 +316,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" @@ -386,6 +422,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 = "textwrap" version = "0.11.0" @@ -404,6 +454,7 @@ dependencies = [ "serde", "serde_json", "structopt", + "tempfile", "toml_edit", ] diff --git a/Cargo.toml b/Cargo.toml index cbd9d32..fb0b7c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,6 @@ serde = "1.0" serde_json = "1.0" structopt = "0.3" toml_edit = "0.15" + +[dev-dependencies] +tempfile = "3.3.0" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6fa6cc1 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +default: build + +CARGO ?= $(shell which cargo) +RUST_TARGET ?= x86_64-unknown-linux-musl +INSTALL_DIR_PREFIX ?= "/usr/local/bin" + +.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 + +install: .format build + @sudo mkdir -m 755 -p $(INSTALL_DIR_PREFIX) + @sudo install -m 755 target/${RUST_TARGET}/release/toml $(INSTALL_DIR_PREFIX)/toml + +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 + +all: build install test 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 diff --git a/test/test.rs b/test/test.rs index f17eb54..fa6f048 100644 --- a/test/test.rs +++ b/test/test.rs @@ -1,23 +1,82 @@ -use std::env; -use std::ffi::OsString; -use std::path::PathBuf; +use std::fs; use std::process; use std::str; +const TOML_CMD: &str = "toml"; + #[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()); - let stderr = str::from_utf8(proc.stderr.as_slice()).unwrap(); + let cmd = process::Command::new(TOML_CMD).output().unwrap(); + assert!(!cmd.status.success()); + let stderr = str::from_utf8(cmd.stderr.as_slice()).unwrap(); assert!(stderr.contains("-h, --help")); } -fn get_exec_path() -> PathBuf { - // TODO is there no cleaner way to get this from Cargo? - // Also should it really be "debug"? - let target_dir: PathBuf = env::var_os("CARGO_TARGET_DIR") - .unwrap_or_else(|| OsString::from("target")) - .into(); - target_dir.join("debug").join("toml") +#[test] +fn integration_test_cmd_get() { + let body = r#"[a] +b = "c" +[x] +y = "z""#; + let toml_dir = tempfile::tempdir().expect("failed to create tempdir"); + let toml_file = toml_dir.path().join("test.toml"); + fs::write(&toml_file, body).expect("failed to write tempfile"); + let toml_file = toml_file.as_os_str().to_str().unwrap(); + + let cmd = process::Command::new(TOML_CMD) + .args(["get", toml_file, "x.y"]) + .output() + .unwrap(); + assert!(cmd.status.success()); + let stdout = str::from_utf8(cmd.stdout.as_slice()).unwrap(); + assert_eq!("\"z\"\n", stdout); + + // x.z does not exists + let cmd = process::Command::new(TOML_CMD) + .args(["get", toml_file, "x.z"]) + .output() + .unwrap(); + assert!(!cmd.status.success()); +} + +#[test] +fn integration_test_cmd_set() { + // fn set(path: PathBuf, query: &str, value_str: &str, opts: SetOpts) -> Result<(), Error> { + let body = r#"[a] +b = "c" +[x] +y = "z""#; + let toml_dir = tempfile::tempdir().expect("failed to create tempdir"); + let toml_file = toml_dir.path().join("test.toml"); + fs::write(&toml_file, body).expect("failed to write tempfile"); + let toml_file = toml_file.as_os_str().to_str().unwrap(); + + // x.y exists + let cmd = process::Command::new(TOML_CMD) + .args(["set", toml_file, "x.y", "new"]) + .output() + .unwrap(); + assert!(cmd.status.success()); + let stdout = str::from_utf8(cmd.stdout.as_slice()).unwrap(); + let excepted = r#"[a] +b = "c" +[x] +y = "new" +"#; + assert_eq!(excepted, stdout); + + let cmd = process::Command::new(TOML_CMD) + .args(["set", toml_file, "x.z", "123"]) + .output() + .unwrap(); + assert!(cmd.status.success()); + let stdout = str::from_utf8(cmd.stdout.as_slice()).unwrap(); + let excepted = r#"[a] +b = "c" +[x] +y = "z" +z = "123" +"#; + assert_eq!(excepted, stdout); }