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

v.util: rewrite diff module, deprecate old functions #21403

Merged
merged 18 commits into from May 5, 2024

Conversation

ttytm
Copy link
Member

@ttytm ttytm commented May 2, 2024

The rewritten module should extend functionality, make usage easier/less verbose, fix issues and resolve friction points that may stand in the way of using it in whatever context.

Some of the changes:

An overview of the changes is given below. However, it should not be necessary for the review, it was attempted to keep the code changes compact and easy to understand. Feel free to skip the overview.

General reliability and predictability of return values:

  • Use of known diff tools that can actually return a string value.

    To make the function call itself as safe and reliable as possible, tools like code and opendiff that open non-conforming files in a gui and do return a result string is limited to the overwrite via the environment variable. Additionally, it is possible to disable and to customize overwrites via the env variable.

Error handling:

  • Use of Result return types
    • The "old" module handles errors via unrecoverable panics or returns them formatted as string.
      Both should be resolved through using !string return types.

Other changes:

  • Fix \ No newline at end of file in the return values of the string comparing function.
    Which is suboptimal for a function that by definition compares strings.

    // Current
    diff.color_compare_strings(diff.find_working_diff_command()!, 'unique_smthn', 'abc', 'cde')
    // New
    diff.compare_text('abc', 'cde')!

    Comparing the output (assuming the automatically available diff command is diff)

    # Current
    ❯ v run file.v
    # ...
    -abc
    \ No newline at end of file
    +cde
    \ No newline at end of file
    
    # New
    ❯ v run file.v
    # ...
    -abc
    +cde
  • Include delta as diff tool. Which is a feature rich tool for human readable diffs that can help to analyze outputs. It can also emulate diff-so-fancy.

  • Extend tests.

  • For now, vfmt.v was updated as the only module that the diff module internally - also to make it easier testing the changes during a review locally. Other that use the current diff module could be updated separately.

Better handled, more user oriented options:

  • Handle options in a non-conflicting way. The current module has some defaults options that are hard coded to be always added, which might conflict with other args or tools.
    • This is also a fix for the statement in v fmt --help, which shows VDIFF_OPTIONS="-W 80 -y" (using the -y flag for side-by-side comparison).

      Current Updated
      cur_diff_y Screenshot_20240504_130540

      As a reference to verify that it's not a regression 87270e9 is a commit before the recent fixes and preparations of the diff module.

    • Allows to use other tools

      Current Updated
      cur_delta Screenshot_20240504_130445

@ttytm ttytm marked this pull request as ready for review May 2, 2024 16:36
Comment on lines 95 to 97
// Add `\n` when comparing strings to prevent `\ No newline at end of file` in the output.
os.write_file(path1, text1 + '\n')!
os.write_file(path2, text2 + '\n')!
Copy link
Member

Choose a reason for hiding this comment

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

The default should not change the content of what is passed.

The No newline at end of file message is a valuable signal.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can you add more info why? This would help me agree.

I would agree immediately if we would compare files. But this is comparing strings and the No newline at end of file seems inappropriate when comparing strings.

Copy link
Member Author

@ttytm ttytm May 2, 2024

Choose a reason for hiding this comment

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

Or let me phrase it like this. I want to make aware that this is comparing strings.
So No newline at end of file would be in the output for most of comparisons made. If you are aware and this is the preferred way, I have no trouble updating this.

Copy link
Member

Choose a reason for hiding this comment

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

Consider the most frequent usage - comparing text, generated by vfmt, to input source, that was written by a human, using an unknown text editor.
The input may not have an ending line. The output will.

Consider also that such diffs, will be used for assert txt1 == txt2.
When those 2 differ, it is very valuable to know what the exact difference was, and that is easier to discern, when you know that the tool does not lie to you, by manipulating and hiding data.

Copy link
Member Author

@ttytm ttytm May 2, 2024

Choose a reason for hiding this comment

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

Hmm, I still think there can be a misunderstanding here - on both sides. So lets try to get things clear.

