Skip to content

Commit ff053b8

Browse files
authored
Merge pull request #140 from matiasb/metadata-only-and-examples
Update README
2 parents b5d4cbc + f7f4b3e commit ff053b8

3 files changed

Lines changed: 109 additions & 12 deletions

File tree

README.rst

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ Quick start
4848
--- a/unidiff/utils.py
4949
+++ b/unidiff/utils.py
5050
@@ -37,4 +37,3 @@
51-
# - deleted line
52-
# \ No newline case (ignore)
53-
RE_HUNK_BODY_LINE = re.compile(r'^([- \+\\])')
51+
# - deleted line
52+
# \ No newline case (ignore)
53+
RE_HUNK_BODY_LINE = re.compile(r'^([- \+\\])')
5454
-
5555
5656
@@ -62,11 +62,6 @@ you can get stats (if it is a new, removed or modified file; the source/target
6262
lines; etc), besides having access to each hunk (also like a list) and its
6363
respective info.
6464

65-
For git diffs, the file mode is exposed through the :code:`source_mode` and
66-
:code:`target_mode` attributes (e.g. :code:`'100644'`, :code:`'100755'`,
67-
:code:`'120000'`), or :code:`None` when unknown. The :code:`is_symlink`
68-
property is a shortcut to detect symbolic links (mode :code:`120000`).
69-
7065
At any point you can get the string representation of the current object, and
7166
that will return the unified diff data of it.
7267

@@ -132,6 +127,91 @@ parsing more efficient:
132127
>>> patch = PatchSet.from_filename('tests/samples/bzr.diff', encoding='utf-8', metadata_only=True)
133128
134129
130+
Inspecting files, hunks and lines
131+
---------------------------------
132+
133+
.. code-block:: python
134+
135+
>>> from unidiff import PatchSet
136+
>>> patch = PatchSet.from_string(
137+
... '--- a/story.txt\n'
138+
... '+++ b/story.txt\n'
139+
... '@@ -1,4 +1,4 @@\n'
140+
... ' Once upon a time\n'
141+
... '-there was a bug\n'
142+
... '+there was a fix\n'
143+
... ' the end\n'
144+
... ' really\n')
145+
>>> patched_file = patch[0]
146+
>>> patched_file.path
147+
'story.txt'
148+
>>> patched_file.is_modified_file
149+
True
150+
>>> patched_file.added, patched_file.removed
151+
(1, 1)
152+
>>> hunk = patched_file[0]
153+
>>> hunk.source_start, hunk.target_start
154+
(1, 1)
155+
>>> removed = [line for line in hunk if line.is_removed]
156+
>>> len(removed)
157+
1
158+
>>> removed[0].value
159+
'there was a bug\n'
160+
>>> removed[0].source_line_no
161+
2
162+
>>> added = [line for line in hunk if line.is_added]
163+
>>> added[0].value, added[0].target_line_no
164+
('there was a fix\n', 2)
165+
166+
167+
Git file modes, symlinks and line numbers
168+
------------------------------------------
169+
170+
For git diffs, the file mode is exposed through the :code:`source_mode` and
171+
:code:`target_mode` attributes (e.g. :code:`'100644'`, :code:`'100755'`,
172+
:code:`'120000'`), or :code:`None` when unknown. The :code:`is_symlink`
173+
property is a shortcut to detect symbolic links (mode :code:`120000`):
174+
175+
.. code-block:: python
176+
177+
>>> from unidiff import PatchSet
178+
>>> patch = PatchSet.from_filename('tests/samples/git_symlink.diff')
179+
>>> patched_file = patch[0]
180+
>>> patched_file.path
181+
'bin/check'
182+
>>> patched_file.is_added_file
183+
True
184+
>>> patched_file.target_mode
185+
'120000'
186+
>>> patched_file.is_symlink
187+
True
188+
189+
Each :code:`PatchedFile` also exposes :code:`diff_line_no`, the 1-based line
190+
number in the diff where its entry starts. This is useful to locate files that
191+
have no hunks, such as binary changes:
192+
193+
.. code-block:: python
194+
195+
>>> from unidiff import PatchSet
196+
>>> patch = PatchSet.from_filename('tests/samples/debdiff.diff')
197+
>>> [(f.path, f.is_binary_file, f.diff_line_no) for f in patch]
198+
[('new/added.txt', False, 3), ('/t/p2/a.png', True, 6), ('/t/p2/b.png', True, 7)]
199+
200+
201+
Parsing from bytes
202+
------------------
203+
204+
:code:`PatchSet` and :code:`PatchSet.from_string` also accept :code:`bytes`,
205+
which are decoded using the given :code:`encoding` (defaulting to UTF-8):
206+
207+
.. code-block:: python
208+
209+
>>> from unidiff import PatchSet
210+
>>> patch = PatchSet(b'--- a/f\n+++ b/f\n@@ -1,2 +1,2 @@\n hola\n-mundo\n+world\n')
211+
>>> patch.added, patch.removed
212+
(1, 1)
213+
214+
135215
Diffs with embedded carriage returns or control characters
136216
----------------------------------------------------------
137217

