Repository: ikamensh/flynt

Issue:
feature request: dryrun mode?
Would it be possible to support a mode that doesn't modify files in place? Perhaps just printing a diff to stdout about what would change, and then exit status based on whether any files would be modified?

Discussion:
ikamensh: Thanks for your contribution! 
few remarks:

1) Issue #48 was proposing  to e.g. print the diff to the stdout, which this version of the feature is not doing yet.
would you like to expand this contribution to print the diff? 

2) let's print a message at the beginning stating the changed behavior. Something like "Flynt is running in dry mode. Files will not be changed." Reasoning: prevent any confusion.
cphyc: Thanks for the feedback! Does this looks reasonable to you? 

Patch:
diff --git a/README.md b/README.md
index a4f43ec..797566f 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,10 @@ optional arguments:
   --no-multiline        convert only single line expressions
   -ll LINE_LENGTH, --line-length LINE_LENGTH
                         for expressions spanning multiple lines, convert only if the resulting single line will fit into the line length limit. Default value is 88 characters.
+  -d, --dry-run         Do not change the files in-place and print the diff
+                        instead. Note that this must be used in conjunction
+                        with '--fail-on-change' when used for linting
+                        purposes.
   -tc, --transform-concats
                         Replace string concatenations (defined as + operations involving string literals) with f-strings. Available only if flynt is installed with 3.8+
                         interpreter.
diff --git a/src/flynt/api.py b/src/flynt/api.py
index 4ffabeb..a409281 100644
--- a/src/flynt/api.py
+++ b/src/flynt/api.py
@@ -4,6 +4,7 @@ import sys
 import time
 import traceback
 from typing import Tuple, List
+from difflib import unified_diff
 
 import astor
 
@@ -77,8 +78,12 @@ def _fstringify_file(
         )
         return default_result()
 
-    with open(filename, "w", encoding="utf-8") as f:
-        f.write(new_code)
+    if state.dry_run:
+        for l in unified_diff(contents.split('\n'), new_code.split('\n'), fromfile=filename):
+            print(l)
+    else:
+        with open(filename, "w", encoding="utf-8") as f:
+            f.write(new_code)
 
     return True, changes, len(contents), len(new_code)
 
diff --git a/src/flynt/cli.py b/src/flynt/cli.py
index d374794..e10d4c1 100644
--- a/src/flynt/cli.py
+++ b/src/flynt/cli.py
@@ -52,6 +52,16 @@ def main():
         default=88,
     )
 
+    parser.add_argument(
+        "-d",
+        "--dry-run",
+        action="store_true",
+        default=False,
+        help="Do not change the files in-place and print the diff instead. "
+             "Note that this must be used in conjunction with '--fail-on-change' when "
+             "used for linting purposes."
+    )
+
     parser.add_argument(
         "-tc",
         "--transform-concats",
@@ -91,6 +101,9 @@ def main():
 
     args = parser.parse_args()
 
+    if args.dry_run:
+        print("Running flynt in dry-run mode. No files will be changed.")
+
     if args.transform_concats:
         if sys.version_info < (3, 8):
             raise Exception(
@@ -101,6 +114,7 @@ def main():
     state.aggressive = args.aggressive
     state.verbose = args.verbose
     state.quiet = args.quiet
+    state.dry_run = args.dry_run
 
     return fstringify(
         args.src,
diff --git a/src/flynt/state.py b/src/flynt/state.py
index 431aad8..7d9ada3 100644
--- a/src/flynt/state.py
+++ b/src/flynt/state.py
@@ -3,6 +3,7 @@
 verbose = False
 quiet = False
 aggressive = False
+dry_run = False
 
 percent_candidates = 0
 percent_transforms = 0


Test patch:
diff --git a/test/integration/test_api.py b/test/integration/test_api.py
index 5406110..0e87652 100644
--- a/test/integration/test_api.py
+++ b/test/integration/test_api.py
@@ -4,6 +4,7 @@ import shutil
 import pytest
 
 from flynt import api
+from flynt import state
 from flynt.api import _fstringify_file
 
 
@@ -91,3 +92,17 @@ def test_catches_subtle(formattable_file, monkeypatch):
 
     assert not modified
     assert content_after == content_before
+
+
+def test_dry_run(formattable_file, monkeypatch):
+    monkeypatch.setattr(state, "dry_run", True)
+    with open(formattable_file) as f:
+        content_before = f.read()
+
+    modified, _, _, _ = _fstringify_file(formattable_file, True, 1000)
+
+    with open(formattable_file) as f:
+        content_after = f.read()
+
+    assert modified
+    assert content_after == content_before