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

add visit and visit_mut APIs #261

Merged
merged 4 commits into from
Nov 25, 2021
Merged

add visit and visit_mut APIs #261

merged 4 commits into from
Nov 25, 2021

Conversation

sunshowers
Copy link
Contributor

@sunshowers sunshowers commented Nov 21, 2021

This is modeled after the syn::visit and syn::visit_mut APIs. See discussion in #192 (comment) for what prompted me to write this.

Also include some nontrivial examples for custom formatting.

One thing that immediately occurs to me is that we could easily provide a recursive formatter based on this (which would strengthen the argument for it being available unconditionally).

@ordian
Copy link
Member

ordian commented Nov 21, 2021

Should this be enabled by default and/or feature flagged? I currently have it flagged but I think there's a good argument that it should just be made available unconditionally.

I agree. We only gate something only if there is some overhead, like it adds a dependency.

@sunshowers sunshowers changed the title [RFC] add a visit_mut API add visit and visit_mut APIs Nov 22, 2021
@sunshowers
Copy link
Contributor Author

Added a visit API, and removed the feature flagging. Looks pretty good now, let me know what you think!

An easy exercise would be to write a recursive formatter. I can add that in another commit on top of the stack as well.

@sunshowers
Copy link
Contributor Author

Ahh this needs a --all-targets passed into cargo test in CI

examples/visit.rs Outdated Show resolved Hide resolved
examples/visit.rs Outdated Show resolved Hide resolved
@sunshowers
Copy link
Contributor Author

I've changed it so that the table and inline_table visit methods both call table_like, and there's now a single table_like_kv method which should make things simpler. I also made the example slightly more complex -- it can now normalize inline tables into tables within a scope as well.

Along the way I found a formatting bug for tables, and I ended up fixing that as well.

Seems like we may want to provide a bunch of standard inbox visitors in a follow-up (bundled with toml_edit or perhaps a library on the side):

  • turn inline tables underneath to standard tables
  • turn standard tables underneath to inline tables
  • recursive formatting

Visitors can also be used in a more delegated style, where visitor1 can have internal logic that switches to visitor2 under some sections of the tree.