There still is compare_files. There the + '\n' isn't done and it's the function used by v fmt (it's the only one updated in this PR). It still prints No newline at end of file if it is the case for the formatted file.

In regards of asserting, how there will be a difference? the + '\n' is added to both asserted strings, and it is only added for "quasi"-formatting-reasons of the colored diff output, so that the result resembles the function signature better.

Copy link
Member Author

Choose a reason for hiding this comment

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

However, shouldn't the newline only be added if there isn't one there already? Otherwise, you could end up with an extra on one, adding a difference that wasn't in the original.

Not necessarily, as it is added to both texts it is not a difference that the tools will track and highlight as a difference. So is will not change what is highlighted in the output.

I temporarily had a condition for this before opening the PR but thought to skip the additional check when it doesn't create a diff. Now thinking again, re-adding it could remove one additional non-differing empty line after the diffs, depending on the used configuration.

To give examples of what is highlighted with things as they currently are:

println(diff.compare_text('abc\n\n', 'cde\n', tool: .colordiff)!)

Screenshot_20240503_180441

println(diff.compare_text('abc\n\n', 'cde\n', tool: .colordiff, args: '-y -W 80')!)

Screenshot_20240503_181739

println(diff.compare_text('abc\n\n\n', 'cde\n\n')!)

Screenshot_20240503_181339

Copy link
Contributor

@JalonSolov JalonSolov May 3, 2024

Choose a reason for hiding this comment

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

That extra "non-differing empty line" is what I was talking about. If it isn't there in the original text, it shouldn't show up in the output, either.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, and added it. It ensures a cleaner visualization when using custom options. The results of the comparison itself that shows the differences stays ofc the same.

This comment was marked as outdated.

Copy link
Member Author

Choose a reason for hiding this comment

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

Having had a long day yesterday and other things to close off, I just wanted to get done with things in the PR. But I see, that I wanted to go to fast here. This PR is a small "bigger" change. So it's okay to go as slow as required.

To get the best result. I'll re-add preventing of \ No newline at end of file, as I think it's the right thing to do for that function. Then we can continue discussing it. If there should be any concerns with that, it would help me to address them if you test the function locally. Maybe this resolves concerns in itself or you can find an example and we would have something concrete to discuss.

vlib/v/util/diff.v Outdated Show resolved Hide resolved
vlib/v/util/diff/diff.v Outdated Show resolved Hide resolved
@spytheman
Copy link
Member

How is VDIFF_TOOL and VDIFF_OPTIONS supported in the new code?

From what I can see, they are only read now in find_working_diff_command, which is deprecated in this PR after 2024-06-30.

What will happen, when it expires?

@spytheman
Copy link
Member

Why is find_working_diff_command deprecated at all, if there is not a suitable replacement, offering the same functionality?

@ttytm
Copy link
Member Author

ttytm commented May 4, 2024

Why is find_working_diff_command deprecated at all, if there is not a suitable replacement, offering the same functionality?

Because it is not required. Or do I miss something?
As far as I could see, there isn't a need to make it necessary to use a module function to find the diff command to then pass it to another module function. This can be handled internally. So there is only an internal command for this.

// master
diff.color_compare_strings(diff.find_working_diff_command()!, 'unique_smthn', 'abc', 'cde')

// updated
diff.compare_text('abc', 'cde')!

or

// master
diff_cmd := diff.find_working_diff_command()!
// ...
diff.color_compare_files(diff_cmd, file_path_1, file_path2)

// updated
diff.compare_files(file_path1, file_path_2)!

There is no need to manually search for a diff command to then pass it to one of that functions. If there should ever be the need to check for the automatically found, available diff tool, it is still available as public constant available_tool.

What will happen, when it expires?

It can removed then since it's not required.

@ttytm
Copy link
Member Author

ttytm commented May 4, 2024

How is VDIFF_TOOL and VDIFF_OPTIONS supported in the new code?

They are part of the old code and replaced by an alternative.

By default, it is VDIFF_CMD that can be used to pass a custom tool and options.

To give developers full control, and make the module attractive to use anywhere, the env variable itself is also configurable as part of CompareOptions.
The used variable can be customized or set to none to disable overwrites, in case developers don't want to allow to overwrite values that they are passing to the diff functions.

@[params]
pub struct CompareOptions {
pub:
	// ...
	// Sets the environment variable whose value can overwrite a diff command passed to a compare function.
	// It also enables the use of commands that are not in the list of known diff tools.
	// Set it to `none` to disable it.
	env_overwrite_var ?string = 'VDIFF_CMD'
}

Internally, the new variable is handled in a way that should better support custom options.

E.g. current master (this is stated to be working in v fmt --help, but doesn't)

VDIFF_TOOL="diff" VDIFF_OPTIONS="-W 120 -y" v fmt -diff file.v

No issues with the new command

VDIFF_CMD="diff -W 120 -y" v fmt -diff file.v

Screenshots and another example for this are in the last section of the PRs description.

Of course, when starting to use the new functions, documentation should be updated in the corresponding locations, like v fmt --help.

@ttytm
Copy link
Member Author

ttytm commented May 4, 2024

It is intended that everything is intuitive to use, please bare with me when explanations make things seem complicated. I'm open for questions if there are more concerns.

@spytheman spytheman merged commit 97984ad into vlang:master May 5, 2024
69 checks passed
@ttytm ttytm deleted the util/diff/new branch May 5, 2024 10:31
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

3 participants