-
Notifications
You must be signed in to change notification settings - Fork 24
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
base: main
Are you sure you want to change the base?
Conversation
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. |
@gnprice I added the 9th commit, this one adds:
For integration in the |
Excellent, thanks! These look like quite helpful tests.
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 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. |
There was a problem hiding this 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.
/// Edit the file to set some data (currently, just print modified version) | ||
/// Edit the file to set some data |
There was a problem hiding this comment.
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.
let now: DateTime<Utc> = Utc::now(); | ||
let ext = now.format("%Y%m%d-%H%M%S-%f"); | ||
let backup_file = format!("{}.{}", path.display(), ext); |
There was a problem hiding this comment.
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
let mut output = OpenOptions::new().write(true).truncate(true).open(path)?; | ||
write!(output, "{}", doc)?; |
There was a problem hiding this comment.
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?
Check if a key exists | ||
|
||
USAGE: | ||
toml check <path> <query> |
There was a problem hiding this comment.
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.
/// Overwrite the TOML file (default: print to stdout) | ||
#[structopt(long)] | ||
overwrite: bool, |
There was a problem hiding this comment.
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.)
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) |
There was a problem hiding this comment.
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`
There was a problem hiding this comment.
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
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: |
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>
OK, I've pushed the following commits to the main branch: Now I've also pushed a rebased version of this PR back to the PR branch. |
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:
check
subcommand to check if a key exists(getting a non-exists key will panic)--overwrite
option toset
to write the result to the original file.