src/visit.rs Outdated Show resolved Hide resolved
src/table.rs Outdated
Comment on lines 375 to 384
Item::Table(table) => {
kv.key.decor = Decor::new(DEFAULT_KEY_PATH_DECOR.0, DEFAULT_KEY_PATH_DECOR.1);
table.decor = Decor::new(DEFAULT_TABLE_DECOR.0, DEFAULT_TABLE_DECOR.1);
}
Item::ArrayOfTables(array) => {
kv.key.decor = Decor::new(DEFAULT_KEY_PATH_DECOR.0, DEFAULT_KEY_PATH_DECOR.1);
for table in array.iter_mut() {
table.decor = Decor::new(DEFAULT_TABLE_DECOR.0, DEFAULT_TABLE_DECOR.1);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Along the way I found a formatting bug for tables, and I ended up fixing that as well.

Table::fmt() wasn't properly applying the right decor to tables and
arrays of tables.

What is being considered a bug in the old version? Not reformatting the table headers? I think this is a question of whether you are considering it from the test document perspective or the in-memory data structure perspective. I was personally viewing this from the document perspective, that we are only formatting the visual table body. I feel like to consider this from the data structure perspective, we would need to recurse.

Copy link
Contributor Author

@sunshowers sunshowers Nov 23, 2021

Choose a reason for hiding this comment

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

Yeah, at first it was about reformatting headers (I ran into an issue where converting an inline table into a standard table would cause the header to have a trailing space). Then I realized that we weren't formatting leading and trailing whitespace on sub tables or arrays of tables either.

The invariant I was going for was that calling fmt() on every element of the document would format the whole document. Currently that invariant is mostly upheld except for the cases fixed in this PR.

Copy link
Member

Choose a reason for hiding this comment

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

btw I realized there is a bug with Table::fmt for the description I gave above, #262

Yeah, at first it was about reformatting headers (I ran into an issue where converting an inline table into a standard table would cause the header to have a trailing space). Then I realized that we weren't formatting leading and trailing whitespace on sub tables or arrays of tables either.

When converting table types, we should probably reset the formatting to defaults since 99% of the time, the formatting will be counter to the users goal.

And/or we provide a "reset to default" formatting / clear formatting function.

The invariant I was going for was that calling fmt() on every element of the document would format the whole document. Currently that invariant is mostly upheld except for the cases fixed in this PR.

Thanks for sharing the intent! That is a worthwhile goal. I wonder if we should have a separate "format table header".

Either way, maybe we should split this out into a separate PR so we can discuss and iterate on it without being tied up with the rest of the work here?

@sunshowers
Copy link
Contributor Author

When converting table types, we should probably reset the formatting to defaults since 99% of the time, the formatting will be counter to the users goal.

I think that makes sense. What do you think about recursive formatting? In particular I'm thinking of the situation where you're converting a standard table into an inline one, and there's multiline arrays within it (this is addressed in the visit_mut example). The TOML spec says:

Inline tables are intended to appear on a single line. A terminating comma (also called trailing comma) is not permitted after the last key/value pair in an inline table. No newlines are allowed between the curly braces unless they are valid within a value. Even so, it is strongly discouraged to break an inline table onto multiples lines. If you find yourself gripped with this desire, it means you should be using standard tables.


And/or we provide a "reset to default" formatting / clear formatting function.

How would this differ from the fmt() function that currently exists?

Thanks for sharing the intent! That is a worthwhile goal. I wonder if we should have a separate "format table header".

So when I started implementing this I was thinking along those lines as well. However, the issue is that the header is not part of the Table data structure, so the closest we can get to it is "format all table and array-of-tables headers that are children of this table".

Either way, maybe we should split this out into a separate PR so we can discuss and iterate on it without being tied up with the rest of the work here?

Ahh unfortunately the issue is that the included example actually depends on the formatting to work properly, so there's a dependency between the two. GH isn't great at expressing dependencies between pull requests (this is something I've personally talked to GH reps about -- also there are others trying to solve this problem). That's why I had to club it together under the same PR. However, I have tried to produce independent, separately reviewable commits.

@sunshowers
Copy link
Contributor Author

Also, as far as recursive formatting goes, I think it would be easiest to build it on top of VisitMut rather than doing something bespoke.

@sunshowers
Copy link
Contributor Author

Oh, re fmt() invariants, here are the two I was going for:

  1. Calling fmt() on every element of a document should format the whole document.
  2. fmt() on an individual element does the minimum necessary work to uphold invariant 1.

@epage
Copy link
Member

epage commented Nov 23, 2021

When in the context of the stated goal, fmt makes sense. Outside of full document reformatting, it feels like unintended behavior to say "I format a table X and other table headers are being re-formatted". I think the only way for us to go down that route is if we make fmt recursive.

That said, I am unsure about making it recursive, about tying key-value pair formatting with header formatting. This feels very restrictive. For example, in cargo-edit, we check if key-value pairs are sorted and try to maintain that. That kind of inferring formatting I can see applying here. Someone is more likely to format all headers the same. In cargo-edit, we could check the header formatting and seek to preserve it. So like sorting not being a part of fmt, I see headers not being a part of fmt because they all operate on a different axis of user care abouts.

With that said, I see fmt, sort_keys, etc all being a subset of a future formatting API.

Either way, maybe we should split this out into a separate PR so we can discuss and iterate on it without being tied up with the rest of the work here?

Ahh unfortunately the issue is that the included example actually depends on the formatting to work properly, so there's a dependency between the two. GH isn't great at expressing dependencies between pull requests (this is something I've personally talked to GH reps about -- also there are others trying to solve this problem). That's why I had to club it together under the same PR. However, I have tried to produce independent, separately reviewable commits.

The dependency is because of the table conversion, correct? Let's focus on fixing that for unblocking this PR. If you want, I can start working on a PR for it.

@sunshowers
Copy link
Contributor Author

The dependency is because of the table conversion, correct? Let's focus on fixing that for unblocking this PR. If you want, I can start working on a PR for it.

Both the array formatting issue (commit 1 which should be uncontroversial I think) and the table conversion block this PR, yeah.

@sunshowers
Copy link
Contributor Author

(If you want to work on it for a bit, sounds great, yeah!)

@sunshowers
Copy link
Contributor Author

So like sorting not being a part of fmt, I see headers not being a part of fmt because they all operate on a different axis of user care abouts.

This makes sense. I can see everything being part of a "normalize" API.

@epage
Copy link
Member

epage commented Nov 23, 2021

In testing #265 against your branch, I realized that it won't help your case. You are already running fmt to workaround what that fixes. The problem is the key pointing to the converted table. Going to give this some thought.

@epage
Copy link
Member

epage commented Nov 23, 2021

I feel like the crux of the problem is that the kv visitor should be given a mutable key so you can clear the existing decor. We don't want to expose a &mut Key because then someone can do a *key = <something> and get a visualization that is independent of the actual key.

I think we should add a KeyMutRef and have the iter_mut return that. The KeyMutRef is a proxy object that would allow the mutations that are safe.

A formatted array is all in one line, so it shouldn't have trailing
commas or whitespace.
@sunshowers
Copy link
Contributor Author

sunshowers commented Nov 24, 2021

Thank you for adding KeyMut! I believe that's all that's necessary to make this work. I've removed the table fmt commit and updated VisitMut to take KeyMut.

This is modeled after the `syn::visit_mut` API.

Also include a nontrivial example for custom formatting.
Similar to the visit_mut API, this allows walking over the document
tree.

I think overall this is probably less useful than `visit_mut`, but is
still worth implementing for completeness.
The previous commits added some tests to the `visit` example -- ensure
that they're run in CI.
@sunshowers
Copy link
Contributor Author

Looks like all of the extra formatting work you did also obviated the need for a ton of fmt calls, so I've removed them from the example. Thank you :)

@epage epage merged commit d8b1f90 into toml-rs:master Nov 25, 2021
@sunshowers sunshowers deleted the visit branch November 25, 2021 18:13
@@ -45,7 +45,7 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
args: --all-features --all-targets
Copy link
Member

Choose a reason for hiding this comment

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

Unless I'm missing something, it looks like --all-targets causes doc tests to not run. Ran into this with another project. when comparing cargo test (fails) to cargo test --all-targets (passes).

:(

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Looks like we are covered with our default feature run, so probably good enough here.

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.

3 participants