tests/test_parser.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,21 @@ def test_patchset_from_string(self):
186186

187187
self.assertEqual(ps1, ps2)
188188

189+
def test_metadata_only_via_convenience_constructors(self):
190+
# from_filename and from_string should forward metadata_only (the
191+
# from_filename usage is documented in the README)
192+
ps_file = PatchSet.from_filename(
193+
self.sample_file, encoding='utf-8', metadata_only=True)
194+
with codecs.open(self.sample_file, 'r', encoding='utf-8') as diff_file:
195+
ps_string = PatchSet.from_string(diff_file.read(), metadata_only=True)
196+
197+
# counts are still computed under metadata_only
198+
self.assertEqual((ps_file.added, ps_file.removed), (21, 17))
199+
self.assertEqual((ps_string.added, ps_string.removed), (21, 17))
200+
# metadata_only skips storing the line content
201+
self.assertEqual(len(ps_file[0][0]), 0)
202+
self.assertEqual(len(ps_string[0][0]), 0)
203+
189204
def test_patchset_from_bytes_string(self):
190205
with codecs.open(self.sample_file, 'rb') as diff_file:
191206
diff_data = diff_file.read()

unidiff/patch.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -617,10 +617,11 @@ def _parse(self, diff: Iterable, encoding: Optional[str],
617617
@classmethod
618618
def from_filename(cls, filename: str, encoding: str = DEFAULT_ENCODING,
619619
errors: Optional[str] = None,
620-
newline: Optional[str] = None) -> PatchSet:
620+
newline: Optional[str] = None,
621+
metadata_only: bool = False) -> PatchSet:
621622
"""Return a PatchSet instance given a diff filename."""
622623
with open(filename, 'r', encoding=encoding, errors=errors, newline=newline) as f:
623-
instance = cls(f)
624+
instance = cls(f, metadata_only=metadata_only)
624625
return instance
625626

626627
@staticmethod
@@ -633,9 +634,10 @@ def _convert_string(data: Union[str, bytes], encoding: Optional[str] = None,
633634

634635
@classmethod
635636
def from_string(cls, data: Union[str, bytes], encoding: Optional[str] = None,
636-
errors: str = 'strict') -> PatchSet:
637+
errors: str = 'strict', metadata_only: bool = False) -> PatchSet:
637638
"""Return a PatchSet instance given a diff string."""
638-
return cls(cls._convert_string(data, encoding, errors))
639+
return cls(cls._convert_string(data, encoding, errors),
640+
metadata_only=metadata_only)
639641

640642
@property
641643
def added_files(self) -> list[PatchedFile]:

0 commit comments

Comments
 (0)