Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update crates and add some sub command/options #13

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

liubin
Copy link
Contributor

@liubin liubin commented Nov 25, 2022

Hi, @gnprice thank you for this tool.

I want to use this tool in a shell script to edit TOML files, to run this tool, I added some commits:

  • update old crates and compile using the latest Rust
  • add a check subcommand to check if a key exists(getting a non-exists key will panic)
  • add --overwrite option to set to write the result to the original file.
  • support set i64/bool values
  • add a GitHub action to create release tarball and container image when pushing a new tag

@gnprice
Copy link
Owner

gnprice commented Dec 2, 2022

Lovely, thank you @liubin! This is great to see. These look like a number of quite useful features.

It may take me a bit of time to read through these changes, but I'll aim to do so in the next few days.

Do you think you might be up for adding a few test cases for the new functionality, too? I recognize that most of the existing functionality isn't tested either, and I'm not going to block merging this on holding out for tests. But if you do have a bit of time to try adding some tests, that would be a much-appreciated contribution as well.

@liubin
Copy link
Contributor Author

liubin commented Dec 2, 2022

@gnprice I added the 9th commit, this one adds:

  • some basic unit tests
  • a Makefile with some basic targets to help development
  • a GitHub workflow to run tests in new pull requests to ensure the code is working. Here is an example of it.

For integration in the test/ directory, it may take some time to think about how to do it.

@gnprice
Copy link
Owner

gnprice commented Dec 2, 2022

Excellent, thanks! These look like quite helpful tests.

For integration in the test/ directory, it may take some time to think about how to do it.

Cool. I think for a lot of the tests one wants to write for this, the ideal form for the test is actually an integration test that operates at the CLI level, so that would be especially helpful.

For example, once we have a good pattern for writing CLI integration tests, I'd want to take the unit test that says

        let result = set_value(toml_file.clone(), "x.z", "false", opts);

and replace it with a CLI integration test that says something like

    let result = run_toml(vec!["set", filename, "x.z", "false"]);

so that it corresponds closely to writing the command line toml set "$filename" x.z false. That way (a) the test directly tests that the interface presented to users works as specified, and (b) the test doesn't depend on internal interfaces like set_value, and so leaves us free to refactor the internal interfaces all we like without having to update tests.

But I won't block on that for merging -- I'll be happy to merge a version with unit tests like these, and leave converting them to CLI-level tests as a further improvement to make in the future.

Copy link
Owner

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! I've now read through all of this except the build/CI/release changes, and the new tests. Very happy to have these changes.

For the housekeeping changes at the start of the branch — rustfmt, clippy, upgrading dependencies and adapting to their API changes — my preference is to keep things more separated out step-by-step in the Git history, and there are a couple of spots where I'd prefer to tell clippy to be quiet rather than take its suggestion. So I'll just go ahead and push a series of commits that takes care of those in my preferred way, and this can rebase atop that.

Comments below on the interesting substantive parts, mainly about choices on what the CLI interface should look like. Definitely interested in your feedback on the interface, based on your experience writing a script using this command.

I'm also interested in the build/CI/release changes and the tests, and will look at those later. Posting this part as a review of its own just to keep things organized.

Comment on lines -28 to +40
/// Edit the file to set some data (currently, just print modified version)
/// Edit the file to set some data
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! I think this is an especially important feature to add.

Comment on lines +271 to +270
let now: DateTime<Utc> = Utc::now();
let ext = now.format("%Y%m%d-%H%M%S-%f");
let backup_file = format!("{}.{}", path.display(), ext);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this format for the backup-file name feels pretty ad-hoc to me. Here's what it gets on an example run:
foo.toml.20221203-045740-563448063
It's expressing a date and time, but it doesn't align with any standard way of writing a date and time.

How about writing it in ISO 8601 format? So the example above would instead be:
foo.toml.2022-12-03T04:57:40,563448063

Comment on lines +276 to +274
let mut output = OpenOptions::new().write(true).truncate(true).open(path)?;
write!(output, "{}", doc)?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could this be made simpler by using fs::write?

Or is there perhaps a subtle behavior difference that I'm missing?

Comment on lines +114 to +117
Check if a key exists

USAGE:
toml check <path> <query>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, can you say more about the motivation for this subcommand?

I think the way I'd expect to handle this use case in a CLI is by using the "get" command, and just redirecting the output to /dev/null if I don't actually want it.

Looking at git config — which I find to be a good source of inspiration for this command's interface, as mentioned in the README — it looks like that's exactly what it does. There's no specialized "check" action in git config --help, but instead there's this:

   --get
       Get the value for a given key (optionally filtered by a regex matching the
       value). Returns error code 1 if the key was not found and the last value if
       multiple key values were found.

I think that'd work well for the toml command, too.


Looks like our current behavior in toml get if there's no data at the given path is to panic, eep:

$ target/debug/toml get Cargo.toml a.b ; echo $?
thread 'main' panicked at 'attempted to leave type `linked_hash_map::Node<alloc::string::String, table::TableKeyValue>` uninitialized, which is invalid', /home/greg/.cargo/registry/src/github.com-1ecc6299db9ec823/linked-hash-map-0.5.2/src/lib.rs:174:52
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
101

Or after upgrading dependencies, the panic is a bit less noisy but still a panic:

$ target/debug/toml get Cargo.toml a.b ; echo $?
thread 'main' panicked at 'index not found', src/main.rs:188:32
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
101

It's panicking in the walk_tpath function.

So I think basically what we want here is to take your check_exists function, which is a non-panicky version of walk_tpath, and put that logic in walk_tpath so that toml get can give a clean error result (just like git config does) instead of panicking.

Comment on lines +64 to +66
/// Overwrite the TOML file (default: print to stdout)
#[structopt(long)]
overwrite: bool,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about going further: instead of just making this a option, how about we make it the default behavior of toml set?

I feel like for most use cases, you're always going to want to write the edited file back. So it'd be cleaner if you could say simply toml set foo.toml …, rather than saying toml set --overwrite (or whatever we might call this option) all the time.

That would also make it align better with the name "set" than it currently does.

We can always include an option to go back to the other behavior, with a name like --dry-run or --print.

(Yes, this is an incompatible change. But as the README says:

The command's status is experimental. The current interface does not yet serve its purposes as well as it could, and incompatible changes are anticipated.

Making toml set update the actual file on disk is exactly the sort of change I had in mind with that warning.)

Comment on lines +286 to +289
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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this feels rather too magical to me for the interface of this command. In particular, this means that even when you're just trying to write string data, you'll sometimes have it come out as an int or a bool instead, just depending on the data -- depending on exactly what string it was you were trying to store.

Or for another angle on the same thing: this approach would mean that it'd be impossible to store some strings, like "1" or "false".

For example, it'd be impossible to correctly set package.edition in a Cargo.toml file:

$ target/debug/toml set --overwrite Cargo.toml package.edition 2018

$ git diff -U0
diff --git a/Cargo.toml b/Cargo.toml
index 2f232cf98..ac60e2325 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10 +10 @@ license = "MIT"
-edition = "2018"
+edition = 2018

$ cargo check
error: failed to parse manifest at `/home/greg/w/toml-cli/Cargo.toml`

Caused by:
  data did not match any variant of untagged enum MaybeWorkspace for key `package.edition`

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, what would you think about having toml set take a flag to tell it how it should parse the value? Probably "string" would still be the default -- I feel like that's probably the most common.

So e.g.

toml set foo.toml x asdf   # a string
toml set foo.toml x --string asdf  # the same thing, explicitly

toml set foo.toml x 2018  # still a string: `x = "2018"`
toml set foo.toml x --int 2018  # an int: `x = 2018`
toml set foo.toml x --int asdf  # error

toml set foo.toml x --bool false
toml set foo.toml x --bool asdf  # error

@liubin
Copy link
Contributor Author

liubin commented Dec 3, 2022

Hi @gnprice thanks for your review, now there are many conflicts in this PR, and this PR contains many commits, it's difficult to fix individual ones, so I'd like to freeze this PR and split it into some separated PRs.

Here I created some issues to track your comments:

@gnprice
Copy link
Owner

gnprice commented Dec 3, 2022

Sure, splitting this PR up sounds great.

I did just push back to your PR branch a rebased version, though, with conflicts resolved. So take a look at that and see if it's helpful in splitting things out.

It will check if the key exists.

Signed-off-by: bin liu <liubin0329@gmail.com>
Signed-off-by: bin liu <liubin0329@gmail.com>
Signed-off-by: bin liu <liubin0329@gmail.com>
now support set bool/i64.

Signed-off-by: bin liu <liubin0329@gmail.com>
Signed-off-by: bin liu <liubin0329@gmail.com>
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>
Signed-off-by: bin liu <liubin0329@gmail.com>
@gnprice
Copy link
Owner

gnprice commented Dec 3, 2022

OK, I've pushed the following commits to the main branch:
2fa3eec deps: Update Cargo.lock format to version 3
c36301c deps: Update lexical-core, fixing the build on newer Rust
8f93d2f deps: toml_edit 0.3
9c4fe0b deps: toml_edit 0.12.6
0acf4e3 deps: toml_edit 0.15, the latest
9219320 deps: cargo update
c4b5564 lint: Silence a clippy::single_match warning
05abfbd lint: Silence another cosmetic clippy complaint
a3c784c lint: Fix remaining clippy warnings
d05e755 fmt: Add some rustfmt::skip markings, and autoformat all

Now cargo fmt and cargo clippy are both clean.

I've also pushed a rebased version of this PR back to the PR branